diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a8877c4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: go - -go: - - tip - -env: - - "PATH=/home/travis/gopath/bin:$PATH" - -install: true - -script: - - env GO111MODULE=on go build -v - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/compress - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/croc - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/crypt - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/tcp - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/utils - - env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/comm - -branches: - except: - - dev - - win diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9cfa3bc..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM golang:1.12-alpine as builder -RUN apk add --no-cache git -WORKDIR /go/croc -COPY . . -RUN go build -v - -FROM alpine:latest -EXPOSE 9009 -EXPOSE 9010 -EXPOSE 9011 -EXPOSE 9012 -EXPOSE 9013 -COPY --from=builder /go/croc/croc /croc -ENTRYPOINT ["/croc"] -CMD ["relay"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0ca9765..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Zack - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 93d8557..85281bb 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,7 @@ - -

-croc -
-Version -Coverage -Build
-Status -Say thanks -

- - -

curl https://getcroc.schollz.com | bash

- -`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool does **all** of the following: - -- allows **any two computers** to transfer data (using a relay) -- provides **end-to-end encryption** (using PAKE) -- enables easy **cross-platform** transfers (Windows, Linux, Mac) -- allows **multiple file** transfers -- allows **resuming transfers** that are interrupted -- local server or port-forwarding **not needed** -- **faster** than [wormhole](https://github.com/warner/magic-wormhole), [rsync](https://linux.die.net/man/1/rsync), [scp](https://linux.die.net/man/1/scp) through compression and multiplexing (speedups 1.5x to 4x) - -For more information about `croc`, see [my blog post](https://schollz.com/software/croc6). - - -## Install - -Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line: - ``` -$ curl https://getcroc.schollz.com | bash +Work in progress. + +This will be yet another re-write of croc (the 7th!). This time it should be +compatible with a browser as it will use WebRTC by default for data transfer and +it the main relay (for PAKE and WebRTC offer exchange) will be made using websockets. ``` - - -On macOS you can install the latest release with [Homebrew](https://brew.sh/): - -``` -$ brew install schollz/tap/croc -``` - - -On Windows you can install the latest release with [Scoop](https://scoop.sh/): - -``` -$ scoop bucket add schollz-bucket https://github.com/schollz/scoop-bucket.git -$ scoop install croc -``` - - -Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.12+): - -``` -$ go get -v github.com/schollz/croc -``` - - - -## Usage - -To send a file, simply do: - -``` -$ croc send [file(s)-or-folder] -Sending 'file-or-folder' (X MB) -Code is: code-phrase -``` - -Then to receive the file (or folder) on another computer, you can just do - -``` -$ croc code-phrase -``` - -The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption. - -There are a number of configurable options (see `--help`). A set of options (like custom relay, ports, and code phrase) can be set using `--remember`. - -### Custom code phrase - -You can send with your own code phrase (must be more than 4 characters). - -``` -$ croc send --code [code-phrase] [file(s)-or-folder] -``` - -### Use pipes - stdin and stdout - -You can pipe to `croc`: - -``` -$ cat [filename] | croc send -``` - -In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` will automatically approve the transfer and pipe it out to `stdout`. - -``` -$ croc --yes [code-phrase] > out -``` - -All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`. - -### Self-host relay - -The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay: - -``` -$ croc relay -``` - -Make sure to open up TCP ports (see `croc relay --help` for which ports to open). - -You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own. - -``` -$ croc --relay "myrelay.example.com:9009" send [filename] -``` - -If it's easier you can also run a relay with Docker: - - -``` -$ docker run -d -p 9009:9009 -p 9010:9010 -p 9011:9011 -p 9012:9012 -p 9013:9013 schollz/croc -``` - -## License - -MIT - -## Acknowledgements - -`croc` has been through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)). - -Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@meyermarcel](https://github.com/meyermarcel), [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy)! diff --git a/go.mod b/go.mod deleted file mode 100644 index b716fb8..0000000 --- a/go.mod +++ /dev/null @@ -1,30 +0,0 @@ -module github.com/schollz/croc/v6 - -go 1.13 - -require ( - github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/cespare/xxhash v1.1.0 - github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/denisbrodbeck/machineid v1.0.1 - github.com/fatih/color v1.7.0 // indirect - github.com/kalafut/imohash v1.0.0 - github.com/kr/pretty v0.1.0 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.9 // indirect - github.com/pkg/errors v0.8.1 - github.com/schollz/logger v1.0.1 - github.com/schollz/mnemonicode v1.0.1 - github.com/schollz/pake v1.1.1 - github.com/schollz/peerdiscovery v1.4.1 - github.com/schollz/progressbar/v2 v2.14.0 - github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9 - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.4.0 - github.com/urfave/cli v1.22.1 - golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc - golang.org/x/net v0.0.0-20191003171128-d98b1b443823 // indirect - golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 // indirect - golang.org/x/text v0.3.2 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 2dccc0e..0000000 --- a/go.sum +++ /dev/null @@ -1,102 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= -github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= -github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/kalafut/imohash v1.0.0 h1:LgCJ+p/BwM2HKpOxFopkeddpzVCfm15EtXMroXD1SYE= -github.com/kalafut/imohash v1.0.0/go.mod h1:c3RHT80ZAp5C/aYgQI92ZlrOymqkZnRDprU87kg75HI= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/logger v1.0.1 h1:BuBAU+euqphM0Ny9qFVScl4RSxatis4nCHIkOxO2cUU= -github.com/schollz/logger v1.0.1/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM= -github.com/schollz/mnemonicode v1.0.1 h1:LiH5hwADZwjwnfXsaD4xgnMyTAtaKHN+e5AyjRU6WSU= -github.com/schollz/mnemonicode v1.0.1/go.mod h1:cl4UAOhUV0mkdjMj/QYaUZbZZdF8BnOqoz8rHMzwboY= -github.com/schollz/pake v1.1.1 h1:QKeojDWzdAdtRC4m89b6HAxw/8gjqrVu7r4SAOxOFg8= -github.com/schollz/pake v1.1.1/go.mod h1:aWMxQ1jwqZRwk3StflHcdyzPR+CyW5W7+WIZD6Y3dEY= -github.com/schollz/peerdiscovery v1.4.1 h1:xtZ/D8/4eq9O6UEhRupZZiJm4BA8+u1IVUgeHo5VPm4= -github.com/schollz/peerdiscovery v1.4.1/go.mod h1:WDdk0/JVyVHVIA/bmhzTkUg32dhJ20O4tExNqV1u6sk= -github.com/schollz/progressbar/v2 v2.13.2 h1:3L9bP5KQOGEnFP8P5V8dz+U0yo5I29iY5Oa9s9EAwn0= -github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8= -github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM= -github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8= -github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9 h1:y08o5oQ/slxXE/F0uh5dd8mdVvb+w4NLcNSDSq4c2F0= -github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9/go.mod h1:kCMoQsqzx4MzGJWaALr6tKyCnlrY0kILGLkA1FOiLF4= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937 h1:lhssCpSe3TjKcbvUoPzFMuv9oUyZDgI3Cmgolfw2C90= -github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4= -golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191003171128-d98b1b443823 h1:Ypyv6BNJh07T1pUSrehkLemqPKXhus2MkfktJ91kRh4= -golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY= -golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/tylerb/is.v1 v1.1.2 h1:AB/MANFml2ySf+adwcinvajyHvsYltAOD+rb/8njfSU= -gopkg.in/tylerb/is.v1 v1.1.2/go.mod h1:9yQB2tyIhZ5oph6Kk5Sq7cJMd9c5Jpa1p3hr9kxzPqo= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/goreleaser.yml b/goreleaser.yml deleted file mode 100644 index e378837..0000000 --- a/goreleaser.yml +++ /dev/null @@ -1,86 +0,0 @@ -project_name: croc -build: - main: main.go - binary: croc - ldflags: -s -w -X main.Version="v{{.Version}}-{{.Date}}" - env: - - CGO_ENABLED=0 - goos: - - darwin - - linux - - windows - - freebsd - - netbsd - - openbsd - - dragonfly - goarch: - - amd64 - - 386 - - arm - - arm64 - goarm: - - 7 -nfpm: - formats: - - deb - vendor: "schollz.com" - homepage: "https://schollz.com/software/croc/" - maintainer: "Zack Scholl " - description: "A simple, secure, and fast way to transfer data." - license: "MIT" - name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" - replacements: - amd64: 64bit - 386: 32bit - arm: ARM - arm64: ARM64 - darwin: macOS - linux: Linux - windows: Windows - openbsd: OpenBSD - netbsd: NetBSD - freebsd: FreeBSD - dragonfly: DragonFlyBSD -archives: - - - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" - replacements: - amd64: 64bit - 386: 32bit - arm: ARM - arm64: ARM64 - darwin: macOS - linux: Linux - windows: Windows - openbsd: OpenBSD - netbsd: NetBSD - freebsd: FreeBSD - dragonfly: DragonFlyBSD - files: - - README.md - - LICENSE - -brew: - github: - owner: schollz - name: homebrew-tap - folder: Formula - description: "croc is a tool that allows any two computers to simply and securely transfer files and folders." - homepage: "https://schollz.com/software/croc/" - install: | - bin.install "croc" - - test: | - system "#{bin}/croc --version" - -scoop: - bucket: - owner: schollz - name: scoop-bucket - homepage: "https://schollz.com/software/croc/" - description: "croc is a tool that allows any two computers to simply and securely transfer files and folders." - license: MIT \ No newline at end of file diff --git a/main.go b/main.go deleted file mode 100644 index ccf97c0..0000000 --- a/main.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -//go:generate git tag -af v$VERSION -m "v$VERSION" -//go:generate go run src/install/updateversion.go -//go:generate git commit -am "bump $VERSION" -//go:generate git tag -af v$VERSION -m "v$VERSION" - -import ( - "fmt" - - "github.com/schollz/croc/v6/src/cli" -) - -func main() { - if err := cli.Run(); err != nil { - fmt.Println(err) - } -} diff --git a/src/cli/cli.go b/src/cli/cli.go deleted file mode 100644 index b15c91e..0000000 --- a/src/cli/cli.go +++ /dev/null @@ -1,379 +0,0 @@ -package cli - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/schollz/croc/v6/src/croc" - "github.com/schollz/croc/v6/src/models" - "github.com/schollz/croc/v6/src/tcp" - "github.com/schollz/croc/v6/src/utils" - log "github.com/schollz/logger" - "github.com/urfave/cli" -) - -// Version specifies the version -var Version string - -// Run will run the command line proram -func Run() (err error) { - // use all of the processors - runtime.GOMAXPROCS(runtime.NumCPU()) - - app := cli.NewApp() - app.Name = "croc" - if Version == "" { - Version = "v6.2.0-ffddd3e" - } - app.Version = Version - app.Compiled = time.Now() - app.Usage = "easily and securely transfer stuff from one computer to another" - app.UsageText = "croc allows any two computers to directly and securely transfer files" - // app.ArgsUsage = "[args and such]" - app.Commands = []cli.Command{ - { - Name: "send", - Usage: "send a file", - Description: "send a file over the relay", - ArgsUsage: "[filename]", - Flags: []cli.Flag{ - cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"}, - cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"}, - cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the local relay (optional)"}, - }, - HelpName: "croc send", - Action: func(c *cli.Context) error { - return send(c) - }, - }, - { - Name: "relay", - Description: "start relay", - HelpName: "croc relay", - Action: func(c *cli.Context) error { - return relay(c) - }, - Flags: []cli.Flag{ - cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"}, - }, - }, - } - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"}, - cli.BoolFlag{Name: "debug", Usage: "increase verbosity (a lot)"}, - cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"}, - cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"}, - cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"}, - cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay"}, - cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"}, - } - app.EnableBashCompletion = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "send\nreceive\relay") - } - app.Action = func(c *cli.Context) error { - // if trying to send but forgot send, let the user know - if c.Args().First() != "" && utils.Exists(c.Args().First()) { - _, fname := filepath.Split(c.Args().First()) - yn := utils.GetInput(fmt.Sprintf("Did you mean to send '%s'? (y/n) ", fname)) - if strings.ToLower(yn) == "y" { - return send(c) - } - } - return receive(c) - } - - return app.Run(os.Args) -} - -func getConfigDir() (homedir string, err error) { - homedir, err = os.UserHomeDir() - if err != nil { - log.Error(err) - return - } - homedir = path.Join(homedir, ".config", "croc") - if _, err = os.Stat(homedir); os.IsNotExist(err) { - log.Debugf("creating home directory %s", homedir) - err = os.MkdirAll(homedir, 0700) - } - return -} - -func setDebugLevel(c *cli.Context) { - if c.GlobalBool("debug") { - log.SetLevel("debug") - log.Debug("debug mode on") - } else { - log.SetLevel("info") - } -} - -func getConfigFile() string { - configFile, err := getConfigDir() - if err != nil { - log.Error(err) - return "" - } - return path.Join(configFile, "send.json") -} - -func send(c *cli.Context) (err error) { - setDebugLevel(c) - crocOptions := croc.Options{ - SharedSecret: c.String("code"), - IsSender: true, - Debug: c.GlobalBool("debug"), - NoPrompt: c.GlobalBool("yes"), - RelayAddress: c.GlobalString("relay"), - Stdout: c.GlobalBool("stdout"), - DisableLocal: c.Bool("no-local"), - RelayPorts: strings.Split(c.String("ports"), ","), - Ask: c.GlobalBool("ask"), - } - b, errOpen := ioutil.ReadFile(getConfigFile()) - if errOpen == nil && !c.GlobalBool("remember") { - var rememberedOptions croc.Options - err = json.Unmarshal(b, &rememberedOptions) - if err != nil { - log.Error(err) - return - } - // update anything that isn't explicitly set - if !c.GlobalIsSet("relay") { - crocOptions.RelayAddress = rememberedOptions.RelayAddress - } - if !c.IsSet("no-local") { - crocOptions.DisableLocal = rememberedOptions.DisableLocal - } - if !c.IsSet("ports") { - crocOptions.RelayPorts = rememberedOptions.RelayPorts - } - if !c.IsSet("code") { - crocOptions.SharedSecret = rememberedOptions.SharedSecret - } - } - - var fnames []string - stat, _ := os.Stdin.Stat() - if (stat.Mode() & os.ModeCharDevice) == 0 { - fnames, err = getStdin() - if err != nil { - return - } - defer func() { - err = os.Remove(fnames[0]) - if err != nil { - log.Error(err) - } - }() - } else { - fnames = append([]string{c.Args().First()}, c.Args().Tail()...) - } - if len(fnames) == 0 { - return errors.New("must specify file: croc send [filename]") - } - - if len(crocOptions.SharedSecret) == 0 { - // generate code phrase - crocOptions.SharedSecret = utils.GetRandomName() - } - - paths, haveFolder, err := getPaths(fnames) - if err != nil { - return - } - - cr, err := croc.New(crocOptions) - if err != nil { - return - } - - // save the config - saveConfig(c, crocOptions) - - err = cr.Send(croc.TransferOptions{ - PathToFiles: paths, - KeepPathInRemote: haveFolder, - }) - - return -} - -func getStdin() (fnames []string, err error) { - f, err := ioutil.TempFile(".", "croc-stdin-") - if err != nil { - return - } - _, err = io.Copy(f, os.Stdin) - if err != nil { - return - } - err = f.Close() - if err != nil { - return - } - fnames = []string{f.Name()} - return -} - -func getPaths(fnames []string) (paths []string, haveFolder bool, err error) { - haveFolder = false - paths = []string{} - for _, fname := range fnames { - stat, errStat := os.Stat(fname) - if errStat != nil { - err = errStat - return - } - if stat.IsDir() { - haveFolder = true - err = filepath.Walk(fname, - func(pathName string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() { - paths = append(paths, filepath.ToSlash(pathName)) - } - return nil - }) - if err != nil { - return - } - } else { - paths = append(paths, filepath.ToSlash(fname)) - } - } - return -} - -func saveConfig(c *cli.Context, crocOptions croc.Options) { - if c.GlobalBool("remember") { - configFile := getConfigFile() - log.Debug("saving config file") - var bConfig []byte - // if the code wasn't set, don't save it - if c.String("code") == "" { - crocOptions.SharedSecret = "" - } - bConfig, err := json.MarshalIndent(crocOptions, "", " ") - if err != nil { - log.Error(err) - return - } - err = ioutil.WriteFile(configFile, bConfig, 0644) - if err != nil { - log.Error(err) - return - } - log.Debugf("wrote %s", configFile) - } -} - -func receive(c *cli.Context) (err error) { - crocOptions := croc.Options{ - SharedSecret: c.String("code"), - IsSender: false, - Debug: c.GlobalBool("debug"), - NoPrompt: c.GlobalBool("yes"), - RelayAddress: c.GlobalString("relay"), - Stdout: c.GlobalBool("stdout"), - Ask: c.GlobalBool("ask"), - } - if c.Args().First() != "" { - crocOptions.SharedSecret = c.Args().First() - } - - // load options here - setDebugLevel(c) - configFile, err := getConfigDir() - if err != nil { - log.Error(err) - return - } - configFile = path.Join(configFile, "receive.json") - b, errOpen := ioutil.ReadFile(configFile) - if errOpen == nil && !c.GlobalBool("remember") { - var rememberedOptions croc.Options - err = json.Unmarshal(b, &rememberedOptions) - if err != nil { - log.Error(err) - return - } - // update anything that isn't explicitly set - if !c.GlobalIsSet("relay") { - crocOptions.RelayAddress = rememberedOptions.RelayAddress - } - if !c.GlobalIsSet("yes") { - crocOptions.NoPrompt = rememberedOptions.NoPrompt - } - if crocOptions.SharedSecret == "" { - crocOptions.SharedSecret = rememberedOptions.SharedSecret - } - } - - if crocOptions.SharedSecret == "" { - crocOptions.SharedSecret = utils.GetInput("Enter receive code: ") - } - if c.GlobalString("out") != "" { - os.Chdir(c.GlobalString("out")) - } - - cr, err := croc.New(crocOptions) - if err != nil { - return - } - - // save the config - if c.GlobalBool("remember") { - log.Debug("saving config file") - var bConfig []byte - bConfig, err = json.MarshalIndent(crocOptions, "", " ") - if err != nil { - log.Error(err) - return - } - err = ioutil.WriteFile(configFile, bConfig, 0644) - if err != nil { - log.Error(err) - return - } - log.Debugf("wrote %s", configFile) - } - - err = cr.Receive() - return -} - -func relay(c *cli.Context) (err error) { - debugString := "info" - if c.GlobalBool("debug") { - debugString = "debug" - } - ports := strings.Split(c.String("ports"), ",") - tcpPorts := strings.Join(ports[1:], ",") - for i, port := range ports { - if i == 0 { - continue - } - go func(portStr string) { - err = tcp.Run(debugString, portStr) - if err != nil { - panic(err) - } - }(port) - } - return tcp.Run(debugString, ports[0], tcpPorts) -} diff --git a/src/comm/comm.go b/src/comm/comm.go deleted file mode 100644 index 4976126..0000000 --- a/src/comm/comm.go +++ /dev/null @@ -1,122 +0,0 @@ -package comm - -import ( - "bytes" - "encoding/binary" - "fmt" - "net" - "time" - - "github.com/pkg/errors" -) - -// Comm is some basic TCP communication -type Comm struct { - connection net.Conn -} - -// NewConnection gets a new comm to a tcp address -func NewConnection(address string, timelimit ...time.Duration) (c *Comm, err error) { - tlimit := 30 * time.Second - if len(timelimit) > 0 { - tlimit = timelimit[0] - } - connection, err := net.DialTimeout("tcp", address, tlimit) - if err != nil { - return - } - c = New(connection) - return -} - -// New returns a new comm -func New(c net.Conn) *Comm { - c.SetReadDeadline(time.Now().Add(3 * time.Hour)) - c.SetDeadline(time.Now().Add(3 * time.Hour)) - c.SetWriteDeadline(time.Now().Add(3 * time.Hour)) - comm := new(Comm) - comm.connection = c - return comm -} - -// Connection returns the net.Conn connection -func (c *Comm) Connection() net.Conn { - return c.connection -} - -// Close closes the connection -func (c *Comm) Close() { - c.connection.Close() -} - -func (c *Comm) Write(b []byte) (int, error) { - header := new(bytes.Buffer) - err := binary.Write(header, binary.LittleEndian, uint32(len(b))) - if err != nil { - fmt.Println("binary.Write failed:", err) - } - tmpCopy := append(header.Bytes(), b...) - n, err := c.connection.Write(tmpCopy) - if n != len(tmpCopy) { - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("wanted to write %d but wrote %d", len(b), n)) - } else { - err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n) - } - } - // log.Printf("wanted to write %d but wrote %d", n, len(b)) - return n, err -} - -func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) { - // read until we get 4 bytes for the header - var header []byte - numBytes = 4 - for { - tmp := make([]byte, numBytes-len(header)) - n, errRead := c.connection.Read(tmp) - if errRead != nil { - err = errRead - return - } - header = append(header, tmp[:n]...) - if numBytes == len(header) { - break - } - } - - var numBytesUint32 uint32 - rbuf := bytes.NewReader(header) - err = binary.Read(rbuf, binary.LittleEndian, &numBytesUint32) - if err != nil { - fmt.Println("binary.Read failed:", err) - } - numBytes = int(numBytesUint32) - buf = make([]byte, 0) - for { - // log.Debugf("bytes: %d/%d",len(buf),numBytes) - tmp := make([]byte, numBytes-len(buf)) - n, errRead := c.connection.Read(tmp) - if errRead != nil { - err = errRead - return - } - buf = append(buf, tmp[:n]...) - if numBytes == len(buf) { - break - } - } - return -} - -// Send a message -func (c *Comm) Send(message []byte) (err error) { - _, err = c.Write(message) - return -} - -// Receive a message -func (c *Comm) Receive() (b []byte, err error) { - b, _, _, err = c.Read() - return -} diff --git a/src/comm/comm_test.go b/src/comm/comm_test.go deleted file mode 100644 index 6f986f4..0000000 --- a/src/comm/comm_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package comm - -import ( - "crypto/rand" - "net" - "testing" - "time" - - log "github.com/schollz/logger" - "github.com/stretchr/testify/assert" -) - -func TestComm(t *testing.T) { - token := make([]byte, 40000000) - rand.Read(token) - - port := "8001" - go func() { - log.Debugf("starting TCP server on " + port) - server, err := net.Listen("tcp", "0.0.0.0:"+port) - if err != nil { - log.Error(err) - } - defer server.Close() - // spawn a new goroutine whenever a client connects - for { - connection, err := server.Accept() - if err != nil { - log.Error(err) - } - log.Debugf("client %s connected", connection.RemoteAddr().String()) - go func(port string, connection net.Conn) { - c := New(connection) - err = c.Send([]byte("hello, world")) - assert.Nil(t, err) - data, err := c.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte("hello, computer"), data) - data, err = c.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte{'\x00'}, data) - data, err = c.Receive() - assert.Nil(t, err) - assert.Equal(t, token, data) - }(port, connection) - } - }() - - time.Sleep(300 * time.Millisecond) - a, err := NewConnection("localhost:"+port, 10*time.Minute) - assert.Nil(t, err) - data, err := a.Receive() - assert.Equal(t, []byte("hello, world"), data) - assert.Nil(t, err) - assert.Nil(t, a.Send([]byte("hello, computer"))) - assert.Nil(t, a.Send([]byte{'\x00'})) - - assert.Nil(t, a.Send(token)) - _ = a.Connection() - a.Close() - assert.NotNil(t, a.Send(token)) - _, err = a.Write(token) - assert.NotNil(t, err) - -} diff --git a/src/compress/compress.go b/src/compress/compress.go deleted file mode 100644 index e23dd3e..0000000 --- a/src/compress/compress.go +++ /dev/null @@ -1,43 +0,0 @@ -package compress - -import ( - "bytes" - "compress/flate" - "io" -) - -// CompressWithOption returns compressed data using the specified level -func CompressWithOption(src []byte, level int) []byte { - compressedData := new(bytes.Buffer) - compress(src, compressedData, level) - return compressedData.Bytes() -} - -// Compress returns a compressed byte slice. -func Compress(src []byte) []byte { - compressedData := new(bytes.Buffer) - compress(src, compressedData, -2) - return compressedData.Bytes() -} - -// Decompress returns a decompressed byte slice. -func Decompress(src []byte) []byte { - compressedData := bytes.NewBuffer(src) - deCompressedData := new(bytes.Buffer) - decompress(compressedData, deCompressedData) - return deCompressedData.Bytes() -} - -// compress uses flate to compress a byte slice to a corresponding level -func compress(src []byte, dest io.Writer, level int) { - compressor, _ := flate.NewWriter(dest, level) - compressor.Write(src) - compressor.Close() -} - -// compress uses flate to decompress an io.Reader -func decompress(src io.Reader, dest io.Writer) { - decompressor := flate.NewReader(src) - io.Copy(dest, decompressor) - decompressor.Close() -} diff --git a/src/compress/compress_test.go b/src/compress/compress_test.go deleted file mode 100644 index e48ed92..0000000 --- a/src/compress/compress_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package compress - -import ( - "crypto/rand" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -var fable = []byte(`The Frog and the Crocodile -Once, there was a frog who lived in the middle of a swamp. His entire family had lived in that swamp for generations, but this particular frog decided that he had had quite enough wetness to last him a lifetime. He decided that he was going to find a dry place to live instead. - -The only thing that separated him from dry land was a swampy, muddy, swiftly flowing river. But the river was home to all sorts of slippery, slittering snakes that loved nothing better than a good, plump frog for dinner, so Frog didn't dare try to swim across. - -So for many days, the frog stayed put, hopping along the bank, trying to think of a way to get across. - -The snakes hissed and jeered at him, daring him to come closer, but he refused. Occasionally they would slither closer, jaws open to attack, but the frog always leaped out of the way. But no matter how far upstream he searched or how far downstream, the frog wasn't able to find a way across the water. - -He had felt certain that there would be a bridge, or a place where the banks came together, yet all he found was more reeds and water. After a while, even the snakes stopped teasing him and went off in search of easier prey. - -The frog sighed in frustration and sat to sulk in the rushes. Suddenly, he spotted two big eyes staring at him from the water. The giant log-shaped animal opened its mouth and asked him, "What are you doing, Frog? Surely there are enough flies right there for a meal." - -The frog croaked in surprise and leaped away from the crocodile. That creature could swallow him whole in a moment without thinking about it! Once he was a satisfied that he was a safe distance away, he answered. "I'm tired of living in swampy waters, and I want to travel to the other side of the river. But if I swim across, the snakes will eat me." - -The crocodile harrumphed in agreement and sat, thinking, for a while. "Well, if you're afraid of the snakes, I could give you a ride across," he suggested. - -"Oh no, I don't think so," Frog answered quickly. "You'd eat me on the way over, or go underwater so the snakes could get me!" - -"Now why would I let the snakes get you? I think they're a terrible nuisance with all their hissing and slithering! The river would be much better off without them altogether! Anyway, if you're so worried that I might eat you, you can ride on my tail." - -The frog considered his offer. He did want to get to dry ground very badly, and there didn't seem to be any other way across the river. He looked at the crocodile from his short, squat buggy eyes and wondered about the crocodile's motives. But if he rode on the tail, the croc couldn't eat him anyway. And he was right about the snakes--no self-respecting crocodile would give a meal to the snakes. - -"Okay, it sounds like a good plan to me. Turn around so I can hop on your tail." - -The crocodile flopped his tail into the marshy mud and let the frog climb on, then he waddled out to the river. But he couldn't stick his tail into the water as a rudder because the frog was on it -- and if he put his tail in the water, the snakes would eat the frog. They clumsily floated downstream for a ways, until the crocodile said, "Hop onto my back so I can steer straight with my tail." The frog moved, and the journey smoothed out. - -From where he was sitting, the frog couldn't see much except the back of Crocodile's head. "Why don't you hop up on my head so you can see everything around us?" Crocodile invited. `) - -func BenchmarkCompressLevelMinusTwo(b *testing.B) { - for i := 0; i < b.N; i++ { - CompressWithOption(fable, -2) - } -} - -func BenchmarkCompressLevelNine(b *testing.B) { - for i := 0; i < b.N; i++ { - CompressWithOption(fable, 9) - } -} - -func BenchmarkCompressLevelMinusTwoBinary(b *testing.B) { - data := make([]byte, 1000000) - rand.Read(data) - for i := 0; i < b.N; i++ { - CompressWithOption(data, -2) - } -} - -func BenchmarkCompressLevelNineBinary(b *testing.B) { - data := make([]byte, 1000000) - rand.Read(data) - for i := 0; i < b.N; i++ { - CompressWithOption(data, 9) - } -} - -func TestCompress(t *testing.T) { - compressedB := CompressWithOption(fable, 9) - dataRateSavings := 100 * (1.0 - float64(len(compressedB))/float64(len(fable))) - fmt.Printf("Level 9: %2.0f%% percent space savings\n", dataRateSavings) - assert.True(t, len(compressedB) < len(fable)) - assert.Equal(t, fable, Decompress(compressedB)) - - compressedB = CompressWithOption(fable, -2) - dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(fable))) - fmt.Printf("Level -2: %2.0f%% percent space savings\n", dataRateSavings) - assert.True(t, len(compressedB) < len(fable)) - - compressedB = Compress(fable) - dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(fable))) - fmt.Printf("Level -2: %2.0f%% percent space savings\n", dataRateSavings) - assert.True(t, len(compressedB) < len(fable)) - - data := make([]byte, 4096) - rand.Read(data) - compressedB = CompressWithOption(data, -2) - dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data))) - fmt.Printf("random, Level -2: %2.0f%% percent space savings\n", dataRateSavings) - - rand.Read(data) - compressedB = CompressWithOption(data, 9) - dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data))) - - fmt.Printf("random, Level 9: %2.0f%% percent space savings\n", dataRateSavings) - -} diff --git a/src/croc/croc.go b/src/croc/croc.go deleted file mode 100644 index ac3356a..0000000 --- a/src/croc/croc.go +++ /dev/null @@ -1,1150 +0,0 @@ -package croc - -import ( - "bytes" - "crypto/elliptic" - "crypto/rand" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/denisbrodbeck/machineid" - "github.com/pkg/errors" - "github.com/schollz/croc/v6/src/comm" - "github.com/schollz/croc/v6/src/compress" - "github.com/schollz/croc/v6/src/crypt" - "github.com/schollz/croc/v6/src/message" - "github.com/schollz/croc/v6/src/models" - "github.com/schollz/croc/v6/src/tcp" - "github.com/schollz/croc/v6/src/utils" - log "github.com/schollz/logger" - "github.com/schollz/pake" - "github.com/schollz/peerdiscovery" - "github.com/schollz/progressbar/v2" - "github.com/schollz/spinner" -) - -func init() { - log.SetLevel("debug") -} - -// Debug toggles debug mode -func Debug(debug bool) { - if debug { - log.SetLevel("debug") - } else { - log.SetLevel("warn") - } -} - -// Options specifies user specific options -type Options struct { - IsSender bool - SharedSecret string - Debug bool - RelayAddress string - RelayPorts []string - Stdout bool - NoPrompt bool - DisableLocal bool - Ask bool -} - -// Client holds the state of the croc transfer -type Client struct { - Options Options - Pake *pake.Pake - Key crypt.Encryption - ExternalIP, ExternalIPConnected string - - // steps involved in forming relationship - Step1ChannelSecured bool - Step2FileInfoTransfered bool - Step3RecipientRequestFile bool - Step4FileTransfer bool - Step5CloseChannels bool - SuccessfulTransfer bool - - // send / receive information of all files - FilesToTransfer []FileInfo - FilesToTransferCurrentNum int - FilesHasFinished map[int]struct{} - - // send / receive information of current file - CurrentFile *os.File - CurrentFileChunkRanges []int64 - CurrentFileChunks []int64 - - TotalSent int64 - TotalChunksTransfered int - chunkMap map[uint64]struct{} - - // tcp connections - conn []*comm.Comm - - bar *progressbar.ProgressBar - spinner *spinner.Spinner - longestFilename int - firstSend bool - - mutex *sync.Mutex - fread *os.File - numfinished int - quit chan bool -} - -// Chunk contains information about the -// needed bytes -type Chunk struct { - Bytes []byte `json:"b,omitempty"` - Location int64 `json:"l,omitempty"` -} - -// FileInfo registers the information about the file -type FileInfo struct { - Name string `json:"n,omitempty"` - FolderRemote string `json:"fr,omitempty"` - FolderSource string `json:"fs,omitempty"` - Hash []byte `json:"h,omitempty"` - Size int64 `json:"s,omitempty"` - ModTime time.Time `json:"m,omitempty"` - IsCompressed bool `json:"c,omitempty"` - IsEncrypted bool `json:"e,omitempty"` -} - -// RemoteFileRequest requests specific bytes -type RemoteFileRequest struct { - CurrentFileChunkRanges []int64 - FilesToTransferCurrentNum int - MachineID string -} - -// SenderInfo lists the files to be transferred -type SenderInfo struct { - FilesToTransfer []FileInfo - MachineID string - Ask bool -} - -// New establishes a new connection for transferring files between two instances. -func New(ops Options) (c *Client, err error) { - c = new(Client) - c.FilesHasFinished = make(map[int]struct{}) - - // setup basic info - c.Options = ops - Debug(c.Options.Debug) - log.Debugf("options: %+v", c.Options) - - c.conn = make([]*comm.Comm, 16) - - // use default key (no encryption, until PAKE succeeds) - c.Key, err = crypt.New(nil, nil) - if err != nil { - return - } - - // initialize pake - if c.Options.IsSender { - c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 1, elliptic.P521(), 1*time.Microsecond) - } else { - c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 0, elliptic.P521(), 1*time.Microsecond) - } - if err != nil { - return - } - - c.mutex = &sync.Mutex{} - return -} - -// TransferOptions for sending -type TransferOptions struct { - PathToFiles []string - KeepPathInRemote bool -} - -func (c *Client) sendCollectFiles(options TransferOptions) (err error) { - c.FilesToTransfer = make([]FileInfo, len(options.PathToFiles)) - totalFilesSize := int64(0) - for i, pathToFile := range options.PathToFiles { - var fstats os.FileInfo - var fullPath string - fullPath, err = filepath.Abs(pathToFile) - if err != nil { - return - } - fullPath = filepath.Clean(fullPath) - var folderName string - folderName, _ = filepath.Split(fullPath) - - fstats, err = os.Stat(fullPath) - if err != nil { - return - } - if len(fstats.Name()) > c.longestFilename { - c.longestFilename = len(fstats.Name()) - } - c.FilesToTransfer[i] = FileInfo{ - Name: fstats.Name(), - FolderRemote: ".", - FolderSource: folderName, - Size: fstats.Size(), - ModTime: fstats.ModTime(), - } - c.FilesToTransfer[i].Hash, err = utils.HashFile(fullPath) - totalFilesSize += fstats.Size() - if err != nil { - return - } - if options.KeepPathInRemote { - var curFolder string - curFolder, err = os.Getwd() - if err != nil { - return - } - curFolder, err = filepath.Abs(curFolder) - if err != nil { - return - } - if !strings.HasPrefix(folderName, curFolder) { - err = fmt.Errorf("remote directory must be relative to current") - return - } - c.FilesToTransfer[i].FolderRemote = strings.TrimPrefix(folderName, curFolder) - c.FilesToTransfer[i].FolderRemote = filepath.ToSlash(c.FilesToTransfer[i].FolderRemote) - c.FilesToTransfer[i].FolderRemote = strings.TrimPrefix(c.FilesToTransfer[i].FolderRemote, "/") - if c.FilesToTransfer[i].FolderRemote == "" { - c.FilesToTransfer[i].FolderRemote = "." - } - } - log.Debugf("file %d info: %+v", i, c.FilesToTransfer[i]) - } - log.Debugf("longestFilename: %+v", c.longestFilename) - fname := fmt.Sprintf("%d files", len(c.FilesToTransfer)) - if len(c.FilesToTransfer) == 1 { - fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name) - } - fmt.Fprintf(os.Stderr, "Sending %s (%s)\n", fname, utils.ByteCountDecimal(totalFilesSize)) - return -} - -func (c *Client) setupLocalRelay() { - // setup the relay locally - for _, port := range c.Options.RelayPorts { - go func(portStr string) { - debugString := "warn" - if c.Options.Debug { - debugString = "debug" - } - err := tcp.Run(debugString, portStr, strings.Join(c.Options.RelayPorts[1:], ",")) - if err != nil { - panic(err) - } - }(port) - } -} - -func (c *Client) broadcastOnLocalNetwork() { - // look for peers first - discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{ - Limit: -1, - Payload: []byte("croc" + c.Options.RelayPorts[0]), - Delay: 10 * time.Millisecond, - TimeLimit: 30 * time.Second, - }) - log.Debugf("discoveries: %+v", discoveries) - - if err == nil && len(discoveries) > 0 { - log.Debug("using local server") - } -} - -func (c *Client) transferOverLocalRelay(options TransferOptions, errchan chan<- error) { - time.Sleep(500 * time.Millisecond) - log.Debug("establishing connection") - var banner string - conn, banner, ipaddr, err := tcp.ConnectToTCPServer("localhost:"+c.Options.RelayPorts[0], c.Options.SharedSecret) - log.Debugf("banner: %s", banner) - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("could not connect to localhost:%s", c.Options.RelayPorts[0])) - log.Debug(err) - // not really an error because it will try to connect over the actual relay - return - } - log.Debugf("connection established: %+v", conn) - for { - data, _ := conn.Receive() - if bytes.Equal(data, []byte("handshake")) { - break - } - } - c.conn[0] = conn - log.Debug("exchanged header message") - c.Options.RelayAddress = "localhost" - c.Options.RelayPorts = strings.Split(banner, ",") - c.ExternalIP = ipaddr - errchan <- c.transfer(options) -} - -// Send will send the specified file -func (c *Client) Send(options TransferOptions) (err error) { - err = c.sendCollectFiles(options) - if err != nil { - return - } - - otherRelay := "" - if c.Options.RelayAddress != models.DEFAULT_RELAY { - otherRelay = "--relay " + c.Options.RelayAddress + " " - } - fmt.Fprintf(os.Stderr, "Code is: %s\nOn the other computer run\n\ncroc %s%s\n", c.Options.SharedSecret, otherRelay, c.Options.SharedSecret) - if c.Options.Ask { - machid, _ := machineid.ID() - fmt.Fprintf(os.Stderr, "\rYour machine ID is '%s'\n", machid) - } - // // c.spinner.Suffix = " waiting for recipient..." - // c.spinner.Start() - // create channel for quitting - // connect to the relay for messaging - errchan := make(chan error, 1) - - if !c.Options.DisableLocal { - // add two things to the error channel - errchan = make(chan error, 2) - c.setupLocalRelay() - go c.broadcastOnLocalNetwork() - go c.transferOverLocalRelay(options, errchan) - } - - go func() { - log.Debugf("establishing connection to %s", c.Options.RelayAddress) - var banner string - conn, banner, ipaddr, err := tcp.ConnectToTCPServer(c.Options.RelayAddress, c.Options.SharedSecret, 5*time.Second) - log.Debugf("banner: %s", banner) - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("could not connect to %s", c.Options.RelayAddress)) - log.Debug(err) - errchan <- err - return - } - log.Debugf("connection established: %+v", conn) - for { - data, _ := conn.Receive() - if bytes.Equal(data, []byte("ips?")) { - // recipient wants to try to connect to local ips - var ips []string - // only get local ips if the local is enabled - if !c.Options.DisableLocal { - // get list of local ips - ips, err = utils.GetLocalIPs() - if err != nil { - log.Debugf("error getting local ips: %s", err.Error()) - } - // prepend the port that is being listened to - ips = append([]string{c.Options.RelayPorts[0]}, ips...) - } - bips, _ := json.Marshal(ips) - conn.Send(bips) - } - if bytes.Equal(data, []byte("handshake")) { - break - } - } - - c.conn[0] = conn - c.Options.RelayPorts = strings.Split(banner, ",") - c.ExternalIP = ipaddr - log.Debug("exchanged header message") - errchan <- c.transfer(options) - }() - - err = <-errchan - if err == nil { - // return if no error - return - } - if !c.Options.DisableLocal { - err = <-errchan - } - return err -} - -// Receive will receive a file -func (c *Client) Receive() (err error) { - fmt.Fprintf(os.Stderr, "connecting...") - // recipient will look for peers first - // and continue if it doesn't find any within 100 ms - usingLocal := false - if !c.Options.DisableLocal { - log.Debug("attempt to discover peers") - discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{ - Limit: 1, - Payload: []byte("ok"), - Delay: 10 * time.Millisecond, - TimeLimit: 100 * time.Millisecond, - }) - if err == nil && len(discoveries) > 0 { - for i := 0; i < len(discoveries); i++ { - log.Debugf("discovery %d has payload: %+v", i, discoveries[i]) - if !bytes.HasPrefix(discoveries[i].Payload, []byte("croc")) { - log.Debug("skipping discovery") - continue - } - log.Debug("switching to local") - c.Options.RelayAddress = fmt.Sprintf("%s:%s", - discoveries[0].Address, - bytes.TrimPrefix(discoveries[0].Payload, []byte("croc")), - ) - c.ExternalIPConnected = c.Options.RelayAddress - usingLocal = true - } - } - log.Debugf("discoveries: %+v", discoveries) - log.Debug("establishing connection") - } - var banner string - c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(c.Options.RelayAddress, c.Options.SharedSecret) - log.Debugf("banner: %s", banner) - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("could not connect to %s", c.Options.RelayAddress)) - return - } - log.Debugf("connection established: %+v", c.conn[0]) - - if !usingLocal && !c.Options.DisableLocal { - // ask the sender for their local ips and port - // and try to connect to them - var data []byte - c.conn[0].Send([]byte("ips?")) - data, err = c.conn[0].Receive() - if err != nil { - return - } - log.Debugf("ips data: %s", data) - var ips []string - json.Unmarshal(data, &ips) - if len(ips) > 1 { - port := ips[0] - ips = ips[1:] - for _, ip := range ips { - serverTry := fmt.Sprintf("%s:%s", ip, port) - conn, banner2, externalIP, errConn := tcp.ConnectToTCPServer(serverTry, c.Options.SharedSecret, 50*time.Millisecond) - if errConn != nil { - log.Debugf("could not connect to " + serverTry) - continue - } - log.Debugf("local connection established to %s", serverTry) - log.Debugf("banner: %s", banner2) - // reset to the local port - banner = banner2 - c.Options.RelayAddress = serverTry - c.ExternalIP = externalIP - c.conn[0].Close() - c.conn[0] = nil - c.conn[0] = conn - break - } - } - } - - c.conn[0].Send([]byte("handshake")) - c.Options.RelayPorts = strings.Split(banner, ",") - log.Debug("exchanged header message") - fmt.Fprintf(os.Stderr, "\rsecuring channel...") - return c.transfer(TransferOptions{}) -} - -func (c *Client) transfer(options TransferOptions) (err error) { - // connect to the server - - // quit with c.quit <- true - c.quit = make(chan bool) - - // if recipient, initialize with sending pake information - log.Debug("ready") - if !c.Options.IsSender && !c.Step1ChannelSecured { - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "pake", - Bytes: c.Pake.Bytes(), - }) - if err != nil { - return - } - } - - // listen for incoming messages and process them - for { - var data []byte - var done bool - data, err = c.conn[0].Receive() - if err != nil { - log.Debugf("got error receiving: %s", err.Error()) - break - } - done, err = c.processMessage(data) - if err != nil { - log.Debugf("got error processing: %s", err.Error()) - break - } - if done { - break - } - } - // purge errors that come from successful transfer - if c.SuccessfulTransfer { - if err != nil { - log.Debugf("purging error: %s", err) - } - err = nil - } - - if c.Options.Stdout && !c.Options.IsSender { - pathToFile := path.Join( - c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote, - c.FilesToTransfer[c.FilesToTransferCurrentNum].Name, - ) - os.Remove(pathToFile) - } - return -} - -func (c *Client) processMessageFileInfo(m message.Message) (done bool, err error) { - var senderInfo SenderInfo - err = json.Unmarshal(m.Bytes, &senderInfo) - if err != nil { - log.Error(err) - return - } - c.FilesToTransfer = senderInfo.FilesToTransfer - fname := fmt.Sprintf("%d files", len(c.FilesToTransfer)) - if len(c.FilesToTransfer) == 1 { - fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name) - } - totalSize := int64(0) - for _, fi := range c.FilesToTransfer { - totalSize += fi.Size - if len(fi.Name) > c.longestFilename { - c.longestFilename = len(fi.Name) - } - } - // c.spinner.Stop() - if !c.Options.NoPrompt || c.Options.Ask || senderInfo.Ask { - if c.Options.Ask || senderInfo.Ask { - machID, _ := machineid.ID() - fmt.Fprintf(os.Stderr, "\rYour machine id is '%s'.\nAccept %s (%s) from '%s'? (y/n) ", machID, fname, utils.ByteCountDecimal(totalSize), senderInfo.MachineID) - } else { - fmt.Fprintf(os.Stderr, "\rAccept %s (%s)? (y/n) ", fname, utils.ByteCountDecimal(totalSize)) - } - if strings.ToLower(strings.TrimSpace(utils.GetInput(""))) != "y" { - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "error", - Message: "refusing files", - }) - return true, fmt.Errorf("refused files") - } - } else { - fmt.Fprintf(os.Stderr, "\rReceiving %s (%s) \n", fname, utils.ByteCountDecimal(totalSize)) - } - fmt.Fprintf(os.Stderr, "\nReceiving (<-%s)\n", c.ExternalIPConnected) - - log.Debug(c.FilesToTransfer) - c.Step2FileInfoTransfered = true - return -} - -func (c *Client) procesMesssagePake(m message.Message) (err error) { - log.Debug("received pake payload") - // if // c.spinner.Suffix != " performing PAKE..." { - // // c.spinner.Stop() - // // c.spinner.Suffix = " performing PAKE..." - // // c.spinner.Start() - // } - notVerified := !c.Pake.IsVerified() - err = c.Pake.Update(m.Bytes) - if err != nil { - return - } - if (notVerified && c.Pake.IsVerified() && !c.Options.IsSender) || !c.Pake.IsVerified() { - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "pake", - Bytes: c.Pake.Bytes(), - }) - } - if c.Pake.IsVerified() { - if c.Options.IsSender { - log.Debug("generating salt") - salt := make([]byte, 8) - rand.Read(salt) - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "salt", - Bytes: salt, - Message: c.ExternalIP, - }) - if err != nil { - return - } - } - - // connects to the other ports of the server for transfer - var wg sync.WaitGroup - wg.Add(len(c.Options.RelayPorts)) - for i := 0; i < len(c.Options.RelayPorts); i++ { - log.Debugf("port: [%s]", c.Options.RelayPorts[i]) - go func(j int) { - defer wg.Done() - server := fmt.Sprintf("%s:%s", strings.Split(c.Options.RelayAddress, ":")[0], c.Options.RelayPorts[j]) - log.Debugf("connecting to %s", server) - c.conn[j+1], _, _, err = tcp.ConnectToTCPServer( - server, - fmt.Sprintf("%s-%d", utils.SHA256(c.Options.SharedSecret)[:7], j), - ) - if err != nil { - panic(err) - } - log.Debugf("connected to %s", server) - if !c.Options.IsSender { - go c.receiveData(j) - } - }(i) - } - wg.Wait() - } - return -} - -func (c *Client) processMessageSalt(m message.Message) (done bool, err error) { - log.Debug("received salt") - if !c.Options.IsSender { - log.Debug("sending salt back") - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "salt", - Bytes: m.Bytes, - Message: c.ExternalIP, - }) - } - log.Debugf("session key is verified, generating encryption with salt: %x", m.Bytes) - key, err := c.Pake.SessionKey() - if err != nil { - return true, err - } - c.Key, err = crypt.New(key, m.Bytes) - if err != nil { - return true, err - } - if c.ExternalIPConnected == "" { - // it can be preset by the local relay - c.ExternalIPConnected = m.Message - } - log.Debugf("connected as %s -> %s", c.ExternalIP, c.ExternalIPConnected) - c.Step1ChannelSecured = true - return -} - -func (c *Client) processMessage(payload []byte) (done bool, err error) { - m, err := message.Decode(c.Key, payload) - if err != nil { - return - } - - switch m.Type { - case "finished": - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "finished", - }) - done = true - c.SuccessfulTransfer = true - return - case "pake": - err = c.procesMesssagePake(m) - case "salt": - done, err = c.processMessageSalt(m) - case "error": - // c.spinner.Stop() - fmt.Print("\r") - err = fmt.Errorf("peer error: %s", m.Message) - return true, err - case "fileinfo": - done, err = c.processMessageFileInfo(m) - case "recipientready": - var remoteFile RemoteFileRequest - err = json.Unmarshal(m.Bytes, &remoteFile) - if err != nil { - return - } - c.FilesToTransferCurrentNum = remoteFile.FilesToTransferCurrentNum - c.CurrentFileChunkRanges = remoteFile.CurrentFileChunkRanges - c.CurrentFileChunks = utils.ChunkRangesToChunks(c.CurrentFileChunkRanges) - log.Debugf("current file chunks: %+v", c.CurrentFileChunks) - c.chunkMap = make(map[uint64]struct{}) - for _, chunk := range c.CurrentFileChunks { - c.chunkMap[uint64(chunk)] = struct{}{} - } - c.Step3RecipientRequestFile = true - - if c.Options.Ask { - fmt.Fprintf(os.Stderr, "Send to machine '%s'? (y/n) ", remoteFile.MachineID) - if strings.ToLower(strings.TrimSpace(utils.GetInput(""))) != "y" { - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "error", - Message: "refusing files", - }) - done = true - err = fmt.Errorf("refused files") - return - } - } - case "close-sender": - c.bar.Finish() - log.Debug("close-sender received...") - c.Step4FileTransfer = false - c.Step3RecipientRequestFile = false - log.Debug("sending close-recipient") - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "close-recipient", - }) - case "close-recipient": - c.Step4FileTransfer = false - c.Step3RecipientRequestFile = false - } - if err != nil { - log.Debugf("got error from processing message: %s", err.Error()) - return - } - err = c.updateState() - if err != nil { - log.Debugf("got error from updating state: %s", err.Error()) - return - } - return -} - -func (c *Client) updateIfSenderChannelSecured() (err error) { - if c.Options.IsSender && c.Step1ChannelSecured && !c.Step2FileInfoTransfered { - var b []byte - machID, _ := machineid.ID() - b, err = json.Marshal(SenderInfo{ - FilesToTransfer: c.FilesToTransfer, - MachineID: machID, - Ask: c.Options.Ask, - }) - if err != nil { - log.Error(err) - return - } - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "fileinfo", - Bytes: b, - }) - if err != nil { - return - } - - c.Step2FileInfoTransfered = true - } - return -} - -func (c *Client) recipientInitializeFile() (err error) { - // start initiating the process to receive a new file - log.Debugf("working on file %d", c.FilesToTransferCurrentNum) - - // recipient sets the file - pathToFile := path.Join( - c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote, - c.FilesToTransfer[c.FilesToTransferCurrentNum].Name, - ) - folderForFile, _ := filepath.Split(pathToFile) - os.MkdirAll(folderForFile, os.ModePerm) - var errOpen error - c.CurrentFile, errOpen = os.OpenFile( - pathToFile, - os.O_WRONLY, 0666) - var truncate bool // default false - c.CurrentFileChunks = []int64{} - c.CurrentFileChunkRanges = []int64{} - if errOpen == nil { - stat, _ := c.CurrentFile.Stat() - truncate = stat.Size() != c.FilesToTransfer[c.FilesToTransferCurrentNum].Size - if truncate == false { - // recipient requests the file and chunks (if empty, then should receive all chunks) - // TODO: determine the missing chunks - c.CurrentFileChunkRanges = utils.MissingChunks( - pathToFile, - c.FilesToTransfer[c.FilesToTransferCurrentNum].Size, - models.TCP_BUFFER_SIZE/2, - ) - } - } else { - c.CurrentFile, errOpen = os.Create(pathToFile) - if errOpen != nil { - errOpen = errors.Wrap(errOpen, "could not create "+pathToFile) - log.Error(errOpen) - return errOpen - } - truncate = true - } - if truncate { - err := c.CurrentFile.Truncate(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size) - if err != nil { - err = errors.Wrap(err, "could not truncate "+pathToFile) - log.Error(err) - return err - } - } - return -} - -func (c *Client) recipientGetFileReady(finished bool) (err error) { - if finished { - // TODO: do the last finishing stuff - log.Debug("finished") - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "finished", - }) - if err != nil { - panic(err) - } - c.SuccessfulTransfer = true - c.FilesHasFinished[c.FilesToTransferCurrentNum] = struct{}{} - } - - err = c.recipientInitializeFile() - if err != nil { - return - } - - c.TotalSent = 0 - machID, _ := machineid.ID() - bRequest, _ := json.Marshal(RemoteFileRequest{ - CurrentFileChunkRanges: c.CurrentFileChunkRanges, - FilesToTransferCurrentNum: c.FilesToTransferCurrentNum, - MachineID: machID, - }) - log.Debug("converting to chunk range") - c.CurrentFileChunks = utils.ChunkRangesToChunks(c.CurrentFileChunkRanges) - - if !finished { - // setup the progressbar - c.setBar() - } - - log.Debugf("sending recipient ready with %d chunks", len(c.CurrentFileChunks)) - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "recipientready", - Bytes: bRequest, - }) - if err != nil { - return - } - c.Step3RecipientRequestFile = true - return -} - -func (c *Client) createEmptyFileAndFinish(fileInfo FileInfo, i int) (err error) { - log.Debugf("touching file with folder / name") - if !utils.Exists(fileInfo.FolderRemote) { - err = os.MkdirAll(fileInfo.FolderRemote, os.ModePerm) - if err != nil { - log.Error(err) - return - } - } - emptyFile, errCreate := os.Create(path.Join(fileInfo.FolderRemote, fileInfo.Name)) - if errCreate != nil { - log.Error(errCreate) - err = errCreate - return - } - emptyFile.Close() - // setup the progressbar - description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[i].Name) - if len(c.FilesToTransfer) == 1 { - description = c.FilesToTransfer[i].Name - } - c.bar = progressbar.NewOptions64(1, - progressbar.OptionOnCompletion(func() { - fmt.Fprintf(os.Stderr, " ✔️\n") - }), - progressbar.OptionSetWidth(20), - progressbar.OptionSetDescription(description), - progressbar.OptionSetRenderBlankState(true), - progressbar.OptionSetBytes64(1), - progressbar.OptionSetWriter(os.Stderr), - ) - c.bar.Finish() - return -} - -func (c *Client) updateIfRecipientHasFileInfo() (err error) { - if !(!c.Options.IsSender && c.Step2FileInfoTransfered && !c.Step3RecipientRequestFile) { - return - } - // find the next file to transfer and send that number - // if the files are the same size, then look for missing chunks - finished := true - - for i, fileInfo := range c.FilesToTransfer { - if _, ok := c.FilesHasFinished[i]; ok { - continue - } - log.Debugf("checking %+v", fileInfo) - if i < c.FilesToTransferCurrentNum { - continue - } - fileHash, errHash := utils.HashFile(path.Join(fileInfo.FolderRemote, fileInfo.Name)) - if fileInfo.Size == 0 { - err = c.createEmptyFileAndFinish(fileInfo, i) - if err != nil { - return - } - continue - } - log.Debugf("%s %+x %+x %+v", fileInfo.Name, fileHash, fileInfo.Hash, errHash) - if !bytes.Equal(fileHash, fileInfo.Hash) { - log.Debugf("hashes are not equal %x != %x", fileHash, fileInfo.Hash) - } else { - log.Debugf("hashes are equal %x == %x", fileHash, fileInfo.Hash) - } - if errHash != nil { - // probably can't find, its okay - log.Debug(errHash) - } - if errHash != nil || !bytes.Equal(fileHash, fileInfo.Hash) { - finished = false - c.FilesToTransferCurrentNum = i - break - } - // TODO: print out something about this file already existing - } - err = c.recipientGetFileReady(finished) - return -} - -func (c *Client) updateState() (err error) { - err = c.updateIfSenderChannelSecured() - if err != nil { - return - } - - err = c.updateIfRecipientHasFileInfo() - if err != nil { - return - } - - if c.Options.IsSender && c.Step3RecipientRequestFile && !c.Step4FileTransfer { - log.Debug("start sending data!") - - if !c.firstSend { - fmt.Fprintf(os.Stderr, "\nSending (->%s)\n", c.ExternalIPConnected) - c.firstSend = true - // if there are empty files, show them as already have been transferred now - for i := range c.FilesToTransfer { - if c.FilesToTransfer[i].Size == 0 { - // setup the progressbar and takedown the progress bar for empty files - description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[i].Name) - if len(c.FilesToTransfer) == 1 { - description = c.FilesToTransfer[i].Name - } - c.bar = progressbar.NewOptions64(1, - progressbar.OptionOnCompletion(func() { - fmt.Fprintf(os.Stderr, " ✔️\n") - }), - progressbar.OptionSetWidth(20), - progressbar.OptionSetDescription(description), - progressbar.OptionSetRenderBlankState(true), - progressbar.OptionSetBytes64(1), - progressbar.OptionSetWriter(os.Stderr), - ) - c.bar.Finish() - } - } - } - c.Step4FileTransfer = true - // setup the progressbar - c.setBar() - c.TotalSent = 0 - log.Debug("beginning sending comms") - pathToFile := path.Join( - c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderSource, - c.FilesToTransfer[c.FilesToTransferCurrentNum].Name, - ) - - c.fread, err = os.Open(pathToFile) - c.numfinished = 0 - if err != nil { - return - } - for i := 0; i < len(c.Options.RelayPorts); i++ { - log.Debugf("starting sending over comm %d", i) - go c.sendData(i) - } - } - return -} - -func (c *Client) setBar() { - description := fmt.Sprintf("%-*s", c.longestFilename, c.FilesToTransfer[c.FilesToTransferCurrentNum].Name) - if len(c.FilesToTransfer) == 1 { - description = c.FilesToTransfer[c.FilesToTransferCurrentNum].Name - } - c.bar = progressbar.NewOptions64( - c.FilesToTransfer[c.FilesToTransferCurrentNum].Size, - progressbar.OptionOnCompletion(func() { - fmt.Fprintf(os.Stderr, " ✔️\n") - }), - progressbar.OptionSetWidth(20), - progressbar.OptionSetDescription(description), - progressbar.OptionSetRenderBlankState(true), - progressbar.OptionSetBytes64(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size), - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionThrottle(100*time.Millisecond), - ) - byteToDo := int64(len(c.CurrentFileChunks) * models.TCP_BUFFER_SIZE / 2) - if byteToDo > 0 { - bytesDone := c.FilesToTransfer[c.FilesToTransferCurrentNum].Size - byteToDo - log.Debug(byteToDo) - log.Debug(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size) - log.Debug(bytesDone) - if bytesDone > 0 { - c.bar.Add64(bytesDone) - } - } -} - -func (c *Client) receiveData(i int) { - log.Debugf("%d receiving data", i) - for { - data, err := c.conn[i+1].Receive() - if err != nil { - break - } - - data, err = c.Key.Decrypt(data) - if err != nil { - panic(err) - } - data = compress.Decompress(data) - - // get position - var position uint64 - rbuf := bytes.NewReader(data[:8]) - err = binary.Read(rbuf, binary.LittleEndian, &position) - if err != nil { - panic(err) - } - positionInt64 := int64(position) - - c.mutex.Lock() - _, err = c.CurrentFile.WriteAt(data[8:], positionInt64) - c.mutex.Unlock() - if err != nil { - panic(err) - } - c.bar.Add(len(data[8:])) - c.TotalSent += int64(len(data[8:])) - c.TotalChunksTransfered++ - if c.TotalChunksTransfered == len(c.CurrentFileChunks) || c.TotalSent == c.FilesToTransfer[c.FilesToTransferCurrentNum].Size { - log.Debug("finished receiving!") - c.CurrentFile.Close() - if c.Options.Stdout || strings.HasPrefix(c.FilesToTransfer[c.FilesToTransferCurrentNum].Name, "croc-stdin") { - pathToFile := path.Join( - c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote, - c.FilesToTransfer[c.FilesToTransferCurrentNum].Name, - ) - b, _ := ioutil.ReadFile(pathToFile) - fmt.Print(string(b)) - } - log.Debug("sending close-sender") - err = message.Send(c.conn[0], c.Key, message.Message{ - Type: "close-sender", - }) - if err != nil { - panic(err) - } - } - } - - return -} - -func (c *Client) sendData(i int) { - defer func() { - log.Debugf("finished with %d", i) - c.numfinished++ - if c.numfinished == len(c.Options.RelayPorts) { - log.Debug("closing file") - c.fread.Close() - } - }() - - var readingPos int64 - pos := uint64(0) - curi := float64(0) - for { - // Read file - data := make([]byte, models.TCP_BUFFER_SIZE/2) - // log.Debugf("%d trying to read", i) - n, errRead := c.fread.ReadAt(data, readingPos) - // log.Debugf("%d read %d bytes", i, n) - readingPos += int64(n) - - if math.Mod(curi, float64(len(c.Options.RelayPorts))) == float64(i) { - // check to see if this is a chunk that the recipient wants - usableChunk := true - c.mutex.Lock() - if len(c.chunkMap) != 0 { - if _, ok := c.chunkMap[pos]; !ok { - usableChunk = false - } else { - delete(c.chunkMap, pos) - } - } - c.mutex.Unlock() - if usableChunk { - // log.Debugf("sending chunk %d", pos) - posByte := make([]byte, 8) - binary.LittleEndian.PutUint64(posByte, pos) - - dataToSend, err := c.Key.Encrypt( - compress.Compress( - append(posByte, data[:n]...), - ), - ) - if err != nil { - panic(err) - } - - err = c.conn[i+1].Send(dataToSend) - if err != nil { - panic(err) - } - c.bar.Add(n) - c.TotalSent += int64(n) - // time.Sleep(100 * time.Millisecond) - } else { - // log.Debugf("skipping chunk %d", pos) - } - } - - curi++ - pos += uint64(n) - - if errRead != nil { - if errRead == io.EOF { - break - } - panic(errRead) - } - } - return -} diff --git a/src/croc/croc_test.go b/src/croc/croc_test.go deleted file mode 100644 index 492a05f..0000000 --- a/src/croc/croc_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package croc - -import ( - "io/ioutil" - "os" - "sync" - "testing" - "time" - - "github.com/schollz/croc/v6/src/tcp" - log "github.com/schollz/logger" - "github.com/stretchr/testify/assert" -) - -func TestCroc(t *testing.T) { - log.SetLevel("trace") - defer os.Remove("README.md") - go tcp.Run("debug", "8081", "8082,8083,8084,8085") - go tcp.Run("debug", "8082") - go tcp.Run("debug", "8083") - go tcp.Run("debug", "8084") - go tcp.Run("debug", "8085") - time.Sleep(300 * time.Millisecond) - - log.Debug("setting up sender") - sender, err := New(Options{ - IsSender: true, - SharedSecret: "test", - Debug: true, - RelayAddress: "localhost:8081", - RelayPorts: []string{"8081"}, - Stdout: false, - NoPrompt: true, - DisableLocal: true, - }) - if err != nil { - panic(err) - } - - log.Debug("setting up receiver") - receiver, err := New(Options{ - IsSender: false, - SharedSecret: "test", - Debug: true, - RelayAddress: "localhost:8081", - Stdout: false, - NoPrompt: true, - DisableLocal: true, - }) - if err != nil { - panic(err) - } - - var wg sync.WaitGroup - wg.Add(2) - go func() { - sender.Send(TransferOptions{ - PathToFiles: []string{"../../README.md"}, - }) - wg.Done() - }() - time.Sleep(100 * time.Millisecond) - go func() { - receiver.Receive() - wg.Done() - }() - - wg.Wait() -} - -func TestCrocLocal(t *testing.T) { - log.SetLevel("trace") - defer os.Remove("LICENSE") - defer os.Remove("touched") - time.Sleep(300 * time.Millisecond) - - log.Debug("setting up sender") - sender, err := New(Options{ - IsSender: true, - SharedSecret: "test", - Debug: true, - RelayAddress: "localhost:8181", - RelayPorts: []string{"8181", "8182"}, - Stdout: true, - NoPrompt: true, - DisableLocal: false, - }) - if err != nil { - panic(err) - } - time.Sleep(1 * time.Second) - - log.Debug("setting up receiver") - receiver, err := New(Options{ - IsSender: false, - SharedSecret: "test", - Debug: true, - RelayAddress: "localhost:8181", - Stdout: true, - NoPrompt: true, - DisableLocal: false, - }) - if err != nil { - panic(err) - } - - var wg sync.WaitGroup - os.Create("touched") - wg.Add(2) - go func() { - sender.Send(TransferOptions{ - PathToFiles: []string{"../../LICENSE", "touched"}, - KeepPathInRemote: false, - }) - wg.Done() - }() - time.Sleep(100 * time.Millisecond) - go func() { - receiver.Receive() - wg.Done() - }() - - wg.Wait() -} - -func TestCrocError(t *testing.T) { - content := []byte("temporary file's content") - tmpfile, err := ioutil.TempFile("", "example") - if err != nil { - panic(err) - } - - defer os.Remove(tmpfile.Name()) // clean up - - if _, err := tmpfile.Write(content); err != nil { - panic(err) - } - if err := tmpfile.Close(); err != nil { - panic(err) - } - - Debug(false) - log.SetLevel("warn") - sender, _ := New(Options{ - IsSender: true, - SharedSecret: "test33", - Debug: true, - RelayAddress: "doesntexistok.com:8381", - RelayPorts: []string{"8381", "8382"}, - Stdout: true, - NoPrompt: true, - DisableLocal: true, - }) - err = sender.Send(TransferOptions{ - PathToFiles: []string{tmpfile.Name()}, - KeepPathInRemote: true, - }) - log.Debug(err) - assert.NotNil(t, err) - -} diff --git a/src/crypt/crypt.go b/src/crypt/crypt.go deleted file mode 100644 index 65ba77c..0000000 --- a/src/crypt/crypt.go +++ /dev/null @@ -1,86 +0,0 @@ -package crypt - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - - "golang.org/x/crypto/pbkdf2" -) - -// Encryption is the basic type for storing -// the key, passphrase and salt -type Encryption struct { - key []byte - passphrase []byte - salt []byte -} - -// New generates a new Encryption, using the supplied passphrase and -// an optional supplied salt. -// Passing nil passphrase will not use decryption. -func New(passphrase []byte, salt []byte) (e Encryption, err error) { - if passphrase == nil { - e = Encryption{nil, nil, nil} - return - } - e.passphrase = passphrase - if salt == nil { - e.salt = make([]byte, 8) - // http://www.ietf.org/rfc/rfc2898.txt - // Salt. - rand.Read(e.salt) - } else { - e.salt = salt - } - e.key = pbkdf2.Key([]byte(passphrase), e.salt, 100, 32, sha256.New) - return -} - -// Salt returns the salt bytes -func (e Encryption) Salt() []byte { - return e.salt -} - -// Encrypt will generate an Encryption, prefixed with the IV -func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) { - if e.passphrase == nil { - encrypted = plaintext - return - } - // generate a random iv each time - // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - // Section 8.2 - ivBytes := make([]byte, 12) - rand.Read(ivBytes) - b, err := aes.NewCipher(e.key) - if err != nil { - return - } - aesgcm, err := cipher.NewGCM(b) - if err != nil { - return - } - encrypted = aesgcm.Seal(nil, ivBytes, plaintext, nil) - encrypted = append(ivBytes, encrypted...) - return -} - -// Decrypt an Encryption -func (e Encryption) Decrypt(encrypted []byte) (plaintext []byte, err error) { - if e.passphrase == nil { - plaintext = encrypted - return - } - b, err := aes.NewCipher(e.key) - if err != nil { - return - } - aesgcm, err := cipher.NewGCM(b) - if err != nil { - return - } - plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil) - return -} diff --git a/src/crypt/crypt_test.go b/src/crypt/crypt_test.go deleted file mode 100644 index 14ac639..0000000 --- a/src/crypt/crypt_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package crypt - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func BenchmarkEncryptionNew(b *testing.B) { - for i := 0; i < b.N; i++ { - bob, _ := New([]byte("password"), nil) - bob.Encrypt([]byte("hello, world")) - } -} - -func BenchmarkEncryption(b *testing.B) { - bob, _ := New([]byte("password"), nil) - for i := 0; i < b.N; i++ { - bob.Encrypt([]byte("hello, world")) - } -} - -func TestEncryption(t *testing.T) { - bob, err := New([]byte("password"), nil) - assert.Nil(t, err) - jane, err := New([]byte("password"), bob.Salt()) - assert.Nil(t, err) - enc, err := bob.Encrypt([]byte("hello, world")) - assert.Nil(t, err) - dec, err := jane.Decrypt(enc) - assert.Nil(t, err) - assert.Equal(t, dec, []byte("hello, world")) - - jane2, err := New([]byte("password"), nil) - assert.Nil(t, err) - dec, err = jane2.Decrypt(enc) - assert.NotNil(t, err) - assert.NotEqual(t, dec, []byte("hello, world")) - - jane3, err := New([]byte("passwordwrong"), bob.Salt()) - assert.Nil(t, err) - dec, err = jane3.Decrypt(enc) - assert.NotNil(t, err) - assert.NotEqual(t, dec, []byte("hello, world")) - -} - -func TestNoEncryption(t *testing.T) { - bob, err := New(nil, nil) - assert.Nil(t, err) - jane, err := New(nil, nil) - assert.Nil(t, err) - enc, err := bob.Encrypt([]byte("hello, world")) - assert.Nil(t, err) - dec, err := jane.Decrypt(enc) - assert.Nil(t, err) - assert.Equal(t, dec, []byte("hello, world")) - assert.Equal(t, enc, []byte("hello, world")) - -} diff --git a/src/install/default.txt b/src/install/default.txt deleted file mode 100644 index cd3a604..0000000 --- a/src/install/default.txt +++ /dev/null @@ -1,673 +0,0 @@ -#!/bin/bash - -#=============================================================================== -# -# FILE: default.txt -# -# USAGE: curl https://getcroc.schollz.com | bash -# OR -# wget -qO- https://getcroc.schollz.com | bash -# -# DESCRIPTION: croc Installer Script. -# -# This script installs croc into a specified prefix. -# Default prefix = /usr/local/bin -# -# OPTIONS: -p, --prefix "${INSTALL_PREFIX}" -# Prefix to install croc into. Defaults to /usr/local/bin -# REQUIREMENTS: bash, uname, tar/unzip, curl/wget, sudo (if not run -# as root), install, mktemp, sha256sum/shasum/sha256 -# -# BUGS: ...hopefully not. Please report. -# -# NOTES: Homepage: https://schollz.com/software/croc -# Issues: https://github.com/schollz/croc/issues -# -# CREATED: 08/10/2019 16:41 -# REVISION: 0.9.1 -#=============================================================================== -set -o nounset # Treat unset variables as an error - -#------------------------------------------------------------------------------- -# DEFAULTS -#------------------------------------------------------------------------------- -PREFIX="${PREFIX:-}" -ANDROID_ROOT="${ANDROID_ROOT:-}" - -# Termux on Android has ${PREFIX} set which already ends with '/usr' -if [[ -n "${ANDROID_ROOT}" && -n "${PREFIX}" ]]; then - INSTALL_PREFIX="${PREFIX}/bin" -else - INSTALL_PREFIX="/usr/local/bin" -fi - -#------------------------------------------------------------------------------- -# FUNCTIONS -#------------------------------------------------------------------------------- - - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: print_banner -# DESCRIPTION: Prints a banner -# PARAMETERS: none -# RETURNS: 0 -#------------------------------------------------------------------------------- -print_banner() { - cat <<-'EOF' -================================================= - ____ - / ___|_ __ ___ ___ - | | | '__/ _ \ / __| - | |___| | | (_) | (__ - \____|_| \___/ \___| - - ___ _ _ _ - |_ _|_ __ ___| |_ __ _| | | ___ _ __ - | || '_ \/ __| __/ _` | | |/ _ \ '__| - | || | | \__ \ || (_| | | | __/ | - |___|_| |_|___/\__\__,_|_|_|\___|_| -================================================== -EOF -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: print_help -# DESCRIPTION: Prints out a help message -# PARAMETERS: none -# RETURNS: 0 -#------------------------------------------------------------------------------- -print_help() { - local help_header - local help_message - - help_header="croc Installer Script" - help_message="Usage: - -p INSTALL_PREFIX - Prefix to install croc into. Directory must already exist. - Default = /usr/local/bin ('\${PREFIX}/bin' on Termux for Android) - - -h - Prints this helpfull message and exit." - - echo "${help_header}" - echo "" - echo "${help_message}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: print_message -# DESCRIPTION: Prints a message all fancy like -# PARAMETERS: $1 = Message to print -# $2 = Severity. info, ok, error, warn -# RETURNS: Formatted Message to stdout -#------------------------------------------------------------------------------- -print_message() { - local message - local severity - local red - local green - local yellow - local nc - - message="${1}" - severity="${2}" - red='\e[0;31m' - green='\e[0;32m' - yellow='\e[1;33m' - nc='\e[0m' - - case "${severity}" in - "info" ) echo -e "${nc}${message}${nc}";; - "ok" ) echo -e "${green}${message}${nc}";; - "error" ) echo -e "${red}${message}${nc}";; - "warn" ) echo -e "${yellow}${message}${nc}";; - esac - - -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: make_tempdir -# DESCRIPTION: Makes a temp dir using mktemp if available -# PARAMETERS: $1 = Directory template -# RETURNS: 0 = Created temp dir. Also prints temp file path to stdout -# 1 = Failed to create temp dir -# 20 = Failed to find mktemp -#------------------------------------------------------------------------------- -make_tempdir() { - local template - local tempdir - local tempdir_rcode - - template="${1}.XXXXXX" - - if command -v mktemp >/dev/null 2>&1; then - tempdir="$(mktemp -d -t "${template}")" - tempdir_rcode="${?}" - if [[ "${tempdir_rcode}" == "0" ]]; then - echo "${tempdir}" - return 0 - else - return 1 - fi - else - return 20 - fi -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: determine_os -# DESCRIPTION: Attempts to determin host os using uname -# PARAMETERS: none -# RETURNS: 0 = OS Detected. Also prints detected os to stdout -# 1 = Unkown OS -# 20 = 'uname' not found in path -#------------------------------------------------------------------------------- -determine_os() { - local uname_out - - if command -v uname >/dev/null 2>&1; then - uname_out="$(uname)" - if [[ "${uname_out}" == "" ]]; then - return 1 - else - echo "${uname_out}" - return 0 - fi - else - return 20 - fi -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: determine_arch -# DESCRIPTION: Attempt to determin architecture of host -# PARAMETERS: none -# RETURNS: 0 = Arch Detected. Also prints detected arch to stdout -# 1 = Unkown arch -# 20 = 'uname' not found in path -#------------------------------------------------------------------------------- -determine_arch() { - local uname_out - - if command -v uname >/dev/null 2>&1; then - uname_out="$(uname -m)" - if [[ "${uname_out}" == "" ]]; then - return 1 - else - echo "${uname_out}" - return 0 - fi - else - return 20 - fi -} - - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: download_file -# DESCRIPTION: Downloads a file into the specified directory. Attempts to -# use curl, then wget. If neither is found, fail. -# PARAMETERS: $1 = url of file to download -# $2 = location to download file into on host system -# RETURNS: If curl or wget found, returns the return code of curl or wget -# 20 = Could not find curl and wget -#------------------------------------------------------------------------------- -download_file() { - local url - local dir - local filename - local rcode - - url="${1}" - dir="${2}" - filename="${3}" - - if command -v curl >/dev/null 2>&1; then - curl -fsSL "${url}" -o "${dir}/${filename}" - rcode="${?}" - elif command -v wget >/dev/null 2>&1; then - wget --quiet "${url}" -O "${dir}/${filename}" - rcode="${?}" - else - rcode="20" - fi - - return "${rcode}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: checksum_check -# DESCRIPTION: Attempt to verify checksum of downloaded file to ensure -# integrity. Tries multiple tools before faling. -# PARAMETERS: $1 = path to checksum file -# $2 = location of file to check -# $3 = working directory -# RETURNS: 0 = checkusm verified -# 1 = checksum verification failed -# 20 = failed to determine tool to use to check checksum -# 30 = failed to change into or go back from working dir -#------------------------------------------------------------------------------- -checksum_check() { - local checksum_file - local file - local dir - local rcode - local shasum_1 - local shasum_2 - local shasum_c - - checksum_file="${1}" - file="${2}" - dir="${3}" - - cd "${dir}" || return 30 - if command -v sha256sum >/dev/null 2>&1; then - ## Not all sha256sum versions seem to have --ignore-missing, so filter the checksum file - ## to only include the file we downloaded. - grep "$(basename "${file}")" "${checksum_file}" > filtered_checksum.txt - shasum_c="$(sha256sum -c "filtered_checksum.txt")" - rcode="${?}" - elif command -v shasum >/dev/null 2>&1; then - ## With shasum on FreeBSD, we don't get to --ignore-missing, so filter the checksum file - ## to only include the file we downloaded. - grep "$(basename "${file}")" "${checksum_file}" > filtered_checksum.txt - shasum_c="$(shasum -a 256 -c "filtered_checksum.txt")" - rcode="${?}" - elif command -v sha256 >/dev/null 2>&1; then - ## With sha256 on FreeBSD, we don't get to --ignore-missing, so filter the checksum file - ## to only include the file we downloaded. - ## Also sha256 -c option seems to fail, so fall back to an if statement - grep "$(basename "${file}")" "${checksum_file}" > filtered_checksum.txt - shasum_1="$(sha256 -q "${file}")" - shasum_2="$(awk '{print $1}' filtered_checksum.txt)" - if [[ "${shasum_1}" == "${shasum_2}" ]]; then - rcode="0" - else - rcode="1" - fi - shasum_c="Expected: ${shasum_1}, Got: ${shasum_2}" - else - return 20 - fi - cd - >/dev/null 2>&1 || return 30 - - if [[ "${rcode}" -gt "0" ]]; then - echo "${shasum_c}" - fi - return "${rcode}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: extract_file -# DESCRIPTION: Extracts a file into a location. Attempts to determine which -# tool to use by checking file extention. -# PARAMETERS: $1 = file to extract -# $2 = location to extract file into -# $3 = extention -# RETURNS: Return code of the tool used to extract the file -# 20 = Failed to determine which tool to use -# 30 = Failed to find tool in path -#------------------------------------------------------------------------------- -extract_file() { - local file - local dir - local ext - local rcode - - file="${1}" - dir="${2}" - ext="${3}" - - case "${ext}" in - "zip" ) if command -v unzip >/dev/null 2>&1; then - unzip "${file}" -d "${dir}" - rcode="${?}" - else - rcode="30" - fi - ;; - "tar.gz" ) if command -v tar >/dev/null 2>&1; then - tar -xf "${file}" -C "${dir}" - rcode="${?}" - else - rcode="31" - fi - ;; - * ) rcode="20";; - esac - - return "${rcode}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: install_file_freebsd -# DESCRIPTION: Installs a file into a location using 'install'. If EUID not -# 0, then attempt to use sudo. -# PARAMETERS: $1 = file to install -# $2 = location to install file into -# RETURNS: 0 = File Installed -# 1 = File not installed -# 20 = Could not find install command -# 21 = Could not find sudo command -#------------------------------------------------------------------------------- -install_file_freebsd() { - local file - local prefix - local rcode - - file="${1}" - prefix="${2}" - - if command -v install >/dev/null 2>&1; then - if [[ "${EUID}" == "0" ]]; then - install -C -b -B '_old' -m 755 "${file}" "${prefix}" - rcode="${?}" - else - if command -v sudo >/dev/null 2>&1; then - sudo install -C -b -B '_old' -m 755 "${file}" "${prefix}" - rcode="${?}" - else - rcode="21" - fi - fi - else - rcode="20" - fi - - return "${rcode}" -} - - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: install_file_linux -# DESCRIPTION: Installs a file into a location using 'install'. If EUID not -# 0, then attempt to use sudo (unless on android). -# PARAMETERS: $1 = file to install -# $2 = location to install file into -# RETURNS: 0 = File Installed -# 1 = File not installed -# 20 = Could not find install command -# 21 = Could not find sudo command -#------------------------------------------------------------------------------- -install_file_linux() { - local file - local prefix - local rcode - - file="${1}" - prefix="${2}" - - if command -v install >/dev/null 2>&1; then - if [[ "${EUID}" == "0" ]]; then - install -C -b -S '_old' -m 755 -t "${prefix}" "${file}" - rcode="${?}" - else - if command -v sudo >/dev/null 2>&1; then - sudo install -C -b -S '_old' -m 755 "${file}" "${prefix}" - rcode="${?}" - elif [[ "${ANDROID_ROOT}" != "" ]]; then - install -C -b -S '_old' -m 755 -t "${prefix}" "${file}" - rcode="${?}" - else - rcode="21" - fi - fi - else - rcode="20" - fi - - return "${rcode}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: install_file_cygwin -# DESCRIPTION: Installs a file into a location using 'install'. If EUID not -# 0, then attempt to use sudo. -# Not really 100% sure this is how to install croc in cygwin. -# PARAMETERS: $1 = file to install -# $2 = location to install file into -# RETURNS: 0 = File Installed -# 20 = Could not find install command -# 21 = Could not find sudo command -#------------------------------------------------------------------------------- -install_file_cygwin() { - local file - local prefix - local rcode - - file="${1}" - prefix="${2}" - - if command -v install >/dev/null 2>&1; then - if [[ "${EUID}" == "0" ]]; then - install -m 755 "${prefix}" "${file}" - rcode="${?}" - else - if command -v sudo >/dev/null 2>&1; then - sudo install -m 755 "${file}" "${prefix}" - rcode="${?}" - else - rcode="21" - fi - fi - else - rcode="20" - fi - - return "${rcode}" -} - -#--- FUNCTION ---------------------------------------------------------------- -# NAME: main -# DESCRIPTION: Put it all together in a logical way -# ...at least that is the hope... -# PARAMETERS: 1 = prefix -# RETURNS: 0 = All good -# 1 = Something done broke -#------------------------------------------------------------------------------- -main() { - local prefix - local tmpdir - local tmpdir_rcode - local croc_arch - local croc_arch_rcode - local croc_os - local croc_os_rcode - local croc_base_url - local croc_url - local croc_file - local croc_checksum_file - local croc_bin_name - local croc_version - local croc_dl_ext - local download_file_rcode - local download_checksum_file_rcode - local checksum_check_rcode - local extract_file_rcode - local install_file_rcode - - croc_bin_name="croc" - croc_version="6.2.0" - croc_dl_ext="tar.gz" - croc_base_url="https://github.com/schollz/croc/releases/download" - prefix="${1}" - - print_banner - print_message "== Install prefix set to ${prefix}" "info" - - tmpdir="$(make_tempdir "${croc_bin_name}")" - tmpdir_rcode="${?}" - if [[ "${tmpdir_rcode}" == "0" ]]; then - print_message "== Created temp dir at ${tmpdir}" "info" - elif [[ "${tmpdir_rcode}" == "1" ]]; then - print_message "== Failed to create temp dir at ${tmpdir}" "error" - else - print_message "== 'mktemp' not found in path. Is it installed?" "error" - exit 1 - fi - - croc_arch="$(determine_arch)" - croc_arch_rcode="${?}" - if [[ "${croc_arch_rcode}" == "0" ]]; then - print_message "== Architecture detected as ${croc_arch}" "info" - elif [[ "${croc_arch_rcode}" == "1" ]]; then - print_message "== Architecture not detected" "error" - exit 1 - else - print_message "== 'uname' not found in path. Is it installed?" "error" - exit 1 - fi - - croc_os="$(determine_os)" - croc_os_rcode="${?}" - if [[ "${croc_os_rcode}" == "0" ]]; then - print_message "== OS detected as ${croc_os}" "info" - elif [[ "${croc_os_rcode}" == "1" ]]; then - print_message "== OS not detected" "error" - exit 1 - else - print_message "== 'uname' not found in path. Is it installed?" "error" - exit 1 - fi - - case "${croc_os}" in - "Darwin" ) croc_os="macOS";; - "CYGWIN"* ) croc_os="Windows"; - croc_dl_ext="zip"; - print_message "== Cygwin is currently unsupported." "error"; - exit 1;; - esac - - case "${croc_arch}" in - "x86_64" ) croc_arch="64bit";; - "amd64" ) croc_arch="64bit";; - "aarch64" ) croc_arch="ARM64";; - "armv7l" ) croc_arch="ARM";; - "i686" ) croc_arch="32bit";; - * ) croc_arch="unknown";; - esac - - croc_file="${croc_bin_name}_${croc_version}_${croc_os}-${croc_arch}.${croc_dl_ext}" - croc_checksum_file="${croc_bin_name}_${croc_version}_checksums.txt" - croc_url="${croc_base_url}/v${croc_version}/${croc_file}" - croc_checksum_url="${croc_base_url}/v${croc_version}/${croc_checksum_file}" - - download_file "${croc_url}" "${tmpdir}" "${croc_file}" - download_file_rcode="${?}" - if [[ "${download_file_rcode}" == "0" ]]; then - print_message "== Downloaded croc archive into ${tmpdir}" "info" - elif [[ "${download_file_rcode}" == "1" ]]; then - print_message "== Failed to download croc archive" "error" - exit 1 - elif [[ "${download_file_rcode}" == "20" ]]; then - print_message "== Failed to locate curl or wget" "error" - exit 1 - else - print_message "== Return code of download tool returned an unexpected value of ${download_file_rcode}" "error" - exit 1 - fi - download_file "${croc_checksum_url}" "${tmpdir}" "${croc_checksum_file}" - download_checksum_file_rcode="${?}" - if [[ "${download_checksum_file_rcode}" == "0" ]]; then - print_message "== Downloaded croc checksums file into ${tmpdir}" "info" - elif [[ "${download_checksum_file_rcode}" == "1" ]]; then - print_message "== Failed to download croc checksums" "error" - exit 1 - elif [[ "${download_checksum_file_rcode}" == "20" ]]; then - print_message "== Failed to locate curl or wget" "error" - exit 1 - else - print_message "== Return code of download tool returned an unexpected value of ${download_checksum_file_rcode}" "error" - exit 1 - fi - - checksum_check "${tmpdir}/${croc_checksum_file}" "${tmpdir}/${croc_file}" "${tmpdir}" - checksum_check_rcode="${?}" - if [[ "${checksum_check_rcode}" == "0" ]]; then - print_message "== Checksum of ${tmpdir}/${croc_file} verified" "ok" - elif [[ "${checksum_check_rcode}" == "1" ]]; then - print_message "== Failed to verify checksum of ${tmpdir}/${croc_file}" "error" - exit 1 - elif [[ "${checksum_check_rcode}" == "20" ]]; then - print_message "== Failed to find tool to verify sha256 sums" "error" - exit 1 - elif [[ "${checksum_check_rcode}" == "30" ]]; then - print_message "== Failed to change into working directory ${tmpdir}" "error" - exit 1 - else - print_message "== Unknown return code returned while checking checksum of ${tmpdir}/${croc_file}. Returned ${checksum_check_rcode}" "error" - exit 1 - fi - - extract_file "${tmpdir}/${croc_file}" "${tmpdir}/" "${croc_dl_ext}" - extract_file_rcode="${?}" - if [[ "${extract_file_rcode}" == "0" ]]; then - print_message "== Extracted ${croc_file} to ${tmpdir}/" "info" - elif [[ "${extract_file_rcode}" == "1" ]]; then - print_message "== Failed to extract ${croc_file}" "error" - exit 1 - elif [[ "${extract_file_rcode}" == "20" ]]; then - print_message "== Failed to determine which extraction tool to use" "error" - exit 1 - elif [[ "${extract_file_rcode}" == "30" ]]; then - print_message "== Failed to find 'unzip' in path" "error" - exit 1 - elif [[ "${extract_file_rcode}" == "31" ]]; then - print_message "== Failed to find 'tar' in path" "error" - exit 1 - else - print_message "== Unknown error returned from extraction attempt" "error" - exit 1 - fi - - case "${croc_os}" in - "Linux" ) install_file_linux "${tmpdir}/${croc_bin_name}" "${prefix}/"; - install_file_rcode="${?}";; - "FreeBSD" ) install_file_freebsd "${tmpdir}/${croc_bin_name}" "${prefix}/"; - install_file_rcode="${?}";; - "macOS" ) install_file_freebsd "${tmpdir}/${croc_bin_name}" "${prefix}/"; - install_file_rcode="${?}";; - "Windows" ) install_file_cygwin "${tmpdir}/${croc_bin_name}" "${prefix}/"; - install_file_rcode="${?}";; - esac - if [[ "${install_file_rcode}" == "0" ]]; then - print_message "== Installed ${croc_bin_name} to ${prefix}/" "ok" - elif [[ "${install_file_rcode}" == "1" ]]; then - print_message "== Failed to install ${croc_bin_name}" "error" - exit 1 - elif [[ "${install_file_rcode}" == "20" ]]; then - print_message "== Failed to locate 'install' command" "error" - exit 1 - elif [[ "${install_file_rcode}" == "21" ]]; then - print_message "== Failed to locate 'sudo' command" "error" - exit 1 - else - print_message "== Install attempt returned an unexpected value of ${install_file_rcode}" "error" - exit 1 - fi - - print_message "== Installation complete" "ok" - - exit 0 -} - -#------------------------------------------------------------------------------- -# ARGUMENT PARSING -#------------------------------------------------------------------------------- -OPTS="hp:" -while getopts "${OPTS}" optchar; do - case "${optchar}" in - 'h' ) print_help - exit 0 - ;; - 'p' ) INSTALL_PREFIX="${OPTARG}" - ;; - /? ) print_message "Unknown option ${OPTARG}" "warn" - ;; - esac -done - -#------------------------------------------------------------------------------- -# CALL MAIN -#------------------------------------------------------------------------------- -main "${INSTALL_PREFIX}" diff --git a/src/install/updateversion.go b/src/install/updateversion.go deleted file mode 100644 index 679cfa1..0000000 --- a/src/install/updateversion.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os/exec" - "strings" -) - -func main() { - err := run() - if err != nil { - fmt.Println(err) - } -} - -func run() (err error) { - version, err := exec.Command("git", "describe", "--abbrev=0").Output() - if err != nil { - return - } - versionNew := strings.TrimSpace(string(version)) - versionHash, err := exec.Command("git", "rev-parse", "--short", "HEAD").Output() - if err != nil { - return - } - versionHashNew := strings.TrimSpace(string(versionHash)) - fmt.Println(versionNew) - fmt.Println(versionHashNew) - - err = replaceInFile("src/cli/cli.go", `Version = "`, `"`, versionNew+"-"+versionHashNew) - if err == nil { - fmt.Printf("updated cli.go to version %s\n", versionNew) - } - err = replaceInFile("README.md", `version-`, `-b`, strings.Split(versionNew, "-")[0]) - if err == nil { - fmt.Printf("updated README to version %s\n", strings.Split(versionNew, "-")[0]) - } - - err = replaceInFile("src/install/default.txt", `croc_version="`, `"`, strings.Split(versionNew, "-")[0][1:]) - if err == nil { - fmt.Printf("updated default.txt to version %s\n", strings.Split(versionNew, "-")[0][1:]) - } - - return -} - -func replaceInFile(fname, start, end, replacement string) (err error) { - b, err := ioutil.ReadFile(fname) - if err != nil { - return - } - oldVersion := GetStringInBetween(string(b), start, end) - if oldVersion == "" { - err = fmt.Errorf("nothing") - return - } - newF := strings.Replace( - string(b), - fmt.Sprintf("%s%s%s", start, oldVersion, end), - fmt.Sprintf("%s%s%s", start, replacement, end), - 1, - ) - err = ioutil.WriteFile(fname, []byte(newF), 0644) - return -} - -// GetStringInBetween Returns empty string if no start string found -func GetStringInBetween(str string, start string, end string) (result string) { - s := strings.Index(str, start) - if s == -1 { - return - } - s += len(start) - e := strings.Index(str[s:], end) - if e == -1 { - return - } - e += s - return str[s:e] -} diff --git a/src/message/message.go b/src/message/message.go deleted file mode 100644 index 2f0abb6..0000000 --- a/src/message/message.go +++ /dev/null @@ -1,56 +0,0 @@ -package message - -import ( - "encoding/json" - - "github.com/schollz/croc/v6/src/comm" - "github.com/schollz/croc/v6/src/compress" - "github.com/schollz/croc/v6/src/crypt" - log "github.com/schollz/logger" -) - -// Message is the possible payload for messaging -type Message struct { - Type string `json:"t,omitempty"` - Message string `json:"m,omitempty"` - Bytes []byte `json:"b,omitempty"` - Num int `json:"n,omitempty"` -} - -func (m Message) String() string { - b, _ := json.Marshal(m) - return string(b) -} - -// Send will send out -func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) { - mSend, err := Encode(key, m) - if err != nil { - return - } - log.Debugf("writing %s message (%d bytes)", m.Type, len(mSend)) - _, err = c.Write(mSend) - return -} - -// Encode will convert to bytes -func Encode(key crypt.Encryption, m Message) (b []byte, err error) { - b, err = json.Marshal(m) - if err != nil { - return - } - b = compress.Compress(b) - b, err = key.Encrypt(b) - return -} - -// Decode will convert from bytes -func Decode(key crypt.Encryption, b []byte) (m Message, err error) { - b, err = key.Decrypt(b) - if err != nil { - return - } - b = compress.Decompress(b) - err = json.Unmarshal(b, &m) - return -} diff --git a/src/message/message_test.go b/src/message/message_test.go deleted file mode 100644 index cc4772d..0000000 --- a/src/message/message_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package message - -import ( - "crypto/rand" - "fmt" - "net" - "testing" - "time" - - "github.com/schollz/croc/v6/src/comm" - "github.com/schollz/croc/v6/src/crypt" - log "github.com/schollz/logger" - "github.com/stretchr/testify/assert" -) - -func TestMessage(t *testing.T) { - m := Message{Type: "message", Message: "hello, world"} - e, err := crypt.New(nil, nil) - assert.Nil(t, err) - fmt.Println(e.Salt()) - b, err := Encode(e, m) - assert.Nil(t, err) - fmt.Printf("%x\n", b) - - m2, err := Decode(e, b) - assert.Nil(t, err) - assert.Equal(t, m, m2) - assert.Equal(t, `{"t":"message","m":"hello, world"}`, m.String()) -} - -func TestSend(t *testing.T) { - token := make([]byte, 40000000) - rand.Read(token) - - port := "8801" - go func() { - log.Debugf("starting TCP server on " + port) - server, err := net.Listen("tcp", "0.0.0.0:"+port) - if err != nil { - log.Error(err) - } - defer server.Close() - // spawn a new goroutine whenever a client connects - for { - connection, err := server.Accept() - if err != nil { - log.Error(err) - } - log.Debugf("client %s connected", connection.RemoteAddr().String()) - go func(port string, connection net.Conn) { - c := comm.New(connection) - err = c.Send([]byte("hello, world")) - assert.Nil(t, err) - data, err := c.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte("hello, computer"), data) - data, err = c.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte{'\x00'}, data) - data, err = c.Receive() - assert.Nil(t, err) - assert.Equal(t, token, data) - }(port, connection) - } - }() - - time.Sleep(300 * time.Millisecond) - a, err := comm.NewConnection("localhost:"+port, 10*time.Minute) - assert.Nil(t, err) - m := Message{Type: "message", Message: "hello, world"} - e, err := crypt.New(nil, nil) - assert.Nil(t, err) - - assert.Nil(t, Send(a, e, m)) -} diff --git a/src/models/constants.go b/src/models/constants.go deleted file mode 100644 index 4c07498..0000000 --- a/src/models/constants.go +++ /dev/null @@ -1,7 +0,0 @@ -package models - -// TCP_BUFFER_SIZE is the maximum packet size -const TCP_BUFFER_SIZE = 1024 * 64 - -// DEFAULT_RELAY is the default relay used (can be set using --relay) -const DEFAULT_RELAY = "142.93.177.120:9009" diff --git a/src/tcp/tcp.go b/src/tcp/tcp.go deleted file mode 100644 index 473ed26..0000000 --- a/src/tcp/tcp.go +++ /dev/null @@ -1,290 +0,0 @@ -package tcp - -import ( - "bytes" - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/pkg/errors" - "github.com/schollz/croc/v6/src/comm" - "github.com/schollz/croc/v6/src/models" - log "github.com/schollz/logger" -) - -type server struct { - port string - debugLevel string - banner string - rooms roomMap -} - -type roomInfo struct { - first *comm.Comm - second *comm.Comm - opened time.Time - full bool -} - -type roomMap struct { - rooms map[string]roomInfo - sync.Mutex -} - -var timeToRoomDeletion = 10 * time.Minute - -// Run starts a tcp listener, run async -func Run(debugLevel, port string, banner ...string) (err error) { - s := new(server) - s.port = port - s.debugLevel = debugLevel - if len(banner) > 0 { - s.banner = banner[0] - } - return s.start() -} - -func (s *server) start() (err error) { - log.SetLevel(s.debugLevel) - s.rooms.Lock() - s.rooms.rooms = make(map[string]roomInfo) - s.rooms.Unlock() - - // delete old rooms - go func() { - for { - time.Sleep(timeToRoomDeletion) - roomsToDelete := []string{} - s.rooms.Lock() - for room := range s.rooms.rooms { - if time.Since(s.rooms.rooms[room].opened) > 3*time.Hour { - roomsToDelete = append(roomsToDelete, room) - } - } - s.rooms.Unlock() - - for _, room := range roomsToDelete { - s.deleteRoom(room) - } - } - }() - - err = s.run() - if err != nil { - log.Error(err) - } - return -} - -func (s *server) run() (err error) { - log.Infof("starting TCP server on " + s.port) - server, err := net.Listen("tcp", ":"+s.port) - if err != nil { - return errors.Wrap(err, "Error listening on :"+s.port) - } - defer server.Close() - // spawn a new goroutine whenever a client connects - for { - connection, err := server.Accept() - if err != nil { - return errors.Wrap(err, "problem accepting connection") - } - log.Debugf("client %s connected", connection.RemoteAddr().String()) - go func(port string, connection net.Conn) { - errCommunication := s.clientCommuncation(port, comm.New(connection)) - if errCommunication != nil { - log.Warnf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error()) - } - }(s.port, connection) - } -} - -func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) { - // send ok to tell client they are connected - banner := s.banner - if len(banner) == 0 { - banner = "ok" - } - log.Debugf("sending '%s'", banner) - err = c.Send([]byte(banner + "|||" + c.Connection().RemoteAddr().String())) - if err != nil { - return - } - - // wait for client to tell me which room they want - log.Debug("waiting for answer") - roomBytes, err := c.Receive() - if err != nil { - return - } - room := string(roomBytes) - - s.rooms.Lock() - // create the room if it is new - if _, ok := s.rooms.rooms[room]; !ok { - s.rooms.rooms[room] = roomInfo{ - first: c, - opened: time.Now(), - } - s.rooms.Unlock() - // tell the client that they got the room - err = c.Send([]byte("ok")) - if err != nil { - log.Error(err) - s.deleteRoom(room) - return - } - log.Debugf("room %s has 1", room) - return nil - } - if s.rooms.rooms[room].full { - s.rooms.Unlock() - err = c.Send([]byte("room full")) - if err != nil { - log.Error(err) - s.deleteRoom(room) - return - } - return nil - } - log.Debugf("room %s has 2", room) - s.rooms.rooms[room] = roomInfo{ - first: s.rooms.rooms[room].first, - second: c, - opened: s.rooms.rooms[room].opened, - full: true, - } - otherConnection := s.rooms.rooms[room].first - s.rooms.Unlock() - - // second connection is the sender, time to staple connections - var wg sync.WaitGroup - wg.Add(1) - - // start piping - go func(com1, com2 *comm.Comm, wg *sync.WaitGroup) { - log.Debug("starting pipes") - pipe(com1.Connection(), com2.Connection()) - wg.Done() - log.Debug("done piping") - }(otherConnection, c, &wg) - - // tell the sender everything is ready - err = c.Send([]byte("ok")) - if err != nil { - s.deleteRoom(room) - return - } - wg.Wait() - - // delete room - s.deleteRoom(room) - return nil -} - -func (s *server) deleteRoom(room string) { - s.rooms.Lock() - defer s.rooms.Unlock() - if _, ok := s.rooms.rooms[room]; !ok { - return - } - log.Debugf("deleting room: %s", room) - if s.rooms.rooms[room].first != nil { - s.rooms.rooms[room].first.Close() - } - if s.rooms.rooms[room].second != nil { - s.rooms.rooms[room].second.Close() - } - s.rooms.rooms[room] = roomInfo{first: nil, second: nil} - delete(s.rooms.rooms, room) - -} - -// chanFromConn creates a channel from a Conn object, and sends everything it -// Read()s from the socket to the channel. -func chanFromConn(conn net.Conn) chan []byte { - c := make(chan []byte, 1) - - go func() { - b := make([]byte, models.TCP_BUFFER_SIZE) - - for { - n, err := conn.Read(b) - if n > 0 { - res := make([]byte, n) - // Copy the buffer so it doesn't get changed while read by the recipient. - copy(res, b[:n]) - c <- res - } - if err != nil { - log.Debug(err) - c <- nil - break - } - } - log.Debug("exiting") - }() - - return c -} - -// pipe creates a full-duplex pipe between the two sockets and -// transfers data from one to the other. -func pipe(conn1 net.Conn, conn2 net.Conn) { - chan1 := chanFromConn(conn1) - chan2 := chanFromConn(conn2) - - for { - select { - case b1 := <-chan1: - if b1 == nil { - return - } - conn2.Write(b1) - - case b2 := <-chan2: - if b2 == nil { - return - } - conn1.Write(b2) - } - } -} - -// ConnectToTCPServer will initiate a new connection -// to the specified address, room with optional time limit -func ConnectToTCPServer(address, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) { - if len(timelimit) > 0 { - c, err = comm.NewConnection(address, timelimit[0]) - } else { - c, err = comm.NewConnection(address) - } - if err != nil { - return - } - log.Debug("waiting for first ok") - data, err := c.Receive() - if err != nil { - return - } - banner = strings.Split(string(data), "|||")[0] - ipaddr = strings.Split(string(data), "|||")[1] - log.Debug("sending room") - err = c.Send([]byte(room)) - if err != nil { - return - } - log.Debug("waiting for room confirmation") - data, err = c.Receive() - if err != nil { - return - } - if !bytes.Equal(data, []byte("ok")) { - err = fmt.Errorf("got bad response: %s", data) - return - } - log.Debug("all set") - return -} diff --git a/src/tcp/tcp_test.go b/src/tcp/tcp_test.go deleted file mode 100644 index 8c98909..0000000 --- a/src/tcp/tcp_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package tcp - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestTCP(t *testing.T) { - timeToRoomDeletion = 100 * time.Millisecond - go Run("debug", "8281", "8282") - time.Sleep(100 * time.Millisecond) - c1, banner, _, err := ConnectToTCPServer("localhost:8281", "testRoom", 1*time.Minute) - assert.Equal(t, banner, "8282") - assert.Nil(t, err) - c2, _, _, err := ConnectToTCPServer("localhost:8281", "testRoom") - assert.Nil(t, err) - _, _, _, err = ConnectToTCPServer("localhost:8281", "testRoom") - assert.NotNil(t, err) - _, _, _, err = ConnectToTCPServer("localhost:8281", "testRoom", 1*time.Nanosecond) - assert.NotNil(t, err) - - // try sending data - assert.Nil(t, c1.Send([]byte("hello, c2"))) - data, err := c2.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte("hello, c2"), data) - - assert.Nil(t, c2.Send([]byte("hello, c1"))) - data, err = c1.Receive() - assert.Nil(t, err) - assert.Equal(t, []byte("hello, c1"), data) - - c1.Close() - time.Sleep(300 * time.Millisecond) -} diff --git a/src/utils/utils.go b/src/utils/utils.go deleted file mode 100644 index c4e288e..0000000 --- a/src/utils/utils.go +++ /dev/null @@ -1,234 +0,0 @@ -package utils - -import ( - "bufio" - "bytes" - "crypto/md5" - "crypto/rand" - "crypto/sha256" - "fmt" - "io" - "io/ioutil" - "log" - "math" - "net" - "net/http" - "os" - "strings" - - "github.com/cespare/xxhash" - "github.com/kalafut/imohash" - "github.com/schollz/mnemonicode" -) - -// Exists reports whether the named file or directory exists. -func Exists(name string) bool { - if _, err := os.Stat(name); err != nil { - if os.IsNotExist(err) { - return false - } - } - return true -} - -// GetInput returns the input with a given prompt -func GetInput(prompt string) string { - reader := bufio.NewReader(os.Stdin) - fmt.Fprintf(os.Stderr, "%s", prompt) - text, _ := reader.ReadString('\n') - return strings.TrimSpace(text) -} - -// HashFile returns the hash of a file -func HashFile(fname string) (hash256 []byte, err error) { - return IMOHashFile(fname) -} - -// MD5HashFile returns MD5 hash -func MD5HashFile(fname string) (hash256 []byte, err error) { - f, err := os.Open(fname) - if err != nil { - return - } - defer f.Close() - - h := md5.New() - if _, err = io.Copy(h, f); err != nil { - return - } - - hash256 = h.Sum(nil) - return -} - -// IMOHashFile returns imohash -func IMOHashFile(fname string) (hash []byte, err error) { - b, err := imohash.SumFile(fname) - hash = b[:] - return -} - -// XXHashFile returns the xxhash of a file -func XXHashFile(fname string) (hash256 []byte, err error) { - f, err := os.Open(fname) - if err != nil { - return - } - defer f.Close() - - h := xxhash.New() - if _, err = io.Copy(h, f); err != nil { - return - } - - hash256 = h.Sum(nil) - return -} - -// SHA256 returns sha256 sum -func SHA256(s string) string { - sha := sha256.New() - sha.Write([]byte(s)) - return fmt.Sprintf("%x", sha.Sum(nil)) -} - -// PublicIP returns public ip address -func PublicIP() (ip string, err error) { - resp, err := http.Get("https://canhazip.com") - if err != nil { - return - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - ip = strings.TrimSpace(string(bodyBytes)) - } - return -} - -// LocalIP returns local ip address -func LocalIP() string { - conn, err := net.Dial("udp", "8.8.8.8:80") - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - localAddr := conn.LocalAddr().(*net.UDPAddr) - - return localAddr.IP.String() -} - -// GetRandomName returns mnemoicoded random name -func GetRandomName() string { - result := []string{} - bs := make([]byte, 4) - rand.Read(bs) - result = mnemonicode.EncodeWordList(result, bs) - return strings.Join(result, "-") -} - -// ByteCountDecimal converts bytes to human readable byte string -func ByteCountDecimal(b int64) string { - const unit = 1000 - if b < unit { - return fmt.Sprintf("%d B", b) - } - div, exp := int64(unit), 0 - for n := b / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) -} - -// MissingChunks returns the positions of missing chunks. -// If file doesn't exist, it returns an empty chunk list (all chunks). -// If the file size is not the same as requested, it returns an empty chunk list (all chunks). -func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int64) { - f, err := os.Open(fname) - if err != nil { - return - } - defer f.Close() - - fstat, err := os.Stat(fname) - if fstat.Size() != fsize || err != nil { - return - } - - emptyBuffer := make([]byte, chunkSize) - chunkNum := 0 - chunks := make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize)))) - var currentLocation int64 - for { - buffer := make([]byte, chunkSize) - bytesread, err := f.Read(buffer) - if err != nil { - break - } - if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) { - chunks[chunkNum] = currentLocation - chunkNum++ - } - currentLocation += int64(bytesread) - } - if chunkNum == 0 { - chunkRanges = []int64{} - } else { - chunks = chunks[:chunkNum] - chunkRanges = []int64{int64(chunkSize), chunks[0]} - curCount := 0 - for i, chunk := range chunks { - if i == 0 { - continue - } - curCount++ - if chunk-chunks[i-1] > int64(chunkSize) { - chunkRanges = append(chunkRanges, int64(curCount)) - chunkRanges = append(chunkRanges, chunk) - curCount = 0 - } - } - chunkRanges = append(chunkRanges, int64(curCount+1)) - chunks = chunkRanges - } - return -} - -// ChunkRangesToChunks converts chunk ranges to list -func ChunkRangesToChunks(chunkRanges []int64) (chunks []int64) { - if len(chunkRanges) == 0 { - return - } - chunkSize := chunkRanges[0] - chunks = []int64{} - for i := 1; i < len(chunkRanges); i += 2 { - for j := int64(0); j < (chunkRanges[i+1]); j++ { - chunks = append(chunks, chunkRanges[i]+j*chunkSize) - } - } - return -} - -// GetLocalIPs returns all local ips -func GetLocalIPs() (ips []string, err error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return - } - ips = []string{} - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - ips = append(ips, ipnet.IP.String()) - } - } - } - return -} diff --git a/src/utils/utils_test.go b/src/utils/utils_test.go deleted file mode 100644 index 1e4ac70..0000000 --- a/src/utils/utils_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package utils - -import ( - "fmt" - "io/ioutil" - "log" - "math/rand" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func bigFile() { - rand.Seed(0) - bigBuff := make([]byte, 75000000) - rand.Read(bigBuff) - ioutil.WriteFile("bigfile.test", bigBuff, 0666) -} - -func BenchmarkMD5(b *testing.B) { - bigFile() - b.ResetTimer() - for i := 0; i < b.N; i++ { - MD5HashFile("bigfile.test") - } -} - -func BenchmarkXXHash(b *testing.B) { - bigFile() - b.ResetTimer() - for i := 0; i < b.N; i++ { - XXHashFile("bigfile.test") - } -} -func BenchmarkImoHash(b *testing.B) { - bigFile() - b.ResetTimer() - for i := 0; i < b.N; i++ { - IMOHashFile("bigfile.test") - } -} - -func TestExists(t *testing.T) { - bigFile() - defer os.Remove("bigfile.test") - fmt.Println(GetLocalIPs()) - assert.True(t, Exists("bigfile.test")) - assert.False(t, Exists("doesnotexist")) -} - -func TestMD5HashFile(t *testing.T) { - bigFile() - defer os.Remove("bigfile.test") - b, err := MD5HashFile("bigfile.test") - assert.Nil(t, err) - assert.Equal(t, "9fed05acbacbc6a36555c998501c21f6", fmt.Sprintf("%x", b)) - _, err = MD5HashFile("bigfile.test.nofile") - assert.NotNil(t, err) -} - -func TestIMOHashFile(t *testing.T) { - bigFile() - defer os.Remove("bigfile.test") - b, err := IMOHashFile("bigfile.test") - assert.Nil(t, err) - assert.Equal(t, "c0d1e123a96a598ea801cc503d3db8c0", fmt.Sprintf("%x", b)) -} - -func TestXXHashFile(t *testing.T) { - bigFile() - defer os.Remove("bigfile.test") - b, err := XXHashFile("bigfile.test") - assert.Nil(t, err) - assert.Equal(t, "f2da6ee7e75e8324", fmt.Sprintf("%x", b)) - _, err = XXHashFile("nofile") - assert.NotNil(t, err) -} - -func TestSHA256(t *testing.T) { - assert.Equal(t, "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b", SHA256("hello, world")) -} - -func TestByteCountDecimal(t *testing.T) { - assert.Equal(t, "10.0 kB", ByteCountDecimal(10000)) - assert.Equal(t, "50 B", ByteCountDecimal(50)) - assert.Equal(t, "12.4 MB", ByteCountDecimal(12378517)) -} - -func TestMissingChunks(t *testing.T) { - fileSize := 100 - chunkSize := 10 - rand.Seed(1) - bigBuff := make([]byte, fileSize) - rand.Read(bigBuff) - ioutil.WriteFile("missing.test", bigBuff, 0644) - empty := make([]byte, chunkSize) - f, err := os.OpenFile("missing.test", os.O_RDWR, 0644) - assert.Nil(t, err) - for block := 0; block < fileSize/chunkSize; block++ { - if block == 0 || block == 4 || block == 5 || block >= 7 { - f.WriteAt(empty, int64(block*chunkSize)) - } - } - f.Close() - - chunkRanges := MissingChunks("missing.test", int64(fileSize), chunkSize) - assert.Equal(t, []int64{10, 0, 1, 40, 2, 70, 3}, chunkRanges) - - chunks := ChunkRangesToChunks(chunkRanges) - assert.Equal(t, []int64{0, 40, 50, 70, 80, 90}, chunks) - - os.Remove("missing.test") - - content := []byte("temporary file's content") - tmpfile, err := ioutil.TempFile("", "example") - if err != nil { - log.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) // clean up - - if _, err := tmpfile.Write(content); err != nil { - panic(err) - } - if err := tmpfile.Close(); err != nil { - panic(err) - } - chunkRanges = MissingChunks(tmpfile.Name(), int64(len(content)), chunkSize) - assert.Empty(t, chunkRanges) - chunkRanges = MissingChunks(tmpfile.Name(), int64(len(content)+10), chunkSize) - assert.Empty(t, chunkRanges) - chunkRanges = MissingChunks(tmpfile.Name()+"ok", int64(len(content)), chunkSize) - assert.Empty(t, chunkRanges) - chunks = ChunkRangesToChunks(chunkRanges) - assert.Empty(t, chunks) -} - -// func Test1(t *testing.T) { -// chunkRanges := MissingChunks("../../m/bigfile.test", int64(75000000), 1024*64/2) -// fmt.Println(chunkRanges) -// fmt.Println(ChunkRangesToChunks((chunkRanges))) -// assert.Nil(t, nil) -// } - -func TestHashFile(t *testing.T) { - content := []byte("temporary file's content") - tmpfile, err := ioutil.TempFile("", "example") - if err != nil { - log.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) // clean up - - if _, err := tmpfile.Write(content); err != nil { - panic(err) - } - if err := tmpfile.Close(); err != nil { - panic(err) - } - hashed, err := HashFile(tmpfile.Name()) - assert.Nil(t, err) - assert.Equal(t, "18c9673a4bb8325d323e7f24fda9ae1e", fmt.Sprintf("%x", hashed)) -} - -func TestPublicIP(t *testing.T) { - ip, err := PublicIP() - fmt.Println(ip) - assert.True(t, strings.Contains(ip, ".")) - assert.Nil(t, err) -} - -func TestLocalIP(t *testing.T) { - ip := LocalIP() - fmt.Println(ip) - assert.True(t, strings.Contains(ip, ".")) -} - -func TestGetRandomName(t *testing.T) { - name := GetRandomName() - assert.NotEmpty(t, name) -} diff --git a/src/webrtc/webrtc.go b/src/webrtc/webrtc.go new file mode 100644 index 0000000..21012cd --- /dev/null +++ b/src/webrtc/webrtc.go @@ -0,0 +1,218 @@ +package webrtc + +import ( + "crypto/md5" + "crypto/rand" + "encoding/json" + "errors" + "flag" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/pion/webrtc/v2" + log "github.com/schollz/logger" +) + +const ( + bufferedAmountLowThreshold uint64 = 512 * 1024 // 512 KB + maxBufferedAmount uint64 = 1024 * 1024 // 1 MB + maxPacketSize uint64 = 65535 +) + +func setRemoteDescription(pc *webrtc.PeerConnection, sdp []byte) (err error) { + var desc webrtc.SessionDescription + err = json.Unmarshal(sdp, &desc) + if err != nil { + log.Error(err) + return + } + // Apply the desc as the remote description + err = pc.SetRemoteDescription(desc) + if err != nil { + log.Error(err) + } + return +} + +func createOfferer(finished chan<- error) (pc *webrtc.PeerConnection, err error) { + // Prepare the configuration + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}, + } + + // Create a new PeerConnection + pc, err = webrtc.NewPeerConnection(config) + if err != nil { + log.Error(err) + return + } + + ordered := false + maxRetransmits := uint16(0) + options := &webrtc.DataChannelInit{ + Ordered: &ordered, + MaxRetransmits: &maxRetransmits, + } + + sendMoreCh := make(chan struct{}) + + // Create a datachannel with label 'data' + dc, err := pc.CreateDataChannel("data", options) + if err != nil { + log.Error(err) + return + } + + // Register channel opening handling + sendData := func(buf []byte) error { + fmt.Printf("sent message: %x\n", md5.Sum(buf)) + err := dc.Send(buf) + if err != nil { + return err + } + if dc.BufferedAmount()+uint64(len(buf)) > maxBufferedAmount { + // wait until the bufferedAmount becomes lower than the threshold + <-sendMoreCh + } + return nil + } + + dc.OnOpen(func() { + fmt.Println(time.Now()) + log.Debugf("OnOpen: %s-%d. Start sending a series of 1024-byte packets as fast as it can\n", dc.Label(), dc.ID()) + its := 0 + for { + buf := make([]byte, maxPacketSize) + rand.Read(buf) + its++ + if its == 3 { + buf = []byte{1, 2, 3} + finished <- errors.New("done") + } + err2 := sendData(buf) + if err2 != nil { + finished <- err2 + return + } + time.Sleep(1 * time.Second) + if its == 3 { + break + } + } + }) + + // Set bufferedAmountLowThreshold so that we can get notified when + // we can send more + dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) + + // This callback is made when the current bufferedAmount becomes lower than the threadshold + dc.OnBufferedAmountLow(func() { + sendMoreCh <- struct{}{} + }) + + // Register the OnMessage to handle incoming messages + dc.OnMessage(func(dcMsg webrtc.DataChannelMessage) { + fmt.Printf("got message: %x\n", md5.Sum(dcMsg.Data)) + }) + + return pc, nil +} + +func test1() { + finished := make(chan error, 1) + var sender bool + flag.BoolVar(&sender, "sender", false, "set as sender") + flag.Parse() + log.SetLevel("debug") + if sender { + os.Remove("answer.json") + os.Remove("offer.json") + + offerPC, err := createOfferer(finished) + if err != nil { + log.Error(err) + } + // Now, create an offer + offer, err := offerPC.CreateOffer(nil) + if err != nil { + log.Error(err) + } + + err = offerPC.SetLocalDescription(offer) + if err != nil { + log.Error(err) + } + + desc, err := json.Marshal(offer) + if err != nil { + log.Error(err) + } + fmt.Println(string(desc)) + err = ioutil.WriteFile("offer.json", desc, 0644) + if err != nil { + log.Error(err) + } + + // wait for answer + for { + b, errFile := ioutil.ReadFile("answer.json") + if errFile != nil { + time.Sleep(3 * time.Second) + continue + } + fmt.Println(string(b)) + fmt.Println(time.Now()) + err = setRemoteDescription(offerPC, b) + if err != nil { + log.Error(err) + } + break + } + } else { + answerPC, err := createOfferer(finished) + if err != nil { + log.Error(err) + } + + b, err := ioutil.ReadFile("offer.json") + if err != nil { + log.Error(err) + } + + err = setRemoteDescription(answerPC, b) + if err != nil { + log.Error(err) + } + + answer, err := answerPC.CreateAnswer(nil) + if err != nil { + log.Error(err) + } + + err = answerPC.SetLocalDescription(answer) + if err != nil { + log.Error(err) + } + + desc2, err := json.Marshal(answer) + if err != nil { + log.Error(err) + } + + fmt.Println(string(desc2)) + err = ioutil.WriteFile("answer.json", desc2, 0644) + if err != nil { + log.Error(err) + } + } + + // Block forever + err := <-finished + if err != nil { + fmt.Println(err) + } else { + fmt.Println("finished gracefully") + } +}