diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..e190e6a --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,9 @@ +coverage: + status: + patch: off + project: + default: + target: auto + # adjust accordingly based on how flaky your tests are + # this allows a 10% drop from the previous base commit coverage + threshold: 10% \ No newline at end of file diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml new file mode 100644 index 0000000..840b40a --- /dev/null +++ b/.github/workflows/master.yml @@ -0,0 +1,29 @@ +name: gobuild + +on: + push: + branches: [ "master" ] + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Docker Setup Buildx + uses: docker/setup-buildx-action@v2.0.0 + - name: Build and push + uses: docker/build-push-action@v3 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: asmogo/nws-exit-node:latest \ No newline at end of file diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..94d5e19 --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,35 @@ +name: goreleaser + +on: + push: + # run only against tags + tags: + - '*' + +permissions: + contents: write + # packages: write + # issues: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: git fetch --force --tags + - uses: actions/setup-go@v3 + with: + go-version: '>=1.21.2' + cache: true + # More assembly might be required: Docker logins, GPG, etc. It all depends + # on your needs. + - uses: goreleaser/goreleaser-action@v2 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..e405da0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: tests + +on: [ push, pull_request ] + +jobs: + golang: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + - name: Golang run tests + run: go test -coverprofile=coverage.txt -covermode=atomic -v ./... + - uses: codecov/codecov-action@v3 + with: + verbose: true # optional (default = false) \ No newline at end of file diff --git a/README.md b/README.md index 239c345..bade659 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ If you used environment variables, no further configuration is needed. For `.env` file configurations, do so in the current working directory with the following content: ``` -NOSTR_RELAYS = 'ws://localhost:6666;wss://relay.damus.io' +NOSTR_RELAYS = 'ws://localhost:6666;wss://relay.com' ``` -Here, NOSTR_RELAYS is a list of nostr relays to publish events to and will only be used if there was no nprofile in the request. \ No newline at end of file +Here, NOSTR_RELAYS is a list of nostr relays to publish events to and will only be used if there was no nprofile in the request. diff --git a/cmd/listener/listener.go b/cmd/listener/listener.go new file mode 100644 index 0000000..0fa00b2 --- /dev/null +++ b/cmd/listener/listener.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "log" + "net" +) + +func main() { + startServer() +} +func startServer() { + l, err := net.Listen("tcp", "localhost:3338") + if err != nil { + log.Fatal(err) + } + defer l.Close() + for { + conn, err := l.Accept() + if err != nil { + log.Fatal(err) + } + go handleConnection(conn) + } +} + +func handleRequest(conn net.Conn) { + defer conn.Close() + + reader := bufio.NewReader(conn) + + for { + message, err := reader.ReadString('\n') // change the delimiter according to your messaging protocol + if err != nil { + if err != io.EOF { + log.Fatal(err) + } + break + } + fmt.Printf("Received message: %s", message) + _, err = conn.Write([]byte(message)) + if err != nil { + log.Fatal(err) + } + } +} +func handleConnection(conn net.Conn) { + defer conn.Close() + for { + var num int32 + // Read the integer from the connection + err := binary.Read(conn, binary.BigEndian, &num) + if err != nil { + fmt.Println("Error reading from connection:", err) + return + } + + // Write the integer back to the connection + err = binary.Write(conn, binary.BigEndian, num) + if err != nil { + fmt.Println("Error writing to connection:", err) + return + } + + fmt.Println("Received and sent back:", num) + } + +} diff --git a/cmd/testr/testr.go b/cmd/testr/testr.go new file mode 100644 index 0000000..cca5932 --- /dev/null +++ b/cmd/testr/testr.go @@ -0,0 +1,49 @@ +package main + +import ( + "encoding/binary" + "fmt" + "golang.org/x/net/proxy" + "os" +) + +func main() { + // set up a socks5 dialer + dialer, err := proxy.SOCKS5("tcp", "localhost:8882", nil, proxy.Direct) + if err != nil { + fmt.Fprintln(os.Stderr, "can't connect to the proxy:", err) + os.Exit(1) + } + // use the dialer to connect to the server + conn, err := dialer.Dial("tcp", "nprofile1qqs9ntc52tn0app0w7azwpj4s39lnz8h0frnzlhf6mun2ptq9ay36kspzemhxue69uhhyetvv9ujuwpnxvejuumsv93k20v2pva:3338") + if err != nil { + fmt.Fprintln(os.Stderr, "can't connect to the server:", err) + os.Exit(1) + } + counter := int32(0) + + for { + + // Increment the counter + counter++ + + // Write the counter to the connection + err = binary.Write(conn, binary.BigEndian, counter) + if err != nil { + fmt.Println("Error writing to connection:", err) + break + } + + // Read the response from the server + var response int32 + err = binary.Read(conn, binary.BigEndian, &response) + if err != nil { + fmt.Println("Error reading from connection:", err) + break + } + + fmt.Println("Sent:", counter, "Received:", response) + + } + _ = conn.Close() +} diff --git a/coverage.txt b/coverage.txt new file mode 100644 index 0000000..79b28a0 --- /dev/null +++ b/coverage.txt @@ -0,0 +1 @@ +mode: atomic diff --git a/netstr/conn_test.go b/netstr/conn_test.go new file mode 100644 index 0000000..11faab6 --- /dev/null +++ b/netstr/conn_test.go @@ -0,0 +1,125 @@ +package netstr + +import ( + "context" + "github.com/asmogo/nws/protocol" + "github.com/nbd-wtf/go-nostr" + "runtime" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestNostrConnection_Read(t *testing.T) { + tests := []struct { + name string + event protocol.IncomingEvent + nc func() *NostrConnection + wantN int + wantErr bool + }{ + { + name: "Read invalid relay", + event: protocol.IncomingEvent{Relay: nil}, + nc: func() *NostrConnection { + ctx, cancelFunc := context.WithCancel(context.Background()) + return &NostrConnection{ + uuid: uuid.New(), + ctx: ctx, + cancel: cancelFunc, + subscriptionChan: make(chan protocol.IncomingEvent, 1), + privateKey: "788de536151854213cc28dff9c3042e7897f0a1d59b391ddbbc1619d7e716e78", + } + }, + wantN: 0, + wantErr: false, + }, + { + name: "Read", + event: protocol.IncomingEvent{ + Relay: &nostr.Relay{URL: "wss://relay.example.com"}, + Event: &nostr.Event{ + ID: "eventID", + PubKey: "8f97a664471f0b6d599a1e4a781c9a25f39902d96fb462c08df48697bb851611", + Content: "BnHzzyrUhKjDcDPOGfXJDYijUsgxw0hUZq2m+bX5QFI=?iv=NrEqv/jL+SASB2YTjo9i9Q=="}}, + nc: func() *NostrConnection { + ctx, cancelFunc := context.WithCancel(context.Background()) + return &NostrConnection{ + uuid: uuid.New(), + ctx: ctx, + cancel: cancelFunc, + subscriptionChan: make(chan protocol.IncomingEvent, 1), + privateKey: "788de536151854213cc28dff9c3042e7897f0a1d59b391ddbbc1619d7e716e78", + } + }, + wantN: 11, // hello world + wantErr: false, + }, + // Add more cases here to cover more corner situations + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nc := tt.nc() + defer nc.Close() + b := make([]byte, 1024) + nc.subscriptionChan <- tt.event + gotN, err := nc.Read(b) + if (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotN != tt.wantN { + t.Errorf("Read() gotN = %v, want %v", gotN, tt.wantN) + } + }) + } + func() { + // Prevent goroutine leak + for range make([]struct{}, 1000) { + runtime.Gosched() + } + }() +} + +func TestNewConnection(t *testing.T) { + testCases := []struct { + name string + opts []NostrConnOption + expectedID string + }{ + { + name: "NoOptions", + }, + { + name: "WithPrivateKey", + opts: []NostrConnOption{WithPrivateKey("privateKey")}, + }, + { + name: "WithSub", + opts: []NostrConnOption{WithSub(true)}, + }, + { + name: "WithDst", + opts: []NostrConnOption{WithDst("destination")}, + }, + { + name: "WithUUID", + opts: []NostrConnOption{WithUUID(uuid.New())}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + connection := NewConnection(ctx, tc.opts...) + + assert.NotNil(t, connection) + assert.NotNil(t, connection.pool) + assert.NotNil(t, connection.ctx) + assert.NotNil(t, connection.cancel) + assert.NotNil(t, connection.subscriptionChan) + + }) + } +}