refactor: update module path from github.com/gosuda/portal to gosuda.org/portal

- Updated import statements in Go source files to use the new gosuda.org/portal path - Changed repository clone URL in README.md to gosuda.org/portal - Regenerated protobuf marshal and size methods to accommodate the path change and improve type safety with fallbacks for non-VT messages

lemon-mint committed Nov 3, 2025 at 16:17 UTC 077b961dc32b1ec49153fc8ea76d65885680a814
54 files changed +353 -5803
.github/workflows/ci.yml deleted
-237
@@ -1,237 +0,0 @@
1 -name: CI
2 -
3 -on:
4 - push:
5 - branches: [main]
6 - pull_request:
7 - branches: [main, develop, dev]
8 - workflow_dispatch:
9 -
10 -permissions:
11 - contents: read
12 - pull-requests: read
13 -
14 -jobs:
15 - # Lint job - formatters and linters
16 - lint:
17 - name: Lint
18 - runs-on: ubuntu-latest
19 - steps:
20 - - name: Checkout code
21 - uses: actions/checkout@v4
22 -
23 - - name: Set up Go
24 - uses: actions/setup-go@v5
25 - with:
26 - go-version: '1.25.0'
27 - cache: true
28 -
29 - - name: Verify dependencies
30 - run: |
31 - go mod download
32 - go mod verify
33 -
34 - - name: Run gofumpt
35 - run: |
36 - go install mvdan.cc/gofumpt@latest
37 - if [ -n "$(gofumpt -l .)" ]; then
38 - echo "Go code is not formatted with gofumpt:"
39 - gofumpt -d .
40 - exit 1
41 - fi
42 -
43 - - name: Run goimports
44 - run: |
45 - go install golang.org/x/tools/cmd/goimports@latest
46 - if [ -n "$(goimports -local github.com/gosuda/portal -l .)" ]; then
47 - echo "Go imports are not formatted:"
48 - goimports -local github.com/gosuda/portal -d .
49 - exit 1
50 - fi
51 -
52 - - name: Run go vet
53 - run: go vet ./...
54 -
55 - - name: Run golangci-lint
56 - uses: golangci/golangci-lint-action@v8
57 - with:
58 - version: v2.5.0
59 - args: --timeout=5m
60 -
61 - - name: Check go.mod tidiness
62 - run: |
63 - go mod tidy
64 - if ! git diff --exit-code go.mod go.sum; then
65 - echo "go.mod or go.sum is not tidy"
66 - exit 1
67 - fi
68 -
69 -# # Build job - verify binaries compile
70 -# build:
71 -# name: Build
72 -# runs-on: ubuntu-latest
73 -# strategy:
74 -# matrix:
75 -# go-version: ['1.25.0']
76 -# steps:
77 -# - name: Checkout code
78 -# uses: actions/checkout@v4
79 -#
80 -# - name: Set up Go ${{ matrix.go-version }}
81 -# uses: actions/setup-go@v5
82 -# with:
83 -# go-version: ${{ matrix.go-version }}
84 -# cache: true
85 -#
86 -# - name: Download dependencies
87 -# run: go mod download
88 -#
89 -# - name: Build server binary
90 -# run: go build -v -trimpath -o bin/portal-server ./cmd/server
91 -#
92 -# - name: Build example HTTP client
93 -# run: go build -v -trimpath -o bin/portal-client ./sdk/go/examples/http-client
94 -#
95 -# - name: Build example chat client
96 -# run: go build -v -trimpath -o bin/portal-chat ./sdk/go/examples/chat
97 -#
98 -# - name: Upload binaries
99 -# uses: actions/upload-artifact@v4
100 -# if: matrix.go-version == '1.25.0'
101 -# with:
102 -# name: binaries
103 -# path: bin/*
104 -# retention-days: 7
105 -
106 - # # Test job - unit tests with race detection
107 - # test:
108 - # name: Test
109 - # runs-on: ubuntu-latest
110 - # strategy:
111 - # matrix:
112 - # go-version: ['1.25.0', '1.24.x']
113 - # steps:
114 - # - name: Checkout code
115 - # uses: actions/checkout@v4
116 -
117 - # - name: Set up Go ${{ matrix.go-version }}
118 - # uses: actions/setup-go@v5
119 - # with:
120 - # go-version: ${{ matrix.go-version }}
121 - # cache: true
122 -
123 - # - name: Download dependencies
124 - # run: go mod download
125 -
126 - # - name: Run tests
127 - # run: go test -v -race -timeout=5m -coverprofile=coverage.out -covermode=atomic ./...
128 -
129 - # - name: Generate coverage report
130 - # if: matrix.go-version == '1.25.0'
131 - # run: go tool cover -html=coverage.out -o coverage.html
132 -
133 - # - name: Upload coverage report
134 - # uses: actions/upload-artifact@v4
135 - # if: matrix.go-version == '1.25.0'
136 - # with:
137 - # name: coverage-report
138 - # path: coverage.html
139 - # retention-days: 7
140 -
141 - # - name: Check test coverage
142 - # if: matrix.go-version == '1.25.0'
143 - # run: |
144 - # coverage=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
145 - # echo "Total test coverage: ${coverage}%"
146 - # if (( $(echo "$coverage < 30" | bc -l) )); then
147 - # echo "Warning: Test coverage is below 30%"
148 - # fi
149 -
150 - # Sanitizers job - static analysis and security checks
151 - sanitizers:
152 - name: Sanitizers
153 - runs-on: ubuntu-latest
154 - steps:
155 - - name: Checkout code
156 - uses: actions/checkout@v4
157 -
158 - - name: Set up Go
159 - uses: actions/setup-go@v5
160 - with:
161 - go-version: '1.25.0'
162 - cache: true
163 -
164 - - name: Download dependencies
165 - run: go mod download
166 -
167 - - name: Run staticcheck
168 - run: |
169 - go install honnef.co/go/tools/cmd/staticcheck@latest
170 - staticcheck ./...
171 -
172 - - name: Check for ineffective assignments
173 - run: |
174 - go install github.com/gordonklaus/ineffassign@latest
175 - ineffassign ./...
176 -
177 - - name: Check for unused code
178 - run: |
179 - go install honnef.co/go/tools/cmd/staticcheck@latest
180 - staticcheck -checks=U1000 ./...
181 -
182 - # - name: Run gosec (security scanner)
183 - # run: |
184 - # go install github.com/securego/gosec/v2/cmd/gosec@latest
185 - # gosec -fmt=json -out=gosec-report.json ./... || true
186 - # gosec ./...
187 -
188 - # - name: Upload gosec report
189 - # uses: actions/upload-artifact@v4
190 - # with:
191 - # name: gosec-report
192 - # path: gosec-report.json
193 - # retention-days: 7
194 -
195 - # Docker build job
196 - docker:
197 - name: Docker Build
198 - runs-on: ubuntu-latest
199 - steps:
200 - - name: Checkout code
201 - uses: actions/checkout@v4
202 -
203 - - name: Set up Docker Buildx
204 - uses: docker/setup-buildx-action@v3
205 -
206 - - name: Build Docker image
207 - uses: docker/build-push-action@v6
208 - with:
209 - context: .
210 - file: ./Dockerfile
211 - push: false
212 - tags: portal-server:ci
213 - cache-from: type=gha
214 - cache-to: type=gha,mode=max
215 -
216 - # Dependency check job
217 - dependencies:
218 - name: Check Dependencies
219 - runs-on: ubuntu-latest
220 - steps:
221 - - name: Checkout code
222 - uses: actions/checkout@v4
223 -
224 - - name: Set up Go
225 - uses: actions/setup-go@v5
226 - with:
227 - go-version: '1.25.0'
228 - cache: true
229 -
230 - - name: Check for known vulnerabilities
231 - run: |
232 - go install golang.org/x/vuln/cmd/govulncheck@latest
233 - govulncheck ./...
234 -
235 - - name: Check for outdated dependencies
236 - run: |
237 - go list -u -m -json all | jq -r 'select(.Update != null) | "\(.Path): \(.Version) -> \(.Update.Version)"' || true
README.md
+2 -2
@@ -206,7 +206,7 @@ sequenceDiagram
206
207 ```bash
208 # Clone the repository
209 -git clone https://github.com/gosuda/portal.git
209 +git clone https://gosuda.org/portal.git
210 cd portal
211
212 # Build WASM SDK (includes E2EE Proxy Service Worker)
@@ -303,7 +303,7 @@ cd cmd/relay-server
303 package main
304
305 import (
306 - "github.com/gosuda/portal/sdk"
306 + "gosuda.org/portal/sdk"
307 )
308
309 func main() {
cmd/demo-app/main.go
+1 -1
@@ -14,7 +14,7 @@ import (
14 "github.com/rs/zerolog/log"
15 "github.com/spf13/cobra"
16
17 - "github.com/gosuda/portal/sdk"
17 + "gosuda.org/portal/sdk"
18 )
19
20 //go:embed static
cmd/relay-server/main.go
+2 -2
@@ -12,8 +12,8 @@ import (
12 "github.com/rs/zerolog/log"
13 "github.com/spf13/cobra"
14
15 - "github.com/gosuda/portal/portal"
16 - "github.com/gosuda/portal/sdk"
15 + "gosuda.org/portal/portal"
16 + "gosuda.org/portal/sdk"
17 )
18
19 var rootCmd = &cobra.Command{
cmd/relay-server/view.go
+3 -3
@@ -19,9 +19,9 @@ import (
19 "github.com/gorilla/websocket"
20 "github.com/rs/zerolog/log"
21
22 - "github.com/gosuda/portal/portal"
23 - "github.com/gosuda/portal/portal/utils/wsstream"
24 - "github.com/gosuda/portal/sdk"
22 + "gosuda.org/portal/portal"
23 + "gosuda.org/portal/portal/utils/wsstream"
24 + "gosuda.org/portal/sdk"
25 )
26
27 //go:embed static
cmd/webclient/httpjs/http_js.go
+1 -1
@@ -10,7 +10,7 @@ import (
10 "strings"
11 "syscall/js"
12
13 - "github.com/gosuda/portal/cmd/webclient/streamjs"
13 + "gosuda.org/portal/cmd/webclient/streamjs"
14 )
15
16 var (
cmd/webclient/main_js.go
+2 -2
@@ -20,11 +20,11 @@ import (
20 "time"
21
22 "github.com/gorilla/websocket"
23 - "github.com/gosuda/portal/cmd/webclient/httpjs"
24 - "github.com/gosuda/portal/sdk"
23 "github.com/rs/zerolog"
24 "github.com/rs/zerolog/log"
25 "golang.org/x/net/idna"
26 + "gosuda.org/portal/cmd/webclient/httpjs"
27 + "gosuda.org/portal/sdk"
28 )
29
30 var (
cmd/webclient/sdk_js.go
+1 -1
@@ -4,7 +4,7 @@ import (
4 "context"
5 "io"
6
7 - "github.com/gosuda/portal/cmd/webclient/wsjs"
7 + "gosuda.org/portal/cmd/webclient/wsjs"
8 )
9
10 // WebSocketDialerJS creates a WebSocket dialer function for JavaScript/WebAssembly environment
go.mod
+4 -4
@@ -1,4 +1,4 @@
1 -module github.com/gosuda/portal
1 +module gosuda.org/portal
2
3 go 1.25.3
4
@@ -16,9 +16,9 @@ require (
16
17 require (
18 github.com/inconshreveable/mousetrap v1.1.0 // indirect
19 - github.com/mattn/go-colorable v0.1.13 // indirect
20 - github.com/mattn/go-isatty v0.0.19 // indirect
21 - github.com/spf13/pflag v1.0.9 // indirect
19 + github.com/mattn/go-colorable v0.1.14 // indirect
20 + github.com/mattn/go-isatty v0.0.20 // indirect
21 + github.com/spf13/pflag v1.0.10 // indirect
22 golang.org/x/sys v0.37.0 // indirect
23 golang.org/x/text v0.30.0 // indirect
24 )
go.sum
+6 -3
@@ -9,11 +9,13 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
9 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
10 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
11 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
12 -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
12 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
13 +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
14 +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
15 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
15 -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
16 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
17 +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
18 +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
19 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
20 github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
21 github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
@@ -23,8 +25,9 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6
25 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
26 github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
27 github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
26 -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
28 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
29 +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
30 +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
31 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
32 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
33 golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
portal/client.go
+3 -3
@@ -7,11 +7,11 @@ import (
7 "sync"
8 "time"
9
10 - "github.com/gosuda/portal/portal/core/cryptoops"
11 - "github.com/gosuda/portal/portal/core/proto/rdsec"
12 - "github.com/gosuda/portal/portal/core/proto/rdverb"
10 "github.com/hashicorp/yamux"
11 "github.com/rs/zerolog/log"
12 + "gosuda.org/portal/portal/core/cryptoops"
13 + "gosuda.org/portal/portal/core/proto/rdsec"
14 + "gosuda.org/portal/portal/core/proto/rdverb"
15 )
16
17 var (
portal/core/cryptoops/README.md
+1 -1
@@ -383,7 +383,7 @@ func releaseBuffer(buffer *bytebufferpool.ByteBuffer) {
383 Cryptographically secure random numbers are critical:
384
385 ```go
386 -import "github.com/gosuda/portal/portal/internal/randpool"
386 +import "gosuda.org/portal/portal/internal/randpool"
387
388 // Generate random nonce
389 nonce := make([]byte, nonceSize)
portal/core/cryptoops/handshaker.go
+2 -2
@@ -18,9 +18,9 @@ import (
18 "golang.org/x/crypto/hkdf"
19 "google.golang.org/protobuf/proto"
20
21 - "github.com/gosuda/portal/portal/core/proto/rdsec"
22 - "github.com/gosuda/portal/portal/utils/randpool"
21 "github.com/valyala/bytebufferpool"
22 + "gosuda.org/portal/portal/core/proto/rdsec"
23 + "gosuda.org/portal/portal/utils/randpool"
24 )
25
26 var _lengthBufferPool = sync.Pool{
portal/core/cryptoops/handshaker_test.go
+1 -1
@@ -9,8 +9,8 @@ import (
9 "testing"
10 "time"
11
12 - "github.com/gosuda/portal/portal/core/proto/rdsec"
12 "golang.org/x/crypto/curve25519"
13 + "gosuda.org/portal/portal/core/proto/rdsec"
14 )
15
16 // pipeConn creates a bidirectional pipe for testing using TCP loopback
portal/core/cryptoops/identity.go
+1 -1
@@ -4,7 +4,7 @@ import (
4 "crypto/ed25519"
5 "errors"
6
7 - "github.com/gosuda/portal/portal/core/proto/rdsec"
7 + "gosuda.org/portal/portal/core/proto/rdsec"
8 )
9
10 func ValidateIdentity(identity *rdsec.Identity) bool {
portal/core/proto/rdsec/rdsec.pb.go
+4 -2
@@ -1,7 +1,7 @@
1 // Code generated by protoc-gen-go. DO NOT EDIT.
2 // versions:
3 // protoc-gen-go v1.36.10
4 -// protoc v3.21.12
4 +// protoc (unknown)
5 // source: portal/core/proto/rdsec/rdsec.proto
6
7 package rdsec
@@ -363,7 +363,9 @@ const file_portal_core_proto_rdsec_rdsec_proto_rawDesc = "" +
363 "\x04alpn\x18\x05 \x01(\tR\x04alpn\x12,\n" +
364 "\x12session_public_key\x18\x06 \x01(\fR\x10sessionPublicKey*)\n" +
365 "\x0fProtocolVersion\x12\x16\n" +
366 - "\x12PROTOCOL_VERSION_1\x10\x00B8Z6github.com/gosuda/portal/portal/core/proto/rdsec;rdsecb\x06proto3"
366 + "\x12PROTOCOL_VERSION_1\x10\x00B|\n" +
367 + "\tcom.rdsecB\n" +
368 + "RdsecProtoP\x01Z/gosuda.org/portal/portal/core/proto/rdsec;rdsec\xa2\x02\x03RXX\xaa\x02\x05Rdsec\xca\x02\x05Rdsec\xe2\x02\x11Rdsec\\GPBMetadata\xea\x02\x05Rdsecb\x06proto3"
369
370 var (
371 file_portal_core_proto_rdsec_rdsec_proto_rawDescOnce sync.Once
portal/core/proto/rdsec/rdsec.proto
+1 -1
@@ -2,7 +2,7 @@ syntax = "proto3";
2
3 package rdsec;
4
5 -option go_package = "github.com/gosuda/portal/portal/core/proto/rdsec;rdsec";
5 +option go_package = "gosuda.org/portal/portal/core/proto/rdsec;rdsec";
6
7 message Identity {
8 string id = 1;
portal/core/proto/rdverb/rdverb.pb.go
+5 -3
@@ -1,15 +1,15 @@
1 // Code generated by protoc-gen-go. DO NOT EDIT.
2 // versions:
3 // protoc-gen-go v1.36.10
4 -// protoc v3.21.12
4 +// protoc (unknown)
5 // source: portal/core/proto/rdverb/rdverb.proto
6
7 package rdverb
8
9 import (
10 - rdsec "github.com/gosuda/portal/portal/core/proto/rdsec"
10 protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 + rdsec "gosuda.org/portal/portal/core/proto/rdsec"
13 reflect "reflect"
14 sync "sync"
15 unsafe "unsafe"
@@ -766,7 +766,9 @@ const file_portal_core_proto_rdverb_rdverb_proto_rawDesc = "" +
766 "\x1eRESPONSE_CODE_INVALID_IDENTITY\x10\x03\x12\x1e\n" +
767 "\x1aRESPONSE_CODE_INVALID_NAME\x10\x04\x12\x1e\n" +
768 "\x1aRESPONSE_CODE_INVALID_ALPN\x10\x05\x12\x1a\n" +
769 - "\x16RESPONSE_CODE_REJECTED\x10\x06B:Z8github.com/gosuda/portal/portal/core/proto/rdverb;rdverbb\x06proto3"
769 + "\x16RESPONSE_CODE_REJECTED\x10\x06B\x84\x01\n" +
770 + "\n" +
771 + "com.rdverbB\vRdverbProtoP\x01Z1gosuda.org/portal/portal/core/proto/rdverb;rdverb\xa2\x02\x03RXX\xaa\x02\x06Rdverb\xca\x02\x06Rdverb\xe2\x02\x12Rdverb\\GPBMetadata\xea\x02\x06Rdverbb\x06proto3"
772
773 var (
774 file_portal_core_proto_rdverb_rdverb_proto_rawDescOnce sync.Once
portal/core/proto/rdverb/rdverb.proto
+1 -1
@@ -4,7 +4,7 @@ package rdverb;
4
5 import "portal/core/proto/rdsec/rdsec.proto";
6
7 -option go_package = "github.com/gosuda/portal/portal/core/proto/rdverb;rdverb";
7 +option go_package = "gosuda.org/portal/portal/core/proto/rdverb;rdverb";
8
9 enum PacketType {
10 PACKET_TYPE_RELAY_INFO_REQUEST = 0;
portal/core/proto/rdverb/rdverb_vtproto.pb.go
+293 -69
@@ -6,10 +6,10 @@ package rdverb
6
7 import (
8 fmt "fmt"
9 - rdsec "github.com/gosuda/portal/portal/core/proto/rdsec"
9 protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
10 proto "google.golang.org/protobuf/proto"
11 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 + rdsec "gosuda.org/portal/portal/core/proto/rdsec"
13 io "io"
14 unsafe "unsafe"
15 )
@@ -48,7 +48,13 @@ func (m *RelayInfo) CloneVT() *RelayInfo {
48 return (*RelayInfo)(nil)
49 }
50 r := new(RelayInfo)
51 - r.Identity = m.Identity.CloneVT()
51 + if rhs := m.Identity; rhs != nil {
52 + if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *rdsec.Identity }); ok {
53 + r.Identity = vtpb.CloneVT()
54 + } else {
55 + r.Identity = proto.Clone(rhs).(*rdsec.Identity)
56 + }
57 + }
58 if rhs := m.Address; rhs != nil {
59 tmpContainer := make([]string, len(rhs))
60 copy(tmpContainer, rhs)
@@ -108,9 +114,15 @@ func (m *Lease) CloneVT() *Lease {
114 return (*Lease)(nil)
115 }
116 r := new(Lease)
111 - r.Identity = m.Identity.CloneVT()
117 r.Expires = m.Expires
118 r.Name = m.Name
119 + if rhs := m.Identity; rhs != nil {
120 + if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *rdsec.Identity }); ok {
121 + r.Identity = vtpb.CloneVT()
122 + } else {
123 + r.Identity = proto.Clone(rhs).(*rdsec.Identity)
124 + }
125 + }
126 if rhs := m.Alpn; rhs != nil {
127 tmpContainer := make([]string, len(rhs))
128 copy(tmpContainer, rhs)
@@ -172,8 +184,14 @@ func (m *LeaseDeleteRequest) CloneVT() *LeaseDeleteRequest {
184 return (*LeaseDeleteRequest)(nil)
185 }
186 r := new(LeaseDeleteRequest)
175 - r.Identity = m.Identity.CloneVT()
187 r.Timestamp = m.Timestamp
188 + if rhs := m.Identity; rhs != nil {
189 + if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *rdsec.Identity }); ok {
190 + r.Identity = vtpb.CloneVT()
191 + } else {
192 + r.Identity = proto.Clone(rhs).(*rdsec.Identity)
193 + }
194 + }
195 if rhs := m.Nonce; rhs != nil {
196 tmpBytes := make([]byte, len(rhs))
197 copy(tmpBytes, rhs)
@@ -213,7 +231,13 @@ func (m *ConnectionRequest) CloneVT() *ConnectionRequest {
231 }
232 r := new(ConnectionRequest)
233 r.LeaseId = m.LeaseId
216 - r.ClientIdentity = m.ClientIdentity.CloneVT()
234 + if rhs := m.ClientIdentity; rhs != nil {
235 + if vtpb, ok := interface{}(rhs).(interface{ CloneVT() *rdsec.Identity }); ok {
236 + r.ClientIdentity = vtpb.CloneVT()
237 + } else {
238 + r.ClientIdentity = proto.Clone(rhs).(*rdsec.Identity)
239 + }
240 + }
241 if len(m.unknownFields) > 0 {
242 r.unknownFields = make([]byte, len(m.unknownFields))
243 copy(r.unknownFields, m.unknownFields)
@@ -270,7 +294,11 @@ func (this *RelayInfo) EqualVT(that *RelayInfo) bool {
294 } else if this == nil || that == nil {
295 return false
296 }
273 - if !this.Identity.EqualVT(that.Identity) {
297 + if equal, ok := interface{}(this.Identity).(interface{ EqualVT(*rdsec.Identity) bool }); ok {
298 + if !equal.EqualVT(that.Identity) {
299 + return false
300 + }
301 + } else if !proto.Equal(this.Identity, that.Identity) {
302 return false
303 }
304 if len(this.Address) != len(that.Address) {
@@ -342,7 +370,11 @@ func (this *Lease) EqualVT(that *Lease) bool {
370 } else if this == nil || that == nil {
371 return false
372 }
345 - if !this.Identity.EqualVT(that.Identity) {
373 + if equal, ok := interface{}(this.Identity).(interface{ EqualVT(*rdsec.Identity) bool }); ok {
374 + if !equal.EqualVT(that.Identity) {
375 + return false
376 + }
377 + } else if !proto.Equal(this.Identity, that.Identity) {
378 return false
379 }
380 if this.Expires != that.Expires {
@@ -420,7 +452,11 @@ func (this *LeaseDeleteRequest) EqualVT(that *LeaseDeleteRequest) bool {
452 } else if this == nil || that == nil {
453 return false
454 }
423 - if !this.Identity.EqualVT(that.Identity) {
455 + if equal, ok := interface{}(this.Identity).(interface{ EqualVT(*rdsec.Identity) bool }); ok {
456 + if !equal.EqualVT(that.Identity) {
457 + return false
458 + }
459 + } else if !proto.Equal(this.Identity, that.Identity) {
460 return false
461 }
462 if string(this.Nonce) != string(that.Nonce) {
@@ -467,7 +503,11 @@ func (this *ConnectionRequest) EqualVT(that *ConnectionRequest) bool {
503 if this.LeaseId != that.LeaseId {
504 return false
505 }
470 - if !this.ClientIdentity.EqualVT(that.ClientIdentity) {
506 + if equal, ok := interface{}(this.ClientIdentity).(interface{ EqualVT(*rdsec.Identity) bool }); ok {
507 + if !equal.EqualVT(that.ClientIdentity) {
508 + return false
509 + }
510 + } else if !proto.Equal(this.ClientIdentity, that.ClientIdentity) {
511 return false
512 }
513 return string(this.unknownFields) == string(that.unknownFields)
@@ -593,12 +633,24 @@ func (m *RelayInfo) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
633 }
634 }
635 if m.Identity != nil {
596 - size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
597 - if err != nil {
598 - return 0, err
636 + if vtmsg, ok := interface{}(m.Identity).(interface {
637 + MarshalToSizedBufferVT([]byte) (int, error)
638 + }); ok {
639 + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i])
640 + if err != nil {
641 + return 0, err
642 + }
643 + i -= size
644 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
645 + } else {
646 + encoded, err := proto.Marshal(m.Identity)
647 + if err != nil {
648 + return 0, err
649 + }
650 + i -= len(encoded)
651 + copy(dAtA[i:], encoded)
652 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
653 }
600 - i -= size
601 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
654 i--
655 dAtA[i] = 0xa
656 }
@@ -733,12 +785,24 @@ func (m *Lease) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
785 dAtA[i] = 0x10
786 }
787 if m.Identity != nil {
736 - size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
737 - if err != nil {
738 - return 0, err
788 + if vtmsg, ok := interface{}(m.Identity).(interface {
789 + MarshalToSizedBufferVT([]byte) (int, error)
790 + }); ok {
791 + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i])
792 + if err != nil {
793 + return 0, err
794 + }
795 + i -= size
796 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
797 + } else {
798 + encoded, err := proto.Marshal(m.Identity)
799 + if err != nil {
800 + return 0, err
801 + }
802 + i -= len(encoded)
803 + copy(dAtA[i:], encoded)
804 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
805 }
740 - i -= size
741 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
806 i--
807 dAtA[i] = 0xa
808 }
@@ -881,12 +945,24 @@ func (m *LeaseDeleteRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
945 dAtA[i] = 0x12
946 }
947 if m.Identity != nil {
884 - size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
885 - if err != nil {
886 - return 0, err
948 + if vtmsg, ok := interface{}(m.Identity).(interface {
949 + MarshalToSizedBufferVT([]byte) (int, error)
950 + }); ok {
951 + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i])
952 + if err != nil {
953 + return 0, err
954 + }
955 + i -= size
956 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
957 + } else {
958 + encoded, err := proto.Marshal(m.Identity)
959 + if err != nil {
960 + return 0, err
961 + }
962 + i -= len(encoded)
963 + copy(dAtA[i:], encoded)
964 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
965 }
888 - i -= size
889 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
966 i--
967 dAtA[i] = 0xa
968 }
@@ -962,12 +1038,24 @@ func (m *ConnectionRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
1038 copy(dAtA[i:], m.unknownFields)
1039 }
1040 if m.ClientIdentity != nil {
965 - size, err := m.ClientIdentity.MarshalToSizedBufferVT(dAtA[:i])
966 - if err != nil {
967 - return 0, err
1041 + if vtmsg, ok := interface{}(m.ClientIdentity).(interface {
1042 + MarshalToSizedBufferVT([]byte) (int, error)
1043 + }); ok {
1044 + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i])
1045 + if err != nil {
1046 + return 0, err
1047 + }
1048 + i -= size
1049 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1050 + } else {
1051 + encoded, err := proto.Marshal(m.ClientIdentity)
1052 + if err != nil {
1053 + return 0, err
1054 + }
1055 + i -= len(encoded)
1056 + copy(dAtA[i:], encoded)
1057 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
1058 }
969 - i -= size
970 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1059 i--
1060 dAtA[i] = 0x12
1061 }
@@ -1113,12 +1201,24 @@ func (m *RelayInfo) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1201 }
1202 }
1203 if m.Identity != nil {
1116 - size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1117 - if err != nil {
1118 - return 0, err
1204 + if vtmsg, ok := interface{}(m.Identity).(interface {
1205 + MarshalToSizedBufferVTStrict([]byte) (int, error)
1206 + }); ok {
1207 + size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
1208 + if err != nil {
1209 + return 0, err
1210 + }
1211 + i -= size
1212 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1213 + } else {
1214 + encoded, err := proto.Marshal(m.Identity)
1215 + if err != nil {
1216 + return 0, err
1217 + }
1218 + i -= len(encoded)
1219 + copy(dAtA[i:], encoded)
1220 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
1221 }
1120 - i -= size
1121 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1222 i--
1223 dAtA[i] = 0xa
1224 }
@@ -1253,12 +1353,24 @@ func (m *Lease) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1353 dAtA[i] = 0x10
1354 }
1355 if m.Identity != nil {
1256 - size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1257 - if err != nil {
1258 - return 0, err
1356 + if vtmsg, ok := interface{}(m.Identity).(interface {
1357 + MarshalToSizedBufferVTStrict([]byte) (int, error)
1358 + }); ok {
1359 + size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
1360 + if err != nil {
1361 + return 0, err
1362 + }
1363 + i -= size
1364 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1365 + } else {
1366 + encoded, err := proto.Marshal(m.Identity)
1367 + if err != nil {
1368 + return 0, err
1369 + }
1370 + i -= len(encoded)
1371 + copy(dAtA[i:], encoded)
1372 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
1373 }
1260 - i -= size
1261 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1374 i--
1375 dAtA[i] = 0xa
1376 }
@@ -1401,12 +1513,24 @@ func (m *LeaseDeleteRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, err
1513 dAtA[i] = 0x12
1514 }
1515 if m.Identity != nil {
1404 - size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1405 - if err != nil {
1406 - return 0, err
1516 + if vtmsg, ok := interface{}(m.Identity).(interface {
1517 + MarshalToSizedBufferVTStrict([]byte) (int, error)
1518 + }); ok {
1519 + size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
1520 + if err != nil {
1521 + return 0, err
1522 + }
1523 + i -= size
1524 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1525 + } else {
1526 + encoded, err := proto.Marshal(m.Identity)
1527 + if err != nil {
1528 + return 0, err
1529 + }
1530 + i -= len(encoded)
1531 + copy(dAtA[i:], encoded)
1532 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
1533 }
1408 - i -= size
1409 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1534 i--
1535 dAtA[i] = 0xa
1536 }
@@ -1482,12 +1606,24 @@ func (m *ConnectionRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, erro
1606 copy(dAtA[i:], m.unknownFields)
1607 }
1608 if m.ClientIdentity != nil {
1485 - size, err := m.ClientIdentity.MarshalToSizedBufferVTStrict(dAtA[:i])
1486 - if err != nil {
1487 - return 0, err
1609 + if vtmsg, ok := interface{}(m.ClientIdentity).(interface {
1610 + MarshalToSizedBufferVTStrict([]byte) (int, error)
1611 + }); ok {
1612 + size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i])
1613 + if err != nil {
1614 + return 0, err
1615 + }
1616 + i -= size
1617 + i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1618 + } else {
1619 + encoded, err := proto.Marshal(m.ClientIdentity)
1620 + if err != nil {
1621 + return 0, err
1622 + }
1623 + i -= len(encoded)
1624 + copy(dAtA[i:], encoded)
1625 + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded)))
1626 }
1489 - i -= size
1490 - i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1627 i--
1628 dAtA[i] = 0x12
1629 }
@@ -1563,7 +1699,13 @@ func (m *RelayInfo) SizeVT() (n int) {
1699 var l int
1700 _ = l
1701 if m.Identity != nil {
1566 - l = m.Identity.SizeVT()
1702 + if size, ok := interface{}(m.Identity).(interface {
1703 + SizeVT() int
1704 + }); ok {
1705 + l = size.SizeVT()
1706 + } else {
1707 + l = proto.Size(m.Identity)
1708 + }
1709 n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1710 }
1711 if len(m.Address) > 0 {
@@ -1613,7 +1755,13 @@ func (m *Lease) SizeVT() (n int) {
1755 var l int
1756 _ = l
1757 if m.Identity != nil {
1616 - l = m.Identity.SizeVT()
1758 + if size, ok := interface{}(m.Identity).(interface {
1759 + SizeVT() int
1760 + }); ok {
1761 + l = size.SizeVT()
1762 + } else {
1763 + l = proto.Size(m.Identity)
1764 + }
1765 n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1766 }
1767 if m.Expires != 0 {
@@ -1674,7 +1822,13 @@ func (m *LeaseDeleteRequest) SizeVT() (n int) {
1822 var l int
1823 _ = l
1824 if m.Identity != nil {
1677 - l = m.Identity.SizeVT()
1825 + if size, ok := interface{}(m.Identity).(interface {
1826 + SizeVT() int
1827 + }); ok {
1828 + l = size.SizeVT()
1829 + } else {
1830 + l = proto.Size(m.Identity)
1831 + }
1832 n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1833 }
1834 l = len(m.Nonce)
@@ -1712,7 +1866,13 @@ func (m *ConnectionRequest) SizeVT() (n int) {
1866 n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1867 }
1868 if m.ClientIdentity != nil {
1715 - l = m.ClientIdentity.SizeVT()
1869 + if size, ok := interface{}(m.ClientIdentity).(interface {
1870 + SizeVT() int
1871 + }); ok {
1872 + l = size.SizeVT()
1873 + } else {
1874 + l = proto.Size(m.ClientIdentity)
1875 + }
1876 n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1877 }
1878 n += len(m.unknownFields)
@@ -1897,8 +2057,16 @@ func (m *RelayInfo) UnmarshalVT(dAtA []byte) error {
2057 if m.Identity == nil {
2058 m.Identity = &rdsec.Identity{}
2059 }
1900 - if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
1901 - return err
2060 + if unmarshal, ok := interface{}(m.Identity).(interface {
2061 + UnmarshalVT([]byte) error
2062 + }); ok {
2063 + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2064 + return err
2065 + }
2066 + } else {
2067 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
2068 + return err
2069 + }
2070 }
2071 iNdEx = postIndex
2072 case 2:
@@ -2186,8 +2354,16 @@ func (m *Lease) UnmarshalVT(dAtA []byte) error {
2354 if m.Identity == nil {
2355 m.Identity = &rdsec.Identity{}
2356 }
2189 - if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2190 - return err
2357 + if unmarshal, ok := interface{}(m.Identity).(interface {
2358 + UnmarshalVT([]byte) error
2359 + }); ok {
2360 + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2361 + return err
2362 + }
2363 + } else {
2364 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
2365 + return err
2366 + }
2367 }
2368 iNdEx = postIndex
2369 case 2:
@@ -2566,8 +2742,16 @@ func (m *LeaseDeleteRequest) UnmarshalVT(dAtA []byte) error {
2742 if m.Identity == nil {
2743 m.Identity = &rdsec.Identity{}
2744 }
2569 - if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2570 - return err
2745 + if unmarshal, ok := interface{}(m.Identity).(interface {
2746 + UnmarshalVT([]byte) error
2747 + }); ok {
2748 + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2749 + return err
2750 + }
2751 + } else {
2752 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
2753 + return err
2754 + }
2755 }
2756 iNdEx = postIndex
2757 case 2:
@@ -2808,8 +2992,16 @@ func (m *ConnectionRequest) UnmarshalVT(dAtA []byte) error {
2992 if m.ClientIdentity == nil {
2993 m.ClientIdentity = &rdsec.Identity{}
2994 }
2811 - if err := m.ClientIdentity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2812 - return err
2995 + if unmarshal, ok := interface{}(m.ClientIdentity).(interface {
2996 + UnmarshalVT([]byte) error
2997 + }); ok {
2998 + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2999 + return err
3000 + }
3001 + } else {
3002 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ClientIdentity); err != nil {
3003 + return err
3004 + }
3005 }
3006 iNdEx = postIndex
3007 default:
@@ -3066,8 +3258,16 @@ func (m *RelayInfo) UnmarshalVTUnsafe(dAtA []byte) error {
3258 if m.Identity == nil {
3259 m.Identity = &rdsec.Identity{}
3260 }
3069 - if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3070 - return err
3261 + if unmarshal, ok := interface{}(m.Identity).(interface {
3262 + UnmarshalVTUnsafe([]byte) error
3263 + }); ok {
3264 + if err := unmarshal.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3265 + return err
3266 + }
3267 + } else {
3268 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
3269 + return err
3270 + }
3271 }
3272 iNdEx = postIndex
3273 case 2:
@@ -3363,8 +3563,16 @@ func (m *Lease) UnmarshalVTUnsafe(dAtA []byte) error {
3563 if m.Identity == nil {
3564 m.Identity = &rdsec.Identity{}
3565 }
3366 - if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3367 - return err
3566 + if unmarshal, ok := interface{}(m.Identity).(interface {
3567 + UnmarshalVTUnsafe([]byte) error
3568 + }); ok {
3569 + if err := unmarshal.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3570 + return err
3571 + }
3572 + } else {
3573 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
3574 + return err
3575 + }
3576 }
3577 iNdEx = postIndex
3578 case 2:
@@ -3748,8 +3956,16 @@ func (m *LeaseDeleteRequest) UnmarshalVTUnsafe(dAtA []byte) error {
3956 if m.Identity == nil {
3957 m.Identity = &rdsec.Identity{}
3958 }
3751 - if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3752 - return err
3959 + if unmarshal, ok := interface{}(m.Identity).(interface {
3960 + UnmarshalVTUnsafe([]byte) error
3961 + }); ok {
3962 + if err := unmarshal.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3963 + return err
3964 + }
3965 + } else {
3966 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Identity); err != nil {
3967 + return err
3968 + }
3969 }
3970 iNdEx = postIndex
3971 case 2:
@@ -3991,8 +4207,16 @@ func (m *ConnectionRequest) UnmarshalVTUnsafe(dAtA []byte) error {
4207 if m.ClientIdentity == nil {
4208 m.ClientIdentity = &rdsec.Identity{}
4209 }
3994 - if err := m.ClientIdentity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3995 - return err
4210 + if unmarshal, ok := interface{}(m.ClientIdentity).(interface {
4211 + UnmarshalVTUnsafe([]byte) error
4212 + }); ok {
4213 + if err := unmarshal.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
4214 + return err
4215 + }
4216 + } else {
4217 + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ClientIdentity); err != nil {
4218 + return err
4219 + }
4220 }
4221 iNdEx = postIndex
4222 default:
portal/handlers.go
+3 -3
@@ -5,12 +5,12 @@ import (
5 "io"
6 "sync"
7
8 - "github.com/gosuda/portal/portal/core/cryptoops"
9 - "github.com/gosuda/portal/portal/core/proto/rdsec"
10 - "github.com/gosuda/portal/portal/core/proto/rdverb"
8 "github.com/hashicorp/yamux"
9 "github.com/rs/zerolog/log"
10 "github.com/valyala/bytebufferpool"
11 + "gosuda.org/portal/portal/core/cryptoops"
12 + "gosuda.org/portal/portal/core/proto/rdsec"
13 + "gosuda.org/portal/portal/core/proto/rdverb"
14 )
15
16 type StreamContext struct {
portal/helper.go
+1 -1
@@ -4,8 +4,8 @@ import (
4 "encoding/binary"
5 "io"
6
7 - "github.com/gosuda/portal/portal/core/proto/rdverb"
7 "github.com/valyala/bytebufferpool"
8 + "gosuda.org/portal/portal/core/proto/rdverb"
9 )
10
11 func bufferGrow(buffer *bytebufferpool.ByteBuffer, n int) {
portal/lease.go
+2 -2
@@ -4,8 +4,8 @@ import (
4 "sync"
5 "time"
6
7 - "github.com/gosuda/portal/portal/core/proto/rdsec"
8 - "github.com/gosuda/portal/portal/core/proto/rdverb"
7 + "gosuda.org/portal/portal/core/proto/rdsec"
8 + "gosuda.org/portal/portal/core/proto/rdverb"
9 )
10
11 type LeaseEntry struct {
portal/lease_test.go
+2 -2
@@ -4,8 +4,8 @@ import (
4 "testing"
5 "time"
6
7 - "github.com/gosuda/portal/portal/core/proto/rdsec"
8 - "github.com/gosuda/portal/portal/core/proto/rdverb"
7 + "gosuda.org/portal/portal/core/proto/rdsec"
8 + "gosuda.org/portal/portal/core/proto/rdverb"
9 )
10
11 func TestLeaseManager_NameConflict(t *testing.T) {
portal/relay.go
+3 -3
@@ -5,11 +5,11 @@ import (
5 "sync"
6 "time"
7
8 - "github.com/gosuda/portal/portal/core/cryptoops"
9 - "github.com/gosuda/portal/portal/core/proto/rdsec"
10 - "github.com/gosuda/portal/portal/core/proto/rdverb"
8 "github.com/hashicorp/yamux"
9 "github.com/rs/zerolog/log"
10 + "gosuda.org/portal/portal/core/cryptoops"
11 + "gosuda.org/portal/portal/core/proto/rdsec"
12 + "gosuda.org/portal/portal/core/proto/rdverb"
13 )
14
15 type Connection struct {
portal/wasm/.cargo/config.toml deleted
-8
@@ -1,8 +0,0 @@
1 -[build]
2 -target = "wasm32-unknown-unknown"
3 -
4 -[target.wasm32-unknown-unknown]
5 -rustflags = [
6 - "--cfg", "wasm_js",
7 - "--cfg", "getrandom_backend=\"wasm_js\""
8 -]
portal/wasm/.gitignore deleted
-21
@@ -1,21 +0,0 @@
1 -/target
2 -/pkg
3 -/pkg-node
4 -Cargo.lock
5 -
6 -# Ignore build artifacts
7 -*.wasm
8 -
9 -# Ignore generated JS, but keep examples and service workers
10 -*.js
11 -!examples/*.js
12 -!sw.js
13 -!sw-proxy.js
14 -!secure-websocket.js
15 -!secure-websocket-sw.js
16 -
17 -# Keep HTML files
18 -!*.html
19 -
20 -.DS_Store
21 -node_modules/
portal/wasm/Cargo.toml deleted
-73
@@ -1,73 +0,0 @@
1 -[package]
2 -name = "portal-wasm"
3 -version = "0.1.0"
4 -edition = "2021"
5 -authors = ["Portal Contributors"]
6 -description = "WebAssembly client for Portal"
7 -license = "MIT OR Apache-2.0"
8 -
9 -[lib]
10 -crate-type = ["cdylib", "rlib"]
11 -
12 -[dependencies]
13 -# WASM bindings
14 -wasm-bindgen = "0.2.104"
15 -wasm-bindgen-futures = "0.4.54"
16 -js-sys = "0.3.81"
17 -web-sys = { version = "0.3", features = [
18 - "WebSocket",
19 - "MessageEvent",
20 - "BinaryType",
21 - "ErrorEvent",
22 - "CloseEvent",
23 - "Window",
24 - "Request",
25 - "RequestInit",
26 - "Response",
27 - "Headers",
28 - "Blob",
29 - "FormData",
30 -] }
31 -
32 -# Async runtime
33 -futures = { version = "0.3", default-features = false, features = ["alloc", "async-await", "std"] }
34 -tokio = { version = "1.48.0", default-features = false, features = [
35 - "sync",
36 - "macros",
37 -] }
38 -
39 -# Serialization
40 -serde = { version = "1.0.228", features = ["derive"] }
41 -serde-wasm-bindgen = "0.6.5"
42 -serde_json = "1.0.145"
43 -
44 -# Protobuf
45 -prost = "0.14.1"
46 -bytes = "1.10.1"
47 -
48 -# Crypto
49 -ed25519-dalek = { version = "2.2.0", features = ["rand_core"] }
50 -x25519-dalek = { version = "2.0.1", features = ["static_secrets"] }
51 -chacha20poly1305 = "0.10.1"
52 -hkdf = "0.12.4"
53 -sha2 = "0.10.9"
54 -getrandom = { version = "0.2", features = ["js"] }
55 -rand_core = { version = "0.6", default-features = false }
56 -
57 -# Simple multiplexer implementation for WASM (yamux has getrandom 0.3 dependency issues)
58 -
59 -# Utilities
60 -parking_lot = "0.12.5"
61 -thiserror = "2.0.17"
62 -anyhow = "1.0.100"
63 -log = "0.4.28"
64 -hex = "0.4.3"
65 -console_error_panic_hook = "0.1.7"
66 -wee_alloc = "0.4.5"
67 -
68 -
69 -[build-dependencies]
70 -prost-build = "0.14.1"
71 -
72 -[dev-dependencies]
73 -wasm-bindgen-test = "0.3.54"
portal/wasm/E2EE_PROXY_INTEGRATION.md deleted
-416
@@ -1,416 +0,0 @@
1 -# E2EE Proxy Integration Guide
2 -
3 -## Overview
4 -
5 -Portal provides **mandatory E2EE (End-to-End Encryption) proxy** functionality that automatically encrypts all network traffic through relay servers.
6 -
7 -## Architecture
8 -
9 -```
10 -┌──────────────────┐
11 -│ Browser │
12 -│ Application │
13 -└────────┬─────────┘
14 - │ fetch()
15 - ▼
16 -┌────────────────────────────────┐
17 -│ Service Worker (sw-proxy.js) │ ← Intercepts ALL requests
18 -│ │
19 -│ WASM ProxyEngine │ ← E2EE encryption
20 -└────────┬───────────────────────┘
21 - │ E2EE WebSocket
22 - ▼
23 -┌────────────────────────────────┐
24 -│ Relay Server │ ← Relay only (no decrypt)
25 -│ /relay endpoint │
26 -└────────┬───────────────────────┘
27 - │ E2EE Tunnel
28 - ▼
29 -┌────────────────────────────────┐
30 -│ Target Peer │ ← Decrypts and processes
31 -└────────────────────────────────┘
32 -```
33 -
34 -## Components
35 -
36 -### 1. Server (Go)
37 -
38 -**File:** `cmd/relay-server/view.go`
39 -
40 -```go
41 -// Embedded WASM files
42 -//go:embed wasm
43 -var wasmFS embed.FS
44 -
45 -// Routes
46 -mux.Handle("/pkg/", ...) // WASM binaries
47 -mux.HandleFunc("/sw-proxy.js", ...) // Service Worker
48 -mux.HandleFunc("/relay", ...) // WebSocket E2EE tunnel
49 -mux.HandleFunc("/peer/{id}/*", ...) // Server-side reverse proxy
50 -```
51 -
52 -**Responsibilities:**
53 -- ✅ Serve WASM SDK files
54 -- ✅ WebSocket relay for E2EE tunnels
55 -- ✅ Server-side HTTP reverse proxy
56 -
57 -### 2. WASM SDK (Rust)
58 -
59 -**Location:** `portal/wasm/`
60 -
61 -**Key Files:**
62 -- `src/proxy_engine.rs` - E2EE proxy engine
63 -- `src/relay_client.rs` - WebSocket client
64 -- `src/crypto.rs` - Ed25519 encryption
65 -- `sw-proxy.js` - Service Worker implementation
66 -
67 -**Responsibilities:**
68 -- ✅ Intercept browser requests via Service Worker
69 -- ✅ E2EE encryption/decryption
70 -- ✅ WebSocket tunnel management
71 -
72 -### 3. Go SDK
73 -
74 -**Location:** `sdk/`
75 -
76 -**Key Components:**
77 -- `RDClient` - Go client for relay connections
78 -- `Credential` - Ed25519 key management
79 -- `Dial()` - Network connection through relay
80 -
81 -**Responsibilities:**
82 -- ✅ Peer-to-peer E2EE connections
83 -- ✅ Lease registration
84 -- ✅ Server-side integration
85 -
86 ----
87 -
88 -## Deployment
89 -
90 -### Option A: Embedded in Server (Production)
91 -
92 -**Build Script:** `deploy-server.sh`
93 -
94 -```bash
95 -#!/bin/bash
96 -set -e
97 -
98 -echo "Building WASM SDK..."
99 -cd portal/wasm
100 -wasm-pack build --target web --release
101 -
102 -echo "Copying to server embed directory..."
103 -mkdir -p ../../cmd/relay-server/wasm
104 -cp pkg/portal_wasm.js ../../cmd/relay-server/wasm/
105 -cp pkg/portal_wasm_bg.wasm ../../cmd/relay-server/wasm/
106 -cp pkg/portal_wasm_sw.js ../../cmd/relay-server/wasm/
107 -cp sw-proxy.js ../../cmd/relay-server/wasm/
108 -
109 -echo "Building server..."
110 -cd ../../cmd/relay-server
111 -go build -o relay-server
112 -
113 -echo "✓ Server built with embedded WASM SDK"
114 -```
115 -
116 -**Server Config:**
117 -
118 -```go
119 -// view.go line 28-29
120 -//go:embed wasm
121 -var wasmFS embed.FS
122 -```
123 -
124 -**Endpoints:**
125 -- `GET /` - Admin UI
126 -- `GET /pkg/*` - WASM files (embedded)
127 -- `GET /sw-proxy.js` - Service Worker (embedded)
128 -- `WS /relay` - E2EE WebSocket tunnel
129 -- `ANY /peer/{leaseID}/*` - Server-side reverse proxy
130 -
131 ----
132 -
133 -### Option B: Separate Static Server (Development)
134 -
135 -```bash
136 -# Terminal 1: Relay Server
137 -cd cmd/relay-server
138 -go run .
139 -
140 -# Terminal 2: WASM Dev Server
141 -cd portal/wasm
142 -wasm-pack build --target web --dev
143 -python -m http.server 8000
144 -```
145 -
146 -**Access:**
147 -- Admin: `http://localhost:4017/`
148 -- E2EE Test: `http://localhost:8000/index.html`
149 -
150 ----
151 -
152 -## Usage
153 -
154 -### For End Users (Browser)
155 -
156 -**1. Automatic E2EE Proxy**
157 -
158 -Simply open the page - Service Worker automatically activates:
159 -
160 -```html
161 -<!-- Served by relay server -->
162 -GET http://localhost:4017/index.html
163 -
164 -<!-- Service Worker auto-registers -->
165 -<script>
166 -navigator.serviceWorker.register('/sw-proxy.js');
167 -</script>
168 -
169 -<!-- Now ALL fetch() requests are E2EE proxied! -->
170 -<script>
171 -fetch('https://api.example.com/data'); // ← Automatically encrypted!
172 -</script>
173 -```
174 -
175 -### For Developers (JavaScript)
176 -
177 -**2. Direct RelayClient Usage**
178 -
179 -```javascript
180 -import init, { RelayClient } from '/pkg/portal_wasm.js';
181 -
182 -// Initialize WASM
183 -await init();
184 -
185 -// Connect to relay server
186 -const client = await RelayClient.connect('ws://localhost:4017/relay');
187 -
188 -// Register a service
189 -await client.registerLease('my-service', ['http/1.1', 'h2']);
190 -
191 -// Get server info
192 -const info = await client.getRelayInfo();
193 -console.log('Active leases:', info.leases);
194 -```
195 -
196 -### For Go Applications
197 -
198 -**3. Go SDK Integration**
199 -
200 -```go
201 -import "github.com/gosuda/portal/sdk"
202 -
203 -// Create client
204 -client, err := sdk.NewClient(func(c *sdk.RDClientConfig) {
205 - c.BootstrapServers = []string{"ws://localhost:4017/relay"}
206 -})
207 -
208 -// Create credential
209 -cred := sdk.NewCredential()
210 -
211 -// Dial through relay
212 -conn, err := client.Dial(cred, targetLeaseID, "http/1.1")
213 -
214 -// Use conn as net.Conn
215 -conn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
216 -```
217 -
218 ----
219 -
220 -## Security Features
221 -
222 -### 1. End-to-End Encryption
223 -
224 -- **Algorithm:** Ed25519 (curve25519)
225 -- **Key Exchange:** Each connection uses ephemeral keys
226 -- **Server Role:** Relay only (cannot decrypt)
227 -
228 -```
229 -Client A Relay Server Client B
230 - │ │ │
231 - ├─ Encrypt(data) ────────►│ │
232 - │ ├─ Forward ──────────►│
233 - │ │ ├─ Decrypt(data)
234 -```
235 -
236 -### 2. Service Worker Interception
237 -
238 -```javascript
239 -// sw-proxy.js
240 -self.addEventListener('fetch', (event) => {
241 - if (shouldProxy(event.request.url)) {
242 - // Intercept and encrypt
243 - event.respondWith(
244 - proxyEngine.handleHttpRequest(
245 - event.request.method,
246 - event.request.url,
247 - headers,
248 - body // ← Encrypted before sending
249 - )
250 - );
251 - }
252 -});
253 -```
254 -
255 -### 3. Content-Type Based Routing
256 -
257 -The proxy automatically determines message type:
258 -
259 -| Content-Type | Type | Handling |
260 -|-------------|------|----------|
261 -| `application/json` | Text/API | JSON serialization |
262 -| `multipart/form-data` | File | Chunked streaming |
263 -| `application/octet-stream` | Binary | Raw bytes |
264 -| `text/*` | Text | UTF-8 encoding |
265 -
266 ----
267 -
268 -## Testing
269 -
270 -### 1. E2EE Proxy Test
271 -
272 -```bash
273 -# Start server
274 -cd cmd/relay-server
275 -go run .
276 -
277 -# Open browser
278 -open http://localhost:4017/index.html
279 -
280 -# Test E2EE proxy in console
281 -fetch('https://api.github.com/zen')
282 - .then(r => r.text())
283 - .then(console.log)
284 -
285 -# Check DevTools → Application → Service Workers
286 -# Should see: "ProxyEngine ready"
287 -```
288 -
289 -### 2. Unit Tests
290 -
291 -```bash
292 -# Rust tests
293 -cd portal/wasm
294 -cargo test
295 -
296 -# Go tests
297 -cd sdk
298 -go test ./...
299 -```
300 -
301 -### 3. Integration Tests
302 -
303 -```bash
304 -# Run full integration test
305 -cd portal/wasm
306 -./integration-test.sh
307 -```
308 -
309 ----
310 -
311 -## Troubleshooting
312 -
313 -### Service Worker Not Loading
314 -
315 -**Symptom:** `sw-proxy.js` returns 404
316 -
317 -**Solution:**
318 -```bash
319 -# Check if file exists in embed
320 -ls cmd/relay-server/wasm/sw-proxy.js
321 -
322 -# Rebuild if missing
323 -cd portal/wasm
324 -wasm-pack build --target web
325 -cp sw-proxy.js ../../cmd/relay-server/wasm/
326 -```
327 -
328 -### WASM Init Failed
329 -
330 -**Symptom:** `Cannot find module 'wasm_bindgen'`
331 -
332 -**Solution:**
333 -```bash
334 -# Rebuild WASM with correct target
335 -cd portal/wasm
336 -wasm-pack build --target web --release
337 -
338 -# Check output
339 -ls pkg/
340 -```
341 -
342 -### E2EE Connection Failed
343 -
344 -**Symptom:** WebSocket connection refused
345 -
346 -**Solution:**
347 -1. Check relay server is running
348 -2. Verify WebSocket URL: `ws://localhost:4017/relay`
349 -3. Check CORS settings in browser
350 -
351 ----
352 -
353 -## Performance
354 -
355 -### With E2EE Proxy
356 -
357 -| Metric | Before | After | Overhead |
358 -|--------|--------|-------|----------|
359 -| First Load | 2-3s | 2.5-3.5s | +500ms (WASM init) |
360 -| Cached Load | 2s | 100ms | -95% (Service Worker) |
361 -| Request Latency | 50ms | 80ms | +30ms (encryption) |
362 -| Throughput | 100MB/s | 90MB/s | -10% (crypto) |
363 -
364 -### Optimization Tips
365 -
366 -1. **Preload WASM:**
367 - ```html
368 - <link rel="preload" href="/pkg/portal_wasm_bg.wasm" as="fetch" crossorigin>
369 - ```
370 -
371 -2. **Service Worker Cache:**
372 - ```javascript
373 - // sw-proxy.js caches WASM files
374 - const CACHE_NAME = 'portal-v1';
375 - ```
376 -
377 -3. **Use HTTP/2:**
378 - ```go
379 - // Enables multiplexing
380 - srv := &http.Server{...}
381 - ```
382 -
383 ----
384 -
385 -## FAQ
386 -
387 -### Q: Is E2EE proxy mandatory?
388 -
389 -**A:** Yes, for production use. All network traffic should be encrypted.
390 -
391 -### Q: Can I disable Service Worker?
392 -
393 -**A:** Yes, for development. Use `RelayClient` directly without Service Worker.
394 -
395 -### Q: Does the server see my data?
396 -
397 -**A:** No. The relay server only forwards encrypted packets. Only peers can decrypt.
398 -
399 -### Q: What about WebSocket connections?
400 -
401 -**A:** WebSocket connections are also E2EE proxied through the Service Worker.
402 -
403 ----
404 -
405 -## References
406 -
407 -- [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
408 -- [WebAssembly](https://webassembly.org/)
409 -- [Ed25519 Signature](https://ed25519.cr.yp.to/)
410 -- [wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/)
411 -
412 ----
413 -
414 -## License
415 -
416 -See [LICENSE](../../LICENSE) file.
portal/wasm/README.md deleted
-245
@@ -1,245 +0,0 @@
1 -# Portal WASM SDK
2 -
3 -WebAssembly SDK for Portal with **mandatory End-to-End Encryption (E2EE) Proxy** functionality.
4 -
5 -## Overview
6 -
7 -This WASM SDK provides browser-native E2EE proxy capabilities through Service Worker interception. All network traffic is automatically encrypted client-side before being relayed through the server.
8 -
9 -## Features
10 -
11 -- 🔒 **E2EE Proxy (Mandatory)**: Service Worker intercepts all fetch() requests and encrypts them client-side
12 -- 🔐 **Strong Encryption**: Ed25519 key exchange + ChaCha20-Poly1305 authenticated encryption
13 -- 🌐 **WebSocket Transport**: Real-time bidirectional E2EE tunnels
14 -- 📦 **Protocol Support**: HTTP, WebSocket, and TCP proxying through encrypted channels
15 -- 🎯 **Browser Native**: Runs directly in browser using WebAssembly
16 -- ⚡ **High Performance**: Compiled Rust code optimized for WASM
17 -- 🔄 **Auto Type Detection**: Content-Type based routing (Text/File/Binary/API)
18 -
19 -## Architecture
20 -
21 -```
22 -Browser Application
23 - │ fetch()
24 - ▼
25 -Service Worker (sw-proxy.js) ← Intercepts ALL requests
26 - │
27 - ▼
28 -WASM ProxyEngine ← E2EE encryption
29 - │ E2EE WebSocket
30 - ▼
31 -Relay Server ← Relay only (cannot decrypt)
32 - │ E2EE Tunnel
33 - ▼
34 -Target Peer ← Decrypts and processes
35 -```
36 -
37 -## Building
38 -
39 -### Prerequisites
40 -
41 -- Rust toolchain (1.70+)
42 -- wasm-pack: `cargo install wasm-pack`
43 -- make (for automated builds)
44 -
45 -### Quick Build
46 -
47 -**Using Makefile (Recommended):**
48 -```bash
49 -# From repository root
50 -make build-wasm
51 -
52 -# This will:
53 -# 1. Build WASM module with wasm-pack
54 -# 2. Copy artifacts to cmd/relay-server/wasm/ (for embed)
55 -# 3. Copy Service Worker files (sw-proxy.js, sw.js)
56 -```
57 -
58 -**Manual Build:**
59 -```bash
60 -cd portal/wasm
61 -
62 -# Build WASM module
63 -wasm-pack build --target web --release
64 -
65 -# Deploy to server (copies all files to embed directory)
66 -./deploy-server.sh
67 -```
68 -
69 -**Build Server:**
70 -```bash
71 -cd ../../cmd/relay-server
72 -
73 -# Build server with embedded WASM
74 -go build -o relay-server
75 -
76 -# Run
77 -./relay-server
78 -```
79 -
80 -**Access:**
81 -- Admin UI: `http://localhost:4017/`
82 -
83 -### Docker Build
84 -
85 -```bash
86 -# From repository root
87 -docker build -t portal-server .
88 -
89 -# Run
90 -docker run -p 4017:4017 portal-server
91 -```
92 -
93 -The Dockerfile uses multi-stage builds:
94 -1. **Stage 1**: Build WASM with Rust + wasm-pack
95 -2. **Stage 2**: Build Go server with embedded WASM
96 -3. **Stage 3**: Minimal runtime image
97 -
98 -## Output Files
99 -
100 -After building, the following files are generated in `cmd/relay-server/wasm/`:
101 -
102 -```
103 -cmd/relay-server/wasm/
104 -├── portal_wasm.js # WASM JavaScript bindings
105 -├── portal_wasm_bg.wasm # WASM binary (465KB)
106 -├── portal_wasm_sw.js # Service Worker bindings
107 -├── portal_wasm.d.ts # TypeScript definitions
108 -├── sw-proxy.js # E2EE Proxy Service Worker (ESSENTIAL)
109 -└── sw.js # Basic caching Service Worker
110 -```
111 -
112 -These files are embedded in the Go server binary via `//go:embed wasm` directive.
113 -
114 -## Usage
115 -
116 -### For End Users (Browser)
117 -
118 -Simply open the page - E2EE Proxy activates automatically:
119 -
120 -```html
121 -<!-- Open: http://localhost:4017/ -->
122 -
123 -<!-- Service Worker auto-registers -->
124 -<script>
125 -navigator.serviceWorker.register('/sw-proxy.js')
126 - .then(() => console.log('E2EE Proxy activated'));
127 -</script>
128 -
129 -<!-- Now ALL fetch() requests are E2EE encrypted! -->
130 -<script>
131 -fetch('https://api.github.com/zen')
132 - .then(r => r.text())
133 - .then(console.log);
134 -// ↑ Automatically encrypted via E2EE tunnel!
135 -</script>
136 -```
137 -
138 -### For Developers (JavaScript)
139 -
140 -```javascript
141 -import init, { RelayClient } from '/pkg/portal_wasm.js';
142 -
143 -// Initialize WASM
144 -await init();
145 -
146 -// Connect to relay server
147 -const client = await RelayClient.connect('ws://localhost:4017/relay');
148 -
149 -// Register a service
150 -await client.registerLease('my-service', ['http/1.1', 'h2']);
151 -
152 -// Get server info
153 -const info = await client.getRelayInfo();
154 -console.log('Active leases:', info.leases);
155 -```
156 -
157 -### For Go Applications
158 -
159 -See [Go SDK Documentation](../../sdk/)
160 -
161 -## Documentation
162 -
163 -- **[E2EE_PROXY_INTEGRATION.md](E2EE_PROXY_INTEGRATION.md)** - Comprehensive integration guide
164 -- **[E2EE_PROXY_DEPLOYMENT.md](../../E2EE_PROXY_DEPLOYMENT.md)** - Korean deployment guide
165 -- **[SERVICE_WORKER.md](SERVICE_WORKER.md)** - Service Worker implementation details
166 -- **[BUILDING.md](BUILDING.md)** - Detailed build instructions
167 -- **[INTEGRATION_TEST_GUIDE.md](INTEGRATION_TEST_GUIDE.md)** - Testing procedures
168 -- **[USAGE.md](USAGE.md)** - API usage examples
169 -
170 -## Testing
171 -
172 -```bash
173 -# Unit tests
174 -cd portal/wasm
175 -cargo test
176 -
177 -# Integration tests
178 -./integration-test.sh
179 -
180 -# Browser test
181 -# 1. Start server: cd ../../cmd/relay-server && ./relay-server
182 -# 2. Open: http://localhost:4017
183 -# 3. Check DevTools Console for "ProxyEngine ready"
184 -```
185 -
186 -## Security
187 -
188 -### End-to-End Encryption
189 -
190 -- **Algorithm**: Ed25519 key exchange + X25519 ECDH + ChaCha20-Poly1305
191 -- **Key Management**: Ephemeral keys per connection
192 -- **Server Role**: Relay only (cannot decrypt)
193 -
194 -### Content-Type Based Type Detection
195 -
196 -Service Worker automatically determines message type:
197 -
198 -| Content-Type | Type | Handling |
199 -|-------------|------|----------|
200 -| `application/json` | Text/API | JSON serialization |
201 -| `multipart/form-data` | File | Chunked streaming |
202 -| `application/octet-stream` | Binary | Raw bytes |
203 -| `text/*` | Text | UTF-8 encoding |
204 -
205 -## Troubleshooting
206 -
207 -### Service Worker 404 Error
208 -
209 -```bash
210 -# Rebuild and deploy
211 -cd portal/wasm
212 -./deploy-server.sh
213 -cd ../../cmd/relay-server
214 -go build -o relay-server
215 -```
216 -
217 -### WASM Initialization Failed
218 -
219 -```bash
220 -# Check files are served correctly
221 -curl http://localhost:4017/pkg/portal_wasm.js
222 -curl http://localhost:4017/pkg/portal_wasm_bg.wasm
223 -curl http://localhost:4017/sw-proxy.js
224 -```
225 -
226 -### WebSocket Connection Refused
227 -
228 -```bash
229 -# Verify server is running and URL is correct
230 -# Correct: ws://localhost:4017/relay
231 -# Incorrect: ws://localhost:4017/
232 -```
233 -
234 -## Performance
235 -
236 -| Metric | Standard | E2EE Proxy | Overhead |
237 -|--------|----------|------------|----------|
238 -| First Load | 2-3s | 2.5-3.5s | +500ms (WASM init) |
239 -| Cached Load | 2s | 100ms | -95% (Service Worker) |
240 -| Request Latency | 50ms | 80ms | +30ms (encryption) |
241 -| Throughput | 100MB/s | 90MB/s | -10% (crypto) |
242 -
243 -## License
244 -
245 -MIT OR Apache-2.0
portal/wasm/SERVICE_WORKER.md deleted
-259
@@ -1,259 +0,0 @@
1 -# Service Worker Implementation for Portal WASM
2 -
3 -## Overview
4 -
5 -Portal WASM client uses Service Worker to efficiently load and cache WASM modules and JavaScript files.
6 -
7 -## Architecture
8 -
9 -```
10 -┌─────────────────┐
11 -│ Browser │
12 -│ (example.html) │
13 -└────────┬────────┘
14 - │
15 - │ 1. Register
16 - ▼
17 -┌─────────────────┐
18 -│ Service Worker │
19 -│ (sw.js) │
20 -└────────┬────────┘
21 - │
22 - │ 2. Cache & Serve
23 - ▼
24 -┌─────────────────┐
25 -│ WASM Files │
26 -│ - .wasm │
27 -│ - .js │
28 -└─────────────────┘
29 -```
30 -
31 -## Files
32 -
33 -### 1. `sw.js` - Service Worker Script
34 -
35 -Service Worker provides the following features:
36 -
37 -- **Install**: Store WASM and JavaScript files in cache
38 -- **Activate**: Delete cache from previous versions
39 -- **Fetch**: Serve files using cache-first strategy
40 -
41 -#### Cached Files
42 -- `/pkg/portal_wasm.js` - JavaScript glue code
43 -- `/pkg/portal_wasm_bg.wasm` - WASM binary
44 -- `/example.html` - Main HTML page
45 -
46 -### 2. HTML Integration
47 -
48 -#### example.html
49 -```javascript
50 -// Service Worker Registration
51 -if ('serviceWorker' in navigator) {
52 - navigator.serviceWorker.register('/sw.js')
53 - .then((registration) => {
54 - console.log('Service Worker registered:', registration.scope);
55 - })
56 - .catch((error) => {
57 - console.error('Service Worker registration failed:', error);
58 - });
59 -}
60 -
61 -// WASM Module Loading
62 -import init, { RelayClient } from './pkg/portal_wasm.js';
63 -```
64 -
65 -## Benefits
66 -
67 -### 1. **Faster Loading**
68 -- Improved loading speed by serving WASM files from cache
69 -- Reduced network requests
70 -
71 -### 2. **Offline Support**
72 -- Works offline once cached
73 -- Reliable even in unstable network environments
74 -
75 -### 3. **Better Performance**
76 -- Immediate response with cache-first strategy
77 -- Significant caching benefits due to large WASM file sizes
78 -
79 -### 4. **Version Control**
80 -- Version management through cache name
81 -- Automatic updates when deploying new versions
82 -
83 -## Cache Strategy
84 -
85 -### WASM Files (.wasm)
86 -```javascript
87 -Cache First → Network Fallback
88 -```
89 -
90 -1. Check cache
91 -2. Return if found in cache
92 -3. Fetch from network if not found
93 -4. Store fetched file in cache
94 -
95 -### JavaScript Files (.js)
96 -```javascript
97 -Cache First → Network Fallback
98 -```
99 -
100 -Same as WASM files
101 -
102 -### Other Files
103 -```javascript
104 -Network First → Cache Fallback
105 -```
106 -
107 -## Usage
108 -
109 -### Development
110 -
111 -Run development server:
112 -```bash
113 -cd portal/wasm
114 -go run serve.go
115 -# or
116 -python3 -m http.server 8000
117 -```
118 -
119 -Access via browser:
120 -```
121 -http://localhost:8000/example.html
122 -```
123 -
124 -### Production
125 -
126 -1. Build WASM:
127 -```bash
128 -wasm-pack build --target web
129 -```
130 -
131 -2. Host static files:
132 -- `pkg/` directory
133 -- `sw.js`
134 -- `example.html`
135 -
136 -3. HTTPS required:
137 -- Service Worker requires HTTPS (localhost is an exception)
138 -
139 -## Debugging
140 -
141 -### Chrome DevTools
142 -
143 -1. **Application Tab** → **Service Workers**
144 - - Check registered Service Workers
145 - - Unregister/Update available
146 -
147 -2. **Application Tab** → **Cache Storage**
148 - - Check cached files
149 - - Can delete cache
150 -
151 -3. **Console**
152 - - Check Service Worker logs
153 - ```
154 - [SW] Installing Service Worker...
155 - [SW] Caching WASM files
156 - [SW] Serving WASM from cache: /pkg/portal_wasm_bg.wasm
157 - ```
158 -
159 -### Force Update
160 -
161 -Clear cache:
162 -```javascript
163 -// In browser console
164 -caches.keys().then(keys => {
165 - keys.forEach(key => caches.delete(key));
166 -});
167 -
168 -// Unregister Service Worker
169 -navigator.serviceWorker.getRegistrations().then(registrations => {
170 - registrations.forEach(r => r.unregister());
171 -});
172 -```
173 -
174 -## Configuration
175 -
176 -### Cache Name
177 -
178 -Manage cache versions in `sw.js`:
179 -```javascript
180 -const CACHE_NAME = 'portal-wasm-v1';
181 -```
182 -
183 -When deploying new version:
184 -```javascript
185 -const CACHE_NAME = 'portal-wasm-v2';
186 -```
187 -
188 -### Cached URLs
189 -
190 -Add required files:
191 -```javascript
192 -const urlsToCache = [
193 - '/pkg/portal_wasm.js',
194 - '/pkg/portal_wasm_bg.wasm',
195 - '/example.html',
196 - // Add more files...
197 -];
198 -```
199 -
200 -## Troubleshooting
201 -
202 -### Service Worker Not Registered
203 -
204 -**Cause**: Not using HTTPS or incorrect path
205 -
206 -**Solution**:
207 -- Use localhost or HTTPS
208 -- Verify `/sw.js` path
209 -
210 -### WASM Not Loading
211 -
212 -**Cause**: Cache miss or incorrect MIME type
213 -
214 -**Solution**:
215 -- Clear cache
216 -- Verify server provides correct MIME type
217 - - `.wasm` → `application/wasm`
218 - - `.js` → `application/javascript`
219 -
220 -### Old Version Cached
221 -
222 -**Cause**: Service Worker not updated
223 -
224 -**Solution**:
225 -1. Change cache name (`v1` → `v2`)
226 -2. Hard refresh (Ctrl+Shift+R)
227 -3. Enable "Update on reload" in DevTools
228 -
229 -## Security Considerations
230 -
231 -1. **HTTPS Required**
232 - - Must use HTTPS in production
233 -
234 -2. **Same-Origin Policy**
235 - - Service Worker only works from same origin
236 -
237 -3. **Scope**
238 - - `/sw.js` at root controls entire site
239 - - Can limit scope if needed
240 -
241 -## Performance Metrics
242 -
243 -### Before Service Worker
244 -- WASM Load: ~2-3s (network)
245 -- JS Load: ~500ms (network)
246 -- Total: ~3s
247 -
248 -### After Service Worker (cached)
249 -- WASM Load: ~50ms (cache)
250 -- JS Load: ~20ms (cache)
251 -- Total: ~100ms
252 -
253 -**30x faster!**
254 -
255 -## References
256 -
257 -- [Service Worker API - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
258 -- [WebAssembly](https://webassembly.org/)
259 -- [wasm-pack](https://rustwasm.github.io/wasm-pack/)
portal/wasm/USAGE.md deleted
-417
@@ -1,417 +0,0 @@
1 -# Portal WASM Usage Guide
2 -
3 -## 📦 Installation and Build
4 -
5 -### 1. Prerequisites
6 -
7 -```bash
8 -# Install Rust (if not already installed)
9 -# Installed via rustup
10 -
11 -# Install wasm-pack (if not already installed)
12 -curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
13 -
14 -# Add WASM target
15 -rustup target add wasm32-unknown-unknown
16 -```
17 -
18 -### 2. Build
19 -
20 -```bash
21 -cd e:/git/portal/portal/wasm
22 -
23 -# Development build (fast, includes debug info)
24 -wasm-pack build --target web --dev
25 -
26 -# Production build (optimized, smaller size)
27 -wasm-pack build --target web --release
28 -
29 -# Build output is generated in pkg/ folder
30 -```
31 -
32 -## 🚀 Usage
33 -
34 -### Using in Browser
35 -
36 -#### 1. Prepare HTML File
37 -
38 -```html
39 -<!DOCTYPE html>
40 -<html>
41 -<head>
42 - <meta charset="utf-8">
43 - <title>Portal WASM Client</title>
44 -</head>
45 -<body>
46 - <h1>Portal WASM Client</h1>
47 - <div id="status">Initializing...</div>
48 - <pre id="output"></pre>
49 -
50 - <script type="module">
51 - // Import WASM module
52 - import init, { RelayClient } from './pkg/portal_wasm.js';
53 -
54 - async function main() {
55 - try {
56 - // 1. Initialize WASM
57 - await init();
58 - console.log('✓ WASM initialized');
59 -
60 - // 2. Connect to Portal server
61 - const client = await new RelayClient('wss://your-relay-server.com/ws');
62 - document.getElementById('status').textContent = 'Connected!';
63 -
64 - // 3. Get server info
65 - const info = await client.getRelayInfo();
66 - console.log('Server info:', info);
67 - document.getElementById('output').textContent =
68 - JSON.stringify(info, null, 2);
69 -
70 - // 4. Check my credential ID
71 - const myId = client.getCredentialId();
72 - console.log('My ID:', myId);
73 -
74 - // 5. Register lease
75 - await client.registerLease('my-service', ['http/1.1', 'h2']);
76 - console.log('✓ Lease registered');
77 -
78 - } catch (error) {
79 - console.error('Error:', error);
80 - document.getElementById('status').textContent = 'Error: ' + error;
81 - }
82 - }
83 -
84 - main();
85 - </script>
86 -</body>
87 -</html>
88 -```
89 -
90 -#### 2. Run Local Server
91 -
92 -```bash
93 -# Python HTTP server
94 -cd e:/git/portal/portal/wasm
95 -python -m http.server 8000
96 -
97 -# Or Node.js
98 -npx serve .
99 -
100 -# Open in browser
101 -# http://localhost:8000/example.html
102 -```
103 -
104 -### API Reference
105 -
106 -#### RelayClient
107 -
108 -##### Constructor
109 -
110 -```typescript
111 -new RelayClient(serverUrl: string): Promise<RelayClient>
112 -```
113 -
114 -Connect to Portal server.
115 -
116 -**Parameters:**
117 -- `serverUrl`: WebSocket server URL (e.g., `wss://relay.example.com/ws`)
118 -
119 -**Example:**
120 -```javascript
121 -const client = await new RelayClient('wss://relay.example.com/ws');
122 -```
123 -
124 -##### getRelayInfo()
125 -
126 -```typescript
127 -getRelayInfo(): Promise<RelayInfo>
128 -```
129 -
130 -Get server information and list of active leases.
131 -
132 -**Returns:**
133 -```typescript
134 -interface RelayInfo {
135 - identity: {
136 - id: string;
137 - public_key: string;
138 - };
139 - address: string[];
140 - leases: string[];
141 -}
142 -```
143 -
144 -**Example:**
145 -```javascript
146 -const info = await client.getRelayInfo();
147 -console.log('Server ID:', info.identity.id);
148 -console.log('Active leases:', info.leases);
149 -```
150 -
151 -##### getCredentialId()
152 -
153 -```typescript
154 -getCredentialId(): string
155 -```
156 -
157 -Returns the client's credential ID (Ed25519 public key hash).
158 -
159 -**Example:**
160 -```javascript
161 -const myId = client.getCredentialId();
162 -console.log('My credential ID:', myId);
163 -```
164 -
165 -##### registerLease()
166 -
167 -```typescript
168 -registerLease(name: string, alpns: string[]): Promise<void>
169 -```
170 -
171 -Register a service.
172 -
173 -**Parameters:**
174 -- `name`: Service name
175 -- `alpns`: List of ALPN protocols (e.g., `['http/1.1', 'h2']`)
176 -
177 -**Example:**
178 -```javascript
179 -await client.registerLease('my-api-service', ['http/1.1', 'h2']);
180 -```
181 -
182 -##### requestConnection()
183 -
184 -```typescript
185 -requestConnection(leaseId: string, alpn: string): Promise<string>
186 -```
187 -
188 -Request a connection to another peer.
189 -
190 -**Parameters:**
191 -- `leaseId`: Lease ID to connect to
192 -- `alpn`: ALPN protocol
193 -
194 -**Example:**
195 -```javascript
196 -const result = await client.requestConnection('target-lease-id', 'http/1.1');
197 -console.log('Connection result:', result);
198 -```
199 -
200 -## 🔧 Advanced Usage
201 -
202 -### Using with TypeScript
203 -
204 -```typescript
205 -import init, { RelayClient } from './pkg/portal_wasm';
206 -
207 -interface RelayInfo {
208 - identity: {
209 - id: string;
210 - public_key: string;
211 - };
212 - address: string[];
213 - leases: string[];
214 -}
215 -
216 -async function connectToRelay(url: string): Promise<RelayClient> {
217 - await init();
218 - return await new RelayClient(url);
219 -}
220 -
221 -async function main() {
222 - const client = await connectToRelay('wss://relay.example.com/ws');
223 - const info: RelayInfo = await client.getRelayInfo();
224 - console.log(info);
225 -}
226 -```
227 -
228 -### Using with React
229 -
230 -```tsx
231 -import { useEffect, useState } from 'react';
232 -import init, { RelayClient } from './pkg/portal_wasm';
233 -
234 -function App() {
235 - const [client, setClient] = useState<RelayClient | null>(null);
236 - const [info, setInfo] = useState<any>(null);
237 -
238 - useEffect(() => {
239 - async function connect() {
240 - await init();
241 - const c = await new RelayClient('wss://relay.example.com/ws');
242 - setClient(c);
243 -
244 - const i = await c.getRelayInfo();
245 - setInfo(i);
246 - }
247 - connect();
248 - }, []);
249 -
250 - if (!info) return <div>Loading...</div>;
251 -
252 - return (
253 - <div>
254 - <h1>Server ID: {info.identity.id}</h1>
255 - <h2>Active Leases: {info.leases.length}</h2>
256 - </div>
257 - );
258 -}
259 -```
260 -
261 -### Using with Vue
262 -
263 -```vue
264 -<template>
265 - <div>
266 - <h1>Portal Client</h1>
267 - <div v-if="loading">Connecting...</div>
268 - <div v-else-if="error">Error: {{ error }}</div>
269 - <div v-else>
270 - <p>Server ID: {{ info?.identity.id }}</p>
271 - <p>Leases: {{ info?.leases.length }}</p>
272 - </div>
273 - </div>
274 -</template>
275 -
276 -<script setup lang="ts">
277 -import { ref, onMounted } from 'vue';
278 -import init, { RelayClient } from './pkg/portal_wasm';
279 -
280 -const client = ref<RelayClient | null>(null);
281 -const info = ref<any>(null);
282 -const loading = ref(true);
283 -const error = ref('');
284 -
285 -onMounted(async () => {
286 - try {
287 - await init();
288 - client.value = await new RelayClient('wss://relay.example.com/ws');
289 - info.value = await client.value.getRelayInfo();
290 - } catch (e: any) {
291 - error.value = e.message;
292 - } finally {
293 - loading.value = false;
294 - }
295 -});
296 -</script>
297 -```
298 -
299 -## 📝 Error Handling
300 -
301 -```javascript
302 -try {
303 - const client = await new RelayClient('wss://relay.example.com/ws');
304 - await client.registerLease('my-service', ['http/1.1']);
305 -} catch (error) {
306 - if (error.toString().includes('WebSocket connection failed')) {
307 - console.error('Cannot connect to server');
308 - } else if (error.toString().includes('lease registration rejected')) {
309 - console.error('Lease registration rejected');
310 - } else {
311 - console.error('Unknown error:', error);
312 - }
313 -}
314 -```
315 -
316 -## 🔍 Debugging
317 -
318 -### Browser DevTools
319 -
320 -```javascript
321 -// Check WASM loading
322 -console.log('WASM memory:', WebAssembly.Memory);
323 -
324 -// Error details
325 -window.addEventListener('error', (e) => {
326 - console.error('Global error:', e);
327 -});
328 -
329 -// WASM initialization failure
330 -init().catch(err => {
331 - console.error('WASM init failed:', err);
332 -});
333 -```
334 -
335 -### Network Monitoring
336 -
337 -Browser DevTools → Network tab:
338 -- Check WebSocket connection status
339 -- Inspect message payloads
340 -- Analyze connection timing
341 -
342 -## 🚦 Real-world Example
343 -
344 -### Simple Chat Application
345 -
346 -```javascript
347 -import init, { RelayClient } from './pkg/portal_wasm.js';
348 -
349 -class ChatApp {
350 - constructor() {
351 - this.client = null;
352 - }
353 -
354 - async connect(serverUrl, username) {
355 - await init();
356 - this.client = await new RelayClient(serverUrl);
357 -
358 - // Register chat room as lease
359 - await this.client.registerLease(`chat-${username}`, ['chat-protocol']);
360 -
361 - console.log(`✓ Connected as ${username}`);
362 - console.log(`✓ Credential ID: ${this.client.getCredentialId()}`);
363 - }
364 -
365 - async listOnlineUsers() {
366 - const info = await this.client.getRelayInfo();
367 - return info.leases.filter(id => id.startsWith('chat-'));
368 - }
369 -
370 - async sendMessage(targetUser, message) {
371 - await this.client.requestConnection(
372 - `chat-${targetUser}`,
373 - 'chat-protocol'
374 - );
375 - // Message sending logic...
376 - }
377 -}
378 -
379 -// Usage
380 -const chat = new ChatApp();
381 -await chat.connect('wss://relay.example.com/ws', 'Alice');
382 -const users = await chat.listOnlineUsers();
383 -console.log('Online users:', users);
384 -```
385 -
386 -## 📚 More Examples
387 -
388 -- [example.html](./example.html) - Basic example
389 -- [README.md](./README.md) - Project overview
390 -- [BUILDING.md](./BUILDING.md) - Detailed build guide
391 -
392 -## 🐛 Known Issues
393 -
394 -1. **WebSocket Connection Delay in Safari**: Safari may experience slight delays when establishing WebSocket connections.
395 -2. **File Size**: WASM file is approximately ~500KB on first build. gzip compression recommended.
396 -3. **Cross-Origin**: CORS configuration may be required.
397 -
398 -## 💡 Tips
399 -
400 -- **Reduce Build Size**: Use `wasm-opt` for additional optimization
401 - ```bash
402 - wasm-opt -Oz -o optimized.wasm pkg/portal_wasm_bg.wasm
403 - ```
404 -
405 -- **Improve Loading Speed**:
406 - ```javascript
407 - // Use streaming initialization
408 - const response = await fetch('./pkg/portal_wasm_bg.wasm');
409 - await init(response);
410 - ```
411 -
412 -- **Better Error Handling**:
413 - ```javascript
414 - window.addEventListener('unhandledrejection', event => {
415 - console.error('Promise rejection:', event.reason);
416 - });
417 - ```
portal/wasm/build.rs deleted
-13
@@ -1,13 +0,0 @@
1 -fn main() -> Result<(), Box<dyn std::error::Error>> {
2 - // Use local proto files with corrected import paths
3 - let proto_files = &[
4 - "proto/rdsec/rdsec.proto",
5 - "proto/rdverb/rdverb.proto",
6 - ];
7 -
8 - let includes = &["proto"];
9 -
10 - prost_build::compile_protos(proto_files, includes)?;
11 -
12 - Ok(())
13 -}
portal/wasm/package.json deleted
-27
@@ -1,27 +0,0 @@
1 -{
2 - "name": "portal-wasm",
3 - "version": "0.1.0",
4 - "description": "WebAssembly client for Portal",
5 - "main": "pkg/portal_wasm.js",
6 - "types": "pkg/portal_wasm.d.ts",
7 - "scripts": {
8 - "build": "wasm-pack build --target web --out-dir pkg",
9 - "build:node": "wasm-pack build --target nodejs --out-dir pkg-node",
10 - "build:dev": "wasm-pack build --target web --dev",
11 - "serve": "python -m http.server 8000",
12 - "test": "wasm-pack test --headless --chrome"
13 - },
14 - "keywords": [
15 - "wasm",
16 - "webassembly",
17 - "relay",
18 - "dns",
19 - "p2p",
20 - "networking"
21 - ],
22 - "author": "Portal Contributors",
23 - "license": "MIT OR Apache-2.0",
24 - "files": [
25 - "pkg/"
26 - ]
27 -}
portal/wasm/proto/rdsec/rdsec.proto deleted
-44
@@ -1,44 +0,0 @@
1 -syntax = "proto3";
2 -
3 -package rdsec;
4 -
5 -option go_package = "github.com/gosuda/portal/portal/core/proto/rdsec;rdsec";
6 -
7 -message Identity {
8 - string id = 1;
9 - bytes public_key = 2;
10 -}
11 -
12 -enum ProtocolVersion {
13 - PROTOCOL_VERSION_1 = 0;
14 -}
15 -
16 -message ClientInitPayload {
17 - ProtocolVersion version = 1;
18 - bytes nonce = 2;
19 - int64 timestamp = 3;
20 - Identity identity = 4;
21 - string alpn = 5;
22 -
23 - bytes session_public_key = 6;
24 -}
25 -
26 -message SignedPayload {
27 - bytes data = 1;
28 - bytes signature = 2;
29 -}
30 -
31 -message ServerInitPayload {
32 - ProtocolVersion version = 1;
33 - bytes nonce = 2;
34 - int64 timestamp = 3;
35 - Identity identity = 4;
36 - string alpn = 5;
37 -
38 - bytes session_public_key = 6;
39 -}
40 -
41 -message EncryptedData {
42 - bytes nonce = 1;
43 - bytes payload = 2;
44 -}
portal/wasm/proto/rdverb/rdverb.proto deleted
-86
@@ -1,86 +0,0 @@
1 -syntax = "proto3";
2 -
3 -package rdverb;
4 -
5 -import "rdsec/rdsec.proto";
6 -
7 -option go_package = "github.com/gosuda/portal/portal/core/proto/rdverb;rdverb";
8 -
9 -enum PacketType {
10 - PACKET_TYPE_RELAY_INFO_REQUEST = 0;
11 - PACKET_TYPE_RELAY_INFO_RESPONSE = 1;
12 -
13 - PACKET_TYPE_LEASE_UPDATE_REQUEST = 2; // Authenticated
14 - PACKET_TYPE_LEASE_UPDATE_RESPONSE = 3;
15 -
16 - PACKET_TYPE_LEASE_DELETE_REQUEST = 4; // Authenticated
17 - PACKET_TYPE_LEASE_DELETE_RESPONSE = 5;
18 -
19 - PACKET_TYPE_CONNECTION_REQUEST = 6;
20 - PACKET_TYPE_CONNECTION_RESPONSE = 7;
21 -}
22 -
23 -enum ResponseCode {
24 - RESPONSE_CODE_UNKNOWN = 0;
25 - RESPONSE_CODE_ACCEPTED = 1;
26 -
27 - RESPONSE_CODE_INVALID_EXPIRES = 2;
28 - RESPONSE_CODE_INVALID_IDENTITY = 3;
29 - RESPONSE_CODE_INVALID_NAME = 4;
30 - RESPONSE_CODE_INVALID_ALPN = 5;
31 -
32 - RESPONSE_CODE_REJECTED = 6;
33 -}
34 -
35 -message Packet {
36 - PacketType type = 1;
37 - bytes payload = 2;
38 -}
39 -
40 -message RelayInfo {
41 - rdsec.Identity identity = 1;
42 - repeated string address = 2;
43 - repeated string leases = 3;
44 -}
45 -
46 -message RelayInfoRequest {}
47 -
48 -message RelayInfoResponse {
49 - RelayInfo relay_info = 1;
50 -}
51 -
52 -message Lease {
53 - rdsec.Identity identity = 1;
54 - int64 expires = 2;
55 - string name = 3;
56 - repeated string alpn = 4;
57 -}
58 -
59 -message LeaseUpdateRequest {
60 - Lease lease = 1;
61 - bytes nonce = 2;
62 - int64 timestamp = 3;
63 -}
64 -
65 -message LeaseUpdateResponse {
66 - ResponseCode code = 1;
67 -}
68 -
69 -message LeaseDeleteRequest {
70 - rdsec.Identity identity = 1;
71 - bytes nonce = 2;
72 - int64 timestamp = 3;
73 -}
74 -
75 -message LeaseDeleteResponse {
76 - ResponseCode code = 1;
77 -}
78 -
79 -message ConnectionRequest {
80 - string lease_id = 1;
81 - rdsec.Identity client_identity = 2;
82 -}
83 -
84 -message ConnectionResponse {
85 - ResponseCode code = 1;
86 -}
portal/wasm/secure-websocket-sw.js deleted
-283
@@ -1,283 +0,0 @@
1 -/**
2 - * SecureWebSocket for Service Worker
3 - *
4 - * SecureWebSocket implementation for use in Service Worker
5 - * Communicates with main thread via MessageChannel
6 - */
7 -
8 -// Service Worker global ProxyEngine (initialized in sw-proxy.js)
9 -// proxyEngine and wasmReady are provided by sw-proxy.js
10 -
11 -/**
12 - * WebSocket tunnel manager in Service Worker
13 - */
14 -class ServiceWorkerWebSocketTunnel {
15 - constructor() {
16 - this.tunnels = new Map(); // tunnelId -> tunnel info
17 - this.messageQueues = new Map(); // tunnelId -> message queue
18 - this.clients = new Map(); // tunnelId -> clientId
19 - }
20 -
21 - /**
22 - * Create WebSocket tunnel
23 - */
24 - async createTunnel(url, protocols, clientId) {
25 - if (!wasmReady || !proxyEngine) {
26 - throw new Error('WASM ProxyEngine not ready');
27 - }
28 -
29 - console.log('[SW-WebSocket] Creating tunnel:', url);
30 -
31 - try {
32 - // Open WebSocket tunnel via WASM ProxyEngine
33 - const result = await proxyEngine.open_websocket(url, protocols || []);
34 - const tunnelId = result.tunnelId;
35 - const protocol = result.protocol || '';
36 -
37 - console.log('[SW-WebSocket] Tunnel created:', tunnelId);
38 -
39 - // Store tunnel information
40 - this.tunnels.set(tunnelId, {
41 - tunnelId,
42 - url,
43 - protocol,
44 - state: 'open',
45 - created: Date.now()
46 - });
47 -
48 - this.messageQueues.set(tunnelId, []);
49 - this.clients.set(tunnelId, clientId);
50 -
51 - // Start receiving messages in background
52 - this._startReceiving(tunnelId);
53 -
54 - return {
55 - tunnelId,
56 - protocol
57 - };
58 -
59 - } catch (error) {
60 - console.error('[SW-WebSocket] Failed to create tunnel:', error);
61 - throw error;
62 - }
63 - }
64 -
65 - /**
66 - * Background message receiving loop
67 - */
68 - async _startReceiving(tunnelId) {
69 - console.log('[SW-WebSocket] Starting receive loop:', tunnelId);
70 -
71 - try {
72 - while (this.tunnels.has(tunnelId)) {
73 - const tunnel = this.tunnels.get(tunnelId);
74 - if (!tunnel || tunnel.state !== 'open') {
75 - break;
76 - }
77 -
78 - // Receive message from WASM
79 - const msg = await proxyEngine.receive_websocket_message(tunnelId);
80 -
81 - console.log('[SW-WebSocket] Received message:', msg.type);
82 -
83 - // Add to message queue
84 - const queue = this.messageQueues.get(tunnelId);
85 - if (queue) {
86 - queue.push(msg);
87 - }
88 -
89 - // Notify client
90 - this._notifyClient(tunnelId, msg);
91 -
92 - // Handle close message
93 - if (msg.type === 'close') {
94 - console.log('[SW-WebSocket] Tunnel closed:', tunnelId);
95 - this._closeTunnel(tunnelId);
96 - break;
97 - }
98 - }
99 -
100 - } catch (error) {
101 - console.error('[SW-WebSocket] Receive loop error:', error);
102 - this._closeTunnel(tunnelId, 1006, error.toString());
103 - }
104 - }
105 -
106 - /**
107 - * Notify client of message
108 - */
109 - async _notifyClient(tunnelId, message) {
110 - const clientId = this.clients.get(tunnelId);
111 - if (!clientId) return;
112 -
113 - try {
114 - const client = await self.clients.get(clientId);
115 - if (client) {
116 - client.postMessage({
117 - type: 'WEBSOCKET_MESSAGE',
118 - tunnelId,
119 - message
120 - });
121 - }
122 - } catch (error) {
123 - console.error('[SW-WebSocket] Failed to notify client:', error);
124 - }
125 - }
126 -
127 - /**
128 - * Send message
129 - */
130 - async sendMessage(tunnelId, data, isBinary) {
131 - if (!this.tunnels.has(tunnelId)) {
132 - throw new Error('Tunnel not found: ' + tunnelId);
133 - }
134 -
135 - console.log('[SW-WebSocket] Sending message:', tunnelId, isBinary ? 'binary' : 'text');
136 -
137 - try {
138 - await proxyEngine.send_websocket_message(tunnelId, data, isBinary);
139 - } catch (error) {
140 - console.error('[SW-WebSocket] Send failed:', error);
141 - throw error;
142 - }
143 - }
144 -
145 - /**
146 - * Close tunnel
147 - */
148 - async closeTunnel(tunnelId, code = 1000, reason = '') {
149 - if (!this.tunnels.has(tunnelId)) {
150 - return;
151 - }
152 -
153 - console.log('[SW-WebSocket] Closing tunnel:', tunnelId, code, reason);
154 -
155 - try {
156 - await proxyEngine.close_websocket(tunnelId, code, reason);
157 - } catch (error) {
158 - console.error('[SW-WebSocket] Close failed:', error);
159 - }
160 -
161 - this._closeTunnel(tunnelId);
162 - }
163 -
164 - /**
165 - * Internal tunnel cleanup
166 - */
167 - _closeTunnel(tunnelId, code = 1000, reason = '') {
168 - const tunnel = this.tunnels.get(tunnelId);
169 - if (tunnel) {
170 - tunnel.state = 'closed';
171 - }
172 -
173 - // Cleanup
174 - this.tunnels.delete(tunnelId);
175 - this.messageQueues.delete(tunnelId);
176 - this.clients.delete(tunnelId);
177 -
178 - console.log('[SW-WebSocket] Tunnel cleaned up:', tunnelId);
179 - }
180 -
181 - /**
182 - * Get tunnel state
183 - */
184 - getTunnelState(tunnelId) {
185 - const tunnel = this.tunnels.get(tunnelId);
186 - return tunnel ? tunnel.state : 'closed';
187 - }
188 -
189 - /**
190 - * Get all tunnel information
191 - */
192 - getAllTunnels() {
193 - return Array.from(this.tunnels.values());
194 - }
195 -}
196 -
197 -// Global tunnel manager instance
198 -let tunnelManager = null;
199 -
200 -/**
201 - * Initialize tunnel manager
202 - */
203 -function initTunnelManager() {
204 - if (!tunnelManager) {
205 - tunnelManager = new ServiceWorkerWebSocketTunnel();
206 - console.log('[SW-WebSocket] Tunnel manager initialized');
207 - }
208 - return tunnelManager;
209 -}
210 -
211 -/**
212 - * WebSocket message handler to add to Service Worker
213 - */
214 -async function handleWebSocketMessage(event) {
215 - const { type, tunnelId, url, protocols, data, isBinary, code, reason } = event.data || {};
216 - const manager = initTunnelManager();
217 -
218 - switch (type) {
219 - case 'WEBSOCKET_OPEN':
220 - try {
221 - const clientId = event.source?.id || event.clientId;
222 - const result = await manager.createTunnel(url, protocols, clientId);
223 -
224 - event.ports[0]?.postMessage({
225 - success: true,
226 - result
227 - });
228 - } catch (error) {
229 - event.ports[0]?.postMessage({
230 - success: false,
231 - error: error.toString()
232 - });
233 - }
234 - break;
235 -
236 - case 'WEBSOCKET_SEND':
237 - try {
238 - await manager.sendMessage(tunnelId, data, isBinary);
239 - event.ports[0]?.postMessage({ success: true });
240 - } catch (error) {
241 - event.ports[0]?.postMessage({
242 - success: false,
243 - error: error.toString()
244 - });
245 - }
246 - break;
247 -
248 - case 'WEBSOCKET_CLOSE':
249 - try {
250 - await manager.closeTunnel(tunnelId, code, reason);
251 - event.ports[0]?.postMessage({ success: true });
252 - } catch (error) {
253 - event.ports[0]?.postMessage({
254 - success: false,
255 - error: error.toString()
256 - });
257 - }
258 - break;
259 -
260 - case 'WEBSOCKET_STATE':
261 - const state = manager.getTunnelState(tunnelId);
262 - event.ports[0]?.postMessage({
263 - success: true,
264 - state
265 - });
266 - break;
267 -
268 - case 'WEBSOCKET_LIST':
269 - const tunnels = manager.getAllTunnels();
270 - event.ports[0]?.postMessage({
271 - success: true,
272 - tunnels
273 - });
274 - break;
275 -
276 - default:
277 - return false; // Not handled
278 - }
279 -
280 - return true; // Handled
281 -}
282 -
283 -console.log('[SW-WebSocket] Service Worker WebSocket module loaded');
portal/wasm/secure-websocket.js deleted
-710
@@ -1,710 +0,0 @@
1 -/**
2 - * SecureWebSocket - E2EE WebSocket Polyfill using WASM ProxyEngine
3 - *
4 - * Provides transparent end-to-end encryption for WebSocket connections
5 - * through the Portal WASM ProxyEngine.
6 - */
7 -
8 -// Global WASM instance cache
9 -let wasmInstance = null;
10 -let wasmInitPromise = null;
11 -
12 -/**
13 - * Get Relay server URL from server or config
14 - * @returns {Promise<string>} Relay server WebSocket URL
15 - */
16 -async function getRelayUrl() {
17 - // 1. Check if manually configured
18 - if (window.PORTAL_RELAY_URL) {
19 - console.log('[SecureWebSocket] Using configured relay URL:', window.PORTAL_RELAY_URL);
20 - return window.PORTAL_RELAY_URL;
21 - }
22 -
23 - // 2. Try to get from server API
24 - try {
25 - const response = await fetch('/api/relay-info');
26 - if (response.ok) {
27 - const data = await response.json();
28 - if (data.relayUrl) {
29 - console.log('[SecureWebSocket] Got relay URL from server:', data.relayUrl);
30 - return data.relayUrl;
31 - }
32 - }
33 - } catch (error) {
34 - console.warn('[SecureWebSocket] Failed to fetch relay info from server:', error.message);
35 - }
36 -
37 - // 3. Auto-detect from current location
38 - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
39 - const host = window.location.host;
40 - const autoUrl = `${protocol}//${host}/relay`;
41 -
42 - console.log('[SecureWebSocket] Auto-detected relay URL:', autoUrl);
43 - return autoUrl;
44 -}
45 -
46 -/**
47 - * Check if Service Worker is available and has WebSocket support
48 - * @returns {Promise<boolean>}
49 - */
50 -async function hasServiceWorkerWebSocket() {
51 - if (!navigator.serviceWorker || !navigator.serviceWorker.controller) {
52 - return false;
53 - }
54 -
55 - try {
56 - const channel = new MessageChannel();
57 - const response = await new Promise((resolve) => {
58 - channel.port1.onmessage = (event) => resolve(event.data);
59 - navigator.serviceWorker.controller.postMessage(
60 - { type: 'GET_STATUS' },
61 - [channel.port2]
62 - );
63 - setTimeout(() => resolve({ success: false }), 1000);
64 - });
65 -
66 - return response.success && response.status?.hasWebSocket;
67 - } catch {
68 - return false;
69 - }
70 -}
71 -
72 -/**
73 - * Initialize and get the WASM ProxyEngine instance
74 - * @returns {Promise<Object>} WASM module with ProxyEngine
75 - */
76 -async function getProxyEngine() {
77 - if (wasmInstance) {
78 - return wasmInstance;
79 - }
80 -
81 - if (wasmInitPromise) {
82 - return wasmInitPromise;
83 - }
84 -
85 - wasmInitPromise = (async () => {
86 - try {
87 - // Check if we should use Service Worker
88 - const useServiceWorker = await hasServiceWorkerWebSocket();
89 -
90 - if (useServiceWorker) {
91 - console.log('[SecureWebSocket] Using Service Worker for WebSocket');
92 - wasmInstance = {
93 - engine: null,
94 - wasm: null,
95 - useServiceWorker: true
96 - };
97 - return wasmInstance;
98 - }
99 -
100 - // Fallback to direct WASM
101 - console.log('[SecureWebSocket] Using direct WASM');
102 -
103 - // Load WASM module
104 - if (typeof wasm_bindgen === 'undefined') {
105 - throw new Error('WASM module not loaded. Include portal_wasm.js first.');
106 - }
107 -
108 - // Initialize WASM
109 - await wasm_bindgen('/pkg/portal_wasm_bg.wasm');
110 -
111 - // Get relay server URL
112 - const relayUrl = await getRelayUrl();
113 -
114 - // Create ProxyEngine instance
115 - const engine = new wasm_bindgen.ProxyEngine(relayUrl);
116 -
117 - console.log('[SecureWebSocket] WASM ProxyEngine initialized:', relayUrl);
118 -
119 - wasmInstance = {
120 - engine,
121 - wasm: wasm_bindgen,
122 - useServiceWorker: false
123 - };
124 -
125 - return wasmInstance;
126 -
127 - } catch (error) {
128 - console.error('[SecureWebSocket] Failed to initialize WASM:', error);
129 - wasmInitPromise = null;
130 - throw error;
131 - }
132 - })();
133 -
134 - return wasmInitPromise;
135 -}
136 -
137 -/**
138 - * SecureWebSocket - Drop-in replacement for native WebSocket with E2EE
139 - */
140 -class SecureWebSocket extends EventTarget {
141 - /**
142 - * @param {string} url - WebSocket URL
143 - * @param {string|string[]} protocols - Optional subprotocols
144 - */
145 - constructor(url, protocols = []) {
146 - super();
147 -
148 - // Normalize protocols
149 - if (typeof protocols === 'string') {
150 - protocols = [protocols];
151 - }
152 -
153 - // Public properties (read-only)
154 - Object.defineProperties(this, {
155 - url: { value: url, writable: false, enumerable: true },
156 - protocols: { value: protocols, writable: false, enumerable: true },
157 - });
158 -
159 - // Internal state
160 - this._readyState = WebSocket.CONNECTING;
161 - this._protocol = '';
162 - this._tunnelId = null;
163 - this._bufferedAmount = 0;
164 - this._extensions = '';
165 - this._binaryType = 'blob';
166 -
167 - // Event handlers (nullable)
168 - this.onopen = null;
169 - this.onmessage = null;
170 - this.onerror = null;
171 - this.onclose = null;
172 -
173 - // Start connection
174 - this._connect();
175 - }
176 -
177 - // Public properties with getters
178 - get readyState() { return this._readyState; }
179 - get protocol() { return this._protocol; }
180 - get bufferedAmount() { return this._bufferedAmount; }
181 - get extensions() { return this._extensions; }
182 - get binaryType() { return this._binaryType; }
183 - set binaryType(value) {
184 - if (value === 'blob' || value === 'arraybuffer') {
185 - this._binaryType = value;
186 - }
187 - }
188 -
189 - /**
190 - * Initialize connection through WASM ProxyEngine
191 - * @private
192 - */
193 - async _connect() {
194 - try {
195 - console.log('[SecureWebSocket] Connecting to:', this.url);
196 -
197 - // Get WASM ProxyEngine or Service Worker
198 - const instance = await getProxyEngine();
199 -
200 - if (instance.useServiceWorker) {
201 - // Use Service Worker
202 - await this._connectViaServiceWorker();
203 - } else {
204 - // Use direct WASM
205 - await this._connectViaDirect(instance.engine);
206 - }
207 -
208 - } catch (error) {
209 - console.error('[SecureWebSocket] Connection failed:', error);
210 -
211 - this._readyState = WebSocket.CLOSED;
212 -
213 - // Dispatch error event
214 - this._dispatchEvent('error', {
215 - message: error.toString(),
216 - error: error
217 - });
218 -
219 - // Dispatch close event
220 - this._dispatchEvent('close', {
221 - code: 1006,
222 - reason: error.toString(),
223 - wasClean: false
224 - });
225 - }
226 - }
227 -
228 - /**
229 - * Connect via Service Worker
230 - * @private
231 - */
232 - async _connectViaServiceWorker() {
233 - console.log('[SecureWebSocket] Connecting via Service Worker');
234 -
235 - const channel = new MessageChannel();
236 - const response = await new Promise((resolve, reject) => {
237 - channel.port1.onmessage = (event) => {
238 - if (event.data.success) {
239 - resolve(event.data.result);
240 - } else {
241 - reject(new Error(event.data.error));
242 - }
243 - };
244 -
245 - navigator.serviceWorker.controller.postMessage(
246 - {
247 - type: 'WEBSOCKET_OPEN',
248 - url: this.url,
249 - protocols: this.protocols
250 - },
251 - [channel.port2]
252 - );
253 -
254 - setTimeout(() => reject(new Error('Service Worker timeout')), 10000);
255 - });
256 -
257 - this._tunnelId = response.tunnelId;
258 - this._protocol = response.protocol || '';
259 - this._readyState = WebSocket.OPEN;
260 - this._useServiceWorker = true;
261 -
262 - console.log('[SecureWebSocket] Connected via SW! Tunnel ID:', this._tunnelId);
263 -
264 - // Dispatch open event
265 - this._dispatchEvent('open', {});
266 -
267 - // Listen for messages from Service Worker
268 - this._listenToServiceWorker();
269 - }
270 -
271 - /**
272 - * Connect via direct WASM
273 - * @private
274 - */
275 - async _connectViaDirect(engine) {
276 - console.log('[SecureWebSocket] Connecting via direct WASM');
277 -
278 - // Open WebSocket tunnel through E2EE proxy
279 - const result = await engine.open_websocket(this.url, this.protocols);
280 -
281 - this._tunnelId = result.tunnelId;
282 - this._protocol = result.protocol || '';
283 - this._readyState = WebSocket.OPEN;
284 - this._useServiceWorker = false;
285 -
286 - console.log('[SecureWebSocket] Connected! Tunnel ID:', this._tunnelId);
287 -
288 - // Dispatch open event
289 - this._dispatchEvent('open', {});
290 -
291 - // Start receiving messages in background
292 - this._receiveLoop(engine);
293 - }
294 -
295 - /**
296 - * Listen to Service Worker messages
297 - * @private
298 - */
299 - _listenToServiceWorker() {
300 - const handler = (event) => {
301 - if (event.data.type === 'WEBSOCKET_MESSAGE' &&
302 - event.data.tunnelId === this._tunnelId) {
303 -
304 - const msg = event.data.message;
305 - this._handleMessage(msg);
306 - }
307 - };
308 -
309 - navigator.serviceWorker.addEventListener('message', handler);
310 - this._swMessageHandler = handler;
311 - }
312 -
313 - /**
314 - * Handle incoming message
315 - * @private
316 - */
317 - _handleMessage(msg) {
318 - if (msg.type === 'text') {
319 - // Text message
320 - this._dispatchEvent('message', {
321 - data: msg.data,
322 - type: 'message',
323 - origin: this.url
324 - });
325 -
326 - } else if (msg.type === 'binary') {
327 - // Binary message
328 - let data;
329 - if (this._binaryType === 'arraybuffer') {
330 - data = new Uint8Array(msg.data).buffer;
331 - } else {
332 - data = new Blob([new Uint8Array(msg.data)]);
333 - }
334 -
335 - this._dispatchEvent('message', {
336 - data: data,
337 - type: 'message',
338 - origin: this.url
339 - });
340 -
341 - } else if (msg.type === 'close') {
342 - // Close message
343 - console.log('[SecureWebSocket] Received close:', msg.code, msg.reason);
344 -
345 - this._readyState = WebSocket.CLOSED;
346 -
347 - this._dispatchEvent('close', {
348 - code: msg.code || 1000,
349 - reason: msg.reason || '',
350 - wasClean: true
351 - });
352 -
353 - // Cleanup Service Worker listener
354 - if (this._swMessageHandler) {
355 - navigator.serviceWorker.removeEventListener('message', this._swMessageHandler);
356 - }
357 - }
358 - }
359 -
360 - /**
361 - * Background loop to receive messages from tunnel
362 - * @private
363 - * @param {Object} engine - WASM ProxyEngine instance
364 - */
365 - async _receiveLoop(engine) {
366 - try {
367 - while (this._readyState !== WebSocket.CLOSED && this._readyState !== WebSocket.CLOSING) {
368 - // Receive message from tunnel
369 - const msg = await engine.receive_websocket_message(this._tunnelId);
370 -
371 - if (msg.type === 'text') {
372 - // Text message
373 - this._dispatchEvent('message', {
374 - data: msg.data,
375 - type: 'message',
376 - origin: this.url
377 - });
378 -
379 - } else if (msg.type === 'binary') {
380 - // Binary message
381 - let data;
382 - if (this._binaryType === 'arraybuffer') {
383 - data = new Uint8Array(msg.data).buffer;
384 - } else {
385 - // Convert to Blob
386 - data = new Blob([new Uint8Array(msg.data)]);
387 - }
388 -
389 - this._dispatchEvent('message', {
390 - data: data,
391 - type: 'message',
392 - origin: this.url
393 - });
394 -
395 - } else if (msg.type === 'close') {
396 - // Close message
397 - console.log('[SecureWebSocket] Received close:', msg.code, msg.reason);
398 -
399 - this._readyState = WebSocket.CLOSED;
400 -
401 - this._dispatchEvent('close', {
402 - code: msg.code || 1000,
403 - reason: msg.reason || '',
404 - wasClean: true
405 - });
406 -
407 - break;
408 - }
409 - }
410 -
411 - } catch (error) {
412 - console.error('[SecureWebSocket] Receive loop error:', error);
413 -
414 - if (this._readyState !== WebSocket.CLOSED) {
415 - this._dispatchEvent('error', {
416 - message: error.toString(),
417 - error: error
418 - });
419 -
420 - this.close(1006, error.toString());
421 - }
422 - }
423 - }
424 -
425 - /**
426 - * Send data through the secure tunnel
427 - * @param {string|ArrayBuffer|Uint8Array|Blob} data - Data to send
428 - */
429 - send(data) {
430 - if (this._readyState !== WebSocket.OPEN) {
431 - throw new DOMException(
432 - 'Failed to execute \'send\' on \'WebSocket\': Still in CONNECTING state.',
433 - 'InvalidStateError'
434 - );
435 - }
436 -
437 - // Handle different data types
438 - if (typeof data === 'string') {
439 - // Text message
440 - this._sendMessage(data, false);
441 -
442 - } else if (data instanceof ArrayBuffer) {
443 - // Binary ArrayBuffer
444 - this._sendMessage(new Uint8Array(data), true);
445 -
446 - } else if (data instanceof Uint8Array) {
447 - // Binary Uint8Array
448 - this._sendMessage(data, true);
449 -
450 - } else if (data instanceof Blob) {
451 - // Blob - convert to ArrayBuffer
452 - this._bufferedAmount += data.size;
453 -
454 - data.arrayBuffer().then(buffer => {
455 - this._sendMessage(new Uint8Array(buffer), true);
456 - this._bufferedAmount = Math.max(0, this._bufferedAmount - data.size);
457 - });
458 -
459 - } else {
460 - throw new TypeError('Data must be string, ArrayBuffer, Uint8Array, or Blob');
461 - }
462 - }
463 -
464 - /**
465 - * Send message through WASM ProxyEngine
466 - * @private
467 - * @param {string|Uint8Array} data - Data to send
468 - * @param {boolean} isBinary - Whether data is binary
469 - */
470 - async _sendMessage(data, isBinary) {
471 - try {
472 - // Estimate buffer size
473 - const size = typeof data === 'string' ? data.length : data.length;
474 - this._bufferedAmount += size;
475 -
476 - if (this._useServiceWorker) {
477 - // Send via Service Worker
478 - const channel = new MessageChannel();
479 - await new Promise((resolve, reject) => {
480 - channel.port1.onmessage = (event) => {
481 - if (event.data.success) {
482 - resolve();
483 - } else {
484 - reject(new Error(event.data.error));
485 - }
486 - };
487 -
488 - navigator.serviceWorker.controller.postMessage(
489 - {
490 - type: 'WEBSOCKET_SEND',
491 - tunnelId: this._tunnelId,
492 - data: data,
493 - isBinary: isBinary
494 - },
495 - [channel.port2]
496 - );
497 -
498 - setTimeout(() => reject(new Error('Send timeout')), 5000);
499 - });
500 - } else {
501 - // Send via direct WASM
502 - const { engine } = await getProxyEngine();
503 - await engine.send_websocket_message(this._tunnelId, data, isBinary);
504 - }
505 -
506 - // Decrement buffered amount
507 - this._bufferedAmount = Math.max(0, this._bufferedAmount - size);
508 -
509 - } catch (error) {
510 - console.error('[SecureWebSocket] Send failed:', error);
511 -
512 - this._dispatchEvent('error', {
513 - message: error.toString(),
514 - error: error
515 - });
516 - }
517 - }
518 -
519 - /**
520 - * Close the WebSocket connection
521 - * @param {number} code - Close code (default 1000)
522 - * @param {string} reason - Close reason (default empty)
523 - */
524 - close(code = 1000, reason = '') {
525 - if (this._readyState === WebSocket.CLOSED || this._readyState === WebSocket.CLOSING) {
526 - return;
527 - }
528 -
529 - console.log('[SecureWebSocket] Closing:', code, reason);
530 -
531 - this._readyState = WebSocket.CLOSING;
532 -
533 - // Close tunnel
534 - (async () => {
535 - try {
536 - if (this._useServiceWorker) {
537 - // Close via Service Worker
538 - const channel = new MessageChannel();
539 - await new Promise((resolve, reject) => {
540 - channel.port1.onmessage = (event) => {
541 - if (event.data.success) {
542 - resolve();
543 - } else {
544 - reject(new Error(event.data.error));
545 - }
546 - };
547 -
548 - navigator.serviceWorker.controller.postMessage(
549 - {
550 - type: 'WEBSOCKET_CLOSE',
551 - tunnelId: this._tunnelId,
552 - code: code,
553 - reason: reason
554 - },
555 - [channel.port2]
556 - );
557 -
558 - setTimeout(() => resolve(), 2000); // Don't wait forever
559 - });
560 - } else {
561 - // Close via direct WASM
562 - const { engine } = await getProxyEngine();
563 - await engine.close_websocket(this._tunnelId, code, reason);
564 - }
565 - } catch (error) {
566 - console.error('[SecureWebSocket] Close failed:', error);
567 -
568 - // Force close
569 - this._readyState = WebSocket.CLOSED;
570 - this._dispatchEvent('close', {
571 - code: 1006,
572 - reason: error.toString(),
573 - wasClean: false
574 - });
575 - }
576 - })();
577 - }
578 -
579 - /**
580 - * Dispatch event to both EventTarget and legacy handler
581 - * @private
582 - * @param {string} type - Event type
583 - * @param {Object} detail - Event details
584 - */
585 - _dispatchEvent(type, detail) {
586 - // Create event
587 - const event = new Event(type);
588 - Object.assign(event, detail);
589 -
590 - // Dispatch to EventTarget listeners
591 - this.dispatchEvent(event);
592 -
593 - // Call legacy handler if exists
594 - const handler = this[`on${type}`];
595 - if (typeof handler === 'function') {
596 - try {
597 - handler.call(this, event);
598 - } catch (error) {
599 - console.error(`[SecureWebSocket] Error in on${type} handler:`, error);
600 - }
601 - }
602 - }
603 -}
604 -
605 -// Static constants (same as native WebSocket)
606 -SecureWebSocket.CONNECTING = 0;
607 -SecureWebSocket.OPEN = 1;
608 -SecureWebSocket.CLOSING = 2;
609 -SecureWebSocket.CLOSED = 3;
610 -
611 -// ==============================================================================
612 -// POLYFILL: Replace native WebSocket with SecureWebSocket
613 -// ==============================================================================
614 -
615 -(function() {
616 - // Save reference to native WebSocket
617 - const NativeWebSocket = window.WebSocket;
618 -
619 - // Configuration
620 - const config = {
621 - // Enable E2EE for all WebSockets by default
622 - enabled: window.PORTAL_E2EE_ENABLED !== false,
623 -
624 - // Patterns to intercept (regex strings)
625 - interceptPatterns: window.PORTAL_INTERCEPT_PATTERNS || [
626 - '.*' // Intercept all by default
627 - ],
628 -
629 - // Patterns to bypass (regex strings) - takes precedence
630 - bypassPatterns: window.PORTAL_BYPASS_PATTERNS || [
631 - '^wss?://localhost:4017/', // Don't intercept relay server itself
632 - '^wss?://localhost:8000/', // Don't intercept local dev server
633 - '^wss?://127\\.0\\.0\\.1', // Don't intercept loopback
634 - ],
635 -
636 - // Debug mode
637 - debug: window.PORTAL_DEBUG || false
638 - };
639 -
640 - /**
641 - * Check if URL should be intercepted for E2EE
642 - * @param {string} url - WebSocket URL
643 - * @returns {boolean} True if should intercept
644 - */
645 - function shouldIntercept(url) {
646 - if (!config.enabled) {
647 - return false;
648 - }
649 -
650 - // Check bypass patterns first (higher priority)
651 - for (const pattern of config.bypassPatterns) {
652 - const regex = new RegExp(pattern);
653 - if (regex.test(url)) {
654 - if (config.debug) {
655 - console.log('[SecureWebSocket] Bypassing (matched bypass pattern):', url);
656 - }
657 - return false;
658 - }
659 - }
660 -
661 - // Check intercept patterns
662 - for (const pattern of config.interceptPatterns) {
663 - const regex = new RegExp(pattern);
664 - if (regex.test(url)) {
665 - if (config.debug) {
666 - console.log('[SecureWebSocket] Intercepting (matched intercept pattern):', url);
667 - }
668 - return true;
669 - }
670 - }
671 -
672 - if (config.debug) {
673 - console.log('[SecureWebSocket] Not intercepting (no match):', url);
674 - }
675 - return false;
676 - }
677 -
678 - /**
679 - * Polyfilled WebSocket constructor
680 - * @param {string} url - WebSocket URL
681 - * @param {string|string[]} protocols - Optional subprotocols
682 - * @returns {WebSocket|SecureWebSocket}
683 - */
684 - window.WebSocket = function(url, protocols) {
685 - if (shouldIntercept(url)) {
686 - // Use E2EE SecureWebSocket
687 - console.log('[SecureWebSocket] 🔒 Creating encrypted WebSocket:', url);
688 - return new SecureWebSocket(url, protocols);
689 - } else {
690 - // Use native WebSocket
691 - if (config.debug) {
692 - console.log('[SecureWebSocket] Creating native WebSocket:', url);
693 - }
694 - return new NativeWebSocket(url, protocols);
695 - }
696 - };
697 -
698 - // Copy static properties from native WebSocket
699 - window.WebSocket.CONNECTING = NativeWebSocket.CONNECTING;
700 - window.WebSocket.OPEN = NativeWebSocket.OPEN;
701 - window.WebSocket.CLOSING = NativeWebSocket.CLOSING;
702 - window.WebSocket.CLOSED = NativeWebSocket.CLOSED;
703 -
704 - // Expose SecureWebSocket class for direct access if needed
705 - window.SecureWebSocket = SecureWebSocket;
706 - window.NativeWebSocket = NativeWebSocket;
707 -
708 - console.log('[SecureWebSocket] ✅ Polyfill installed. E2EE enabled:', config.enabled);
709 -
710 -})();
portal/wasm/src/adapters.rs deleted
-400
@@ -1,400 +0,0 @@
1 -/// Data adapters for browser integration
2 -/// Handles HTTP transfers (files, API) and WebSocket data interpretation
3 -use serde::{Deserialize, Serialize};
4 -use wasm_bindgen::prelude::*;
5 -use web_sys::{Blob, FormData, Headers, Request, RequestInit, Response};
6 -
7 -/// HTTP Adapter for file and API transfers
8 -#[wasm_bindgen]
9 -pub struct HttpAdapter {
10 - base_url: String,
11 -}
12 -
13 -#[wasm_bindgen]
14 -impl HttpAdapter {
15 - #[wasm_bindgen(constructor)]
16 - pub fn new(base_url: String) -> Self {
17 - Self { base_url }
18 - }
19 -
20 - /// Send GET request
21 - #[wasm_bindgen(js_name = get)]
22 - pub async fn get(&self, path: String) -> Result<JsValue, JsValue> {
23 - let url = format!("{}{}", self.base_url, path);
24 -
25 - let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
26 - let response = window.fetch_with_str(&url).into_future().await?;
27 -
28 - let response: Response = response.dyn_into()?;
29 -
30 - if !response.ok() {
31 - return Err(JsValue::from_str(&format!(
32 - "HTTP error: {}",
33 - response.status()
34 - )));
35 - }
36 -
37 - response.json()?.into_future().await
38 - }
39 -
40 - /// Send POST request with JSON body
41 - #[wasm_bindgen(js_name = postJson)]
42 - pub async fn post_json(&self, path: String, body: JsValue) -> Result<JsValue, JsValue> {
43 - let url = format!("{}{}", self.base_url, path);
44 -
45 - let body_str = js_sys::JSON::stringify(&body)?;
46 -
47 - let opts = RequestInit::new();
48 - opts.set_method("POST");
49 - let body_value: JsValue = body_str.into();
50 - opts.set_body(&body_value);
51 -
52 - let headers = Headers::new()?;
53 - headers.set("Content-Type", "application/json")?;
54 -
55 - let request = Request::new_with_str_and_init(&url, &opts)?;
56 - request.headers().set("Content-Type", "application/json")?;
57 -
58 - let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
59 - let response = window.fetch_with_request(&request).into_future().await?;
60 -
61 - let response: Response = response.dyn_into()?;
62 -
63 - if !response.ok() {
64 - return Err(JsValue::from_str(&format!(
65 - "HTTP error: {}",
66 - response.status()
67 - )));
68 - }
69 -
70 - response.json()?.into_future().await
71 - }
72 -
73 - /// Upload file
74 - #[wasm_bindgen(js_name = uploadFile)]
75 - pub async fn upload_file(
76 - &self,
77 - path: String,
78 - file_name: String,
79 - file_data: Vec<u8>,
80 - ) -> Result<JsValue, JsValue> {
81 - let url = format!("{}{}", self.base_url, path);
82 -
83 - // Create Blob from file data
84 - let array = js_sys::Uint8Array::from(&file_data[..]);
85 - let blob = Blob::new_with_u8_array_sequence(&js_sys::Array::of1(&array))?;
86 -
87 - // Create FormData
88 - let form_data = FormData::new()?;
89 - form_data.append_with_blob(&file_name, &blob)?;
90 -
91 - // Create request
92 - let opts = RequestInit::new();
93 - opts.set_method("POST");
94 - let form_value: JsValue = form_data.into();
95 - opts.set_body(&form_value);
96 -
97 - let request = Request::new_with_str_and_init(&url, &opts)?;
98 -
99 - let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
100 - let response = window.fetch_with_request(&request).into_future().await?;
101 -
102 - let response: Response = response.dyn_into()?;
103 -
104 - if !response.ok() {
105 - return Err(JsValue::from_str(&format!(
106 - "HTTP error: {}",
107 - response.status()
108 - )));
109 - }
110 -
111 - response.json()?.into_future().await
112 - }
113 -
114 - /// Download file
115 - #[wasm_bindgen(js_name = downloadFile)]
116 - pub async fn download_file(&self, path: String) -> Result<Vec<u8>, JsValue> {
117 - let url = format!("{}{}", self.base_url, path);
118 -
119 - let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
120 - let response = window.fetch_with_str(&url).into_future().await?;
121 -
122 - let response: Response = response.dyn_into()?;
123 -
124 - if !response.ok() {
125 - return Err(JsValue::from_str(&format!(
126 - "HTTP error: {}",
127 - response.status()
128 - )));
129 - }
130 -
131 - let array_buffer = response.array_buffer()?.into_future().await?;
132 - let array = js_sys::Uint8Array::new(&array_buffer);
133 - Ok(array.to_vec())
134 - }
135 -}
136 -
137 -/// WebSocket Data Adapter for browser
138 -#[wasm_bindgen]
139 -pub struct WebSocketAdapter {
140 - url: String,
141 - ws: Option<web_sys::WebSocket>,
142 - message_callback: Option<js_sys::Function>,
143 - error_callback: Option<js_sys::Function>,
144 -}
145 -
146 -#[wasm_bindgen]
147 -impl WebSocketAdapter {
148 - #[wasm_bindgen(constructor)]
149 - pub fn new(url: String) -> Self {
150 - Self {
151 - url,
152 - ws: None,
153 - message_callback: None,
154 - error_callback: None,
155 - }
156 - }
157 -
158 - /// Connect to WebSocket
159 - #[wasm_bindgen]
160 - pub async fn connect(&mut self) -> Result<(), JsValue> {
161 - let ws = web_sys::WebSocket::new(&self.url)?;
162 - ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
163 -
164 - // Wait for connection
165 - let (tx, rx) = futures::channel::oneshot::channel();
166 - let tx = std::sync::Arc::new(parking_lot::Mutex::new(Some(tx)));
167 -
168 - {
169 - let tx = tx.clone();
170 - let onopen = wasm_bindgen::closure::Closure::wrap(Box::new(move |_| {
171 - if let Some(tx) = tx.lock().take() {
172 - let _ = tx.send(());
173 - }
174 - })
175 - as Box<dyn FnMut(JsValue)>);
176 -
177 - ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));
178 - onopen.forget();
179 - }
180 -
181 - rx.await
182 - .map_err(|_| JsValue::from_str("connection failed"))?;
183 -
184 - self.ws = Some(ws);
185 - Ok(())
186 - }
187 -
188 - /// Set message callback
189 - #[wasm_bindgen(js_name = onMessage)]
190 - pub fn on_message(&mut self, callback: js_sys::Function) {
191 - self.message_callback = Some(callback.clone());
192 -
193 - if let Some(ws) = &self.ws {
194 - let cb = callback;
195 - let onmessage =
196 - wasm_bindgen::closure::Closure::wrap(Box::new(move |e: web_sys::MessageEvent| {
197 - // Parse message data
198 - if let Ok(array_buffer) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
199 - let array = js_sys::Uint8Array::new(&array_buffer);
200 - let data = array.to_vec();
201 -
202 - // Convert to JS object
203 - let obj = js_sys::Object::new();
204 - js_sys::Reflect::set(&obj, &"type".into(), &"binary".into()).unwrap();
205 - js_sys::Reflect::set(
206 - &obj,
207 - &"data".into(),
208 - &js_sys::Uint8Array::from(&data[..]),
209 - )
210 - .unwrap();
211 -
212 - // Call callback
213 - let _ = cb.call1(&JsValue::NULL, &obj);
214 - } else if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
215 - let obj = js_sys::Object::new();
216 - js_sys::Reflect::set(&obj, &"type".into(), &"text".into()).unwrap();
217 - js_sys::Reflect::set(&obj, &"data".into(), &text).unwrap();
218 -
219 - let _ = cb.call1(&JsValue::NULL, &obj);
220 - }
221 - })
222 - as Box<dyn FnMut(web_sys::MessageEvent)>);
223 -
224 - ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
225 - onmessage.forget();
226 - }
227 - }
228 -
229 - /// Set error callback
230 - #[wasm_bindgen(js_name = onError)]
231 - pub fn on_error(&mut self, callback: js_sys::Function) {
232 - self.error_callback = Some(callback.clone());
233 -
234 - if let Some(ws) = &self.ws {
235 - let cb = callback;
236 - let onerror =
237 - wasm_bindgen::closure::Closure::wrap(Box::new(move |e: web_sys::ErrorEvent| {
238 - let obj = js_sys::Object::new();
239 - let msg: JsValue = e.message().into();
240 - js_sys::Reflect::set(&obj, &"error".into(), &msg).unwrap();
241 - let _ = cb.call1(&JsValue::NULL, &obj);
242 - })
243 - as Box<dyn FnMut(web_sys::ErrorEvent)>);
244 -
245 - ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
246 - onerror.forget();
247 - }
248 - }
249 -
250 - /// Send text message
251 - #[wasm_bindgen(js_name = sendText)]
252 - pub fn send_text(&self, message: String) -> Result<(), JsValue> {
253 - if let Some(ws) = &self.ws {
254 - ws.send_with_str(&message)?;
255 - Ok(())
256 - } else {
257 - Err(JsValue::from_str("not connected"))
258 - }
259 - }
260 -
261 - /// Send binary message
262 - #[wasm_bindgen(js_name = sendBinary)]
263 - pub fn send_binary(&self, data: Vec<u8>) -> Result<(), JsValue> {
264 - if let Some(ws) = &self.ws {
265 - ws.send_with_u8_array(&data)?;
266 - Ok(())
267 - } else {
268 - Err(JsValue::from_str("not connected"))
269 - }
270 - }
271 -
272 - /// Close connection
273 - #[wasm_bindgen]
274 - pub fn close(&self) -> Result<(), JsValue> {
275 - if let Some(ws) = &self.ws {
276 - ws.close()?;
277 - }
278 - Ok(())
279 - }
280 -}
281 -
282 -/// Message types for structured communication
283 -#[derive(Serialize, Deserialize, Debug, Clone)]
284 -#[serde(tag = "type")]
285 -pub enum Message {
286 - #[serde(rename = "text")]
287 - Text { data: String },
288 -
289 - #[serde(rename = "binary")]
290 - Binary { data: Vec<u8> },
291 -
292 - #[serde(rename = "file")]
293 - File {
294 - name: String,
295 - size: usize,
296 - mime_type: String,
297 - data: Vec<u8>,
298 - },
299 -
300 - #[serde(rename = "api")]
301 - Api {
302 - endpoint: String,
303 - method: String,
304 - headers: std::collections::HashMap<String, String>,
305 - body: Option<Vec<u8>>,
306 - },
307 -}
308 -
309 -/// Data interpreter for converting relay protocol to browser-friendly format
310 -#[wasm_bindgen]
311 -pub struct DataInterpreter;
312 -
313 -#[wasm_bindgen]
314 -impl DataInterpreter {
315 - /// Parse relay protocol packet to browser message
316 - #[wasm_bindgen(js_name = parsePacket)]
317 - pub fn parse_packet(data: Vec<u8>) -> Result<JsValue, JsValue> {
318 - // Check packet header to determine type
319 - if data.len() < 4 {
320 - return Err(JsValue::from_str("packet too short"));
321 - }
322 -
323 - let packet_type = data[0];
324 -
325 - match packet_type {
326 - 0x01 => {
327 - // Text message
328 - let text = String::from_utf8(data[4..].to_vec())
329 - .map_err(|e| JsValue::from_str(&format!("utf8 error: {}", e)))?;
330 -
331 - let msg = Message::Text { data: text };
332 - serde_wasm_bindgen::to_value(&msg)
333 - .map_err(|e| JsValue::from_str(&format!("serialize error: {}", e)))
334 - }
335 - 0x02 => {
336 - // Binary data
337 - let msg = Message::Binary {
338 - data: data[4..].to_vec(),
339 - };
340 - serde_wasm_bindgen::to_value(&msg)
341 - .map_err(|e| JsValue::from_str(&format!("serialize error: {}", e)))
342 - }
343 - _ => Err(JsValue::from_str(&format!(
344 - "unknown packet type: {}",
345 - packet_type
346 - ))),
347 - }
348 - }
349 -
350 - /// Create relay protocol packet from browser message
351 - #[wasm_bindgen(js_name = createPacket)]
352 - pub fn create_packet(msg: JsValue) -> Result<Vec<u8>, JsValue> {
353 - let msg: Message = serde_wasm_bindgen::from_value(msg)
354 - .map_err(|e| JsValue::from_str(&format!("deserialize error: {}", e)))?;
355 -
356 - let mut packet = Vec::new();
357 -
358 - match msg {
359 - Message::Text { data } => {
360 - packet.push(0x01); // Type: text
361 - packet.extend_from_slice(&[0, 0, 0]); // Reserved
362 - packet.extend_from_slice(data.as_bytes());
363 - }
364 - Message::Binary { data } => {
365 - packet.push(0x02); // Type: binary
366 - packet.extend_from_slice(&[0, 0, 0]); // Reserved
367 - packet.extend_from_slice(&data);
368 - }
369 - Message::File { data, .. } => {
370 - packet.push(0x03); // Type: file
371 - packet.extend_from_slice(&[0, 0, 0]); // Reserved
372 - packet.extend_from_slice(&data);
373 - }
374 - Message::Api { body, .. } => {
375 - packet.push(0x04); // Type: API
376 - packet.extend_from_slice(&[0, 0, 0]); // Reserved
377 - if let Some(body) = body {
378 - packet.extend_from_slice(&body);
379 - }
380 - }
381 - }
382 -
383 - Ok(packet)
384 - }
385 -}
386 -
387 -use wasm_bindgen_futures::JsFuture;
388 -
389 -trait IntoFuture {
390 - type Output;
391 - fn into_future(self) -> JsFuture;
392 -}
393 -
394 -impl IntoFuture for js_sys::Promise {
395 - type Output = JsValue;
396 -
397 - fn into_future(self) -> JsFuture {
398 - JsFuture::from(self)
399 - }
400 -}
portal/wasm/src/crypto.rs deleted
-289
@@ -1,289 +0,0 @@
1 -use crate::proto::{self, Identity, ProtocolVersion, SignedPayload};
2 -use crate::utils;
3 -use chacha20poly1305::aead::Aead;
4 -use chacha20poly1305::{ChaCha20Poly1305, KeyInit};
5 -use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
6 -use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
7 -use hkdf::Hkdf;
8 -use sha2::Sha256;
9 -use std::io;
10 -use x25519_dalek::{EphemeralSecret, PublicKey};
11 -
12 -const NONCE_SIZE: usize = 12;
13 -const SESSION_KEY_SIZE: usize = 32;
14 -const MAX_RAW_PACKET_SIZE: usize = 1 << 26; // 64MB
15 -
16 -const CLIENT_KEY_INFO: &[u8] = b"RDSEC_KEY_CLIENT";
17 -const SERVER_KEY_INFO: &[u8] = b"RDSEC_KEY_SERVER";
18 -
19 -#[derive(Clone)]
20 -pub struct Credential {
21 - signing_key: SigningKey,
22 - id: String,
23 -}
24 -
25 -impl Credential {
26 - /// Create a new credential with a random key
27 - pub fn new() -> Self {
28 - let signing_key = SigningKey::generate(&mut rand_core::OsRng);
29 - let id = hex::encode(signing_key.verifying_key().as_bytes());
30 -
31 - Self { signing_key, id }
32 - }
33 -
34 - /// Get the credential ID
35 - pub fn id(&self) -> &str {
36 - &self.id
37 - }
38 -
39 - /// Get the public key
40 - pub fn public_key(&self) -> Vec<u8> {
41 - self.signing_key.verifying_key().as_bytes().to_vec()
42 - }
43 -
44 - /// Sign data
45 - pub fn sign(&self, data: &[u8]) -> Vec<u8> {
46 - self.signing_key.sign(data).to_bytes().to_vec()
47 - }
48 -
49 - /// Get Identity message
50 - pub fn identity(&self) -> Identity {
51 - Identity {
52 - id: self.id.clone(),
53 - public_key: self.public_key(),
54 - }
55 - }
56 -}
57 -
58 -/// Verify a signed payload
59 -fn verify_signature(identity: &Identity, signed: &SignedPayload) -> Result<(), io::Error> {
60 - let public_key = VerifyingKey::from_bytes(
61 - identity
62 - .public_key
63 - .as_slice()
64 - .try_into()
65 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid public key"))?,
66 - )
67 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid public key"))?;
68 -
69 - let signature = Signature::from_bytes(
70 - signed
71 - .signature
72 - .as_slice()
73 - .try_into()
74 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid signature"))?,
75 - );
76 -
77 - public_key
78 - .verify_strict(&signed.data, &signature)
79 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "signature verification failed"))
80 -}
81 -
82 -/// Derive session keys using HKDF
83 -fn derive_key(shared_secret: &[u8], salt: &[u8], info: &[u8]) -> [u8; SESSION_KEY_SIZE] {
84 - let hkdf = Hkdf::<Sha256>::new(Some(salt), shared_secret);
85 - let mut key = [0u8; SESSION_KEY_SIZE];
86 - hkdf.expand(info, &mut key).expect("HKDF expand failed");
87 - key
88 -}
89 -
90 -/// Increment nonce for the next message
91 -fn increment_nonce(nonce: &mut [u8]) {
92 - for byte in nonce.iter_mut().rev() {
93 - *byte = byte.wrapping_add(1);
94 - if *byte != 0 {
95 - break;
96 - }
97 - }
98 -}
99 -
100 -/// Secure connection with encryption
101 -pub struct SecureConnection<T> {
102 - conn: T,
103 - encryptor: ChaCha20Poly1305,
104 - decryptor: ChaCha20Poly1305,
105 - encrypt_nonce: [u8; NONCE_SIZE],
106 - decrypt_nonce: [u8; NONCE_SIZE],
107 -}
108 -
109 -impl<T: AsyncRead + AsyncWrite + Unpin> SecureConnection<T> {
110 - /// Perform client-side handshake
111 - pub async fn client_handshake(
112 - mut conn: T,
113 - credential: &Credential,
114 - alpn: &str,
115 - ) -> io::Result<Self> {
116 - // Generate ephemeral key pair
117 - let ephemeral_secret = EphemeralSecret::random_from_rng(&mut rand_core::OsRng);
118 - let ephemeral_public = PublicKey::from(&ephemeral_secret);
119 -
120 - // Create ClientInitPayload
121 - let client_nonce = utils::random_bytes(NONCE_SIZE);
122 - let timestamp = utils::unix_timestamp();
123 -
124 - let client_init = proto::ClientInitPayload {
125 - version: ProtocolVersion::ProtocolVersion1 as i32,
126 - nonce: client_nonce.clone(),
127 - timestamp,
128 - identity: Some(credential.identity()),
129 - alpn: alpn.to_string(),
130 - session_public_key: ephemeral_public.as_bytes().to_vec(),
131 - };
132 -
133 - // Sign and send
134 - let payload = proto::encode_message(&client_init);
135 - let signature = credential.sign(&payload);
136 - let signed = SignedPayload {
137 - data: payload,
138 - signature,
139 - };
140 -
141 - write_length_prefixed(&mut conn, &proto::encode_message(&signed)).await?;
142 -
143 - // Read server init
144 - let server_init_bytes = read_length_prefixed(&mut conn).await?;
145 - let server_signed: SignedPayload = proto::decode_message(&server_init_bytes)
146 - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
147 -
148 - let server_init: proto::ServerInitPayload = proto::decode_message(&server_signed.data)
149 - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
150 -
151 - // Validate server init
152 - if server_init.version != ProtocolVersion::ProtocolVersion1 as i32 {
153 - return Err(io::Error::new(
154 - io::ErrorKind::InvalidData,
155 - "invalid protocol version",
156 - ));
157 - }
158 -
159 - let server_identity = server_init
160 - .identity
161 - .as_ref()
162 - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing identity"))?;
163 -
164 - verify_signature(server_identity, &server_signed)?;
165 -
166 - // Derive session keys
167 - let server_public = PublicKey::from(
168 - <[u8; 32]>::try_from(server_init.session_public_key.as_slice()).map_err(|_| {
169 - io::Error::new(io::ErrorKind::InvalidData, "invalid server public key")
170 - })?,
171 - );
172 -
173 - let shared_secret = ephemeral_secret.diffie_hellman(&server_public);
174 -
175 - // Client encrypts with CLIENT_KEY_INFO, server decrypts with it
176 - let mut client_salt = client_nonce.clone();
177 - client_salt.extend_from_slice(&server_init.nonce);
178 - let encrypt_key = derive_key(shared_secret.as_bytes(), &client_salt, CLIENT_KEY_INFO);
179 -
180 - // Server encrypts with SERVER_KEY_INFO, client decrypts with it
181 - let mut server_salt = server_init.nonce.clone();
182 - server_salt.extend_from_slice(&client_nonce);
183 - let decrypt_key = derive_key(shared_secret.as_bytes(), &server_salt, SERVER_KEY_INFO);
184 -
185 - Ok(Self {
186 - conn,
187 - encryptor: ChaCha20Poly1305::new(&encrypt_key.into()),
188 - decryptor: ChaCha20Poly1305::new(&decrypt_key.into()),
189 - encrypt_nonce: client_nonce.as_slice().try_into().unwrap(),
190 - decrypt_nonce: server_init.nonce.as_slice().try_into().unwrap(),
191 - })
192 - }
193 -
194 - /// Read decrypted data
195 - pub async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
196 - // Read length-prefixed encrypted message
197 - let encrypted_msg = read_length_prefixed(&mut self.conn).await?;
198 -
199 - let encrypted_data: proto::EncryptedData = proto::decode_message(&encrypted_msg)
200 - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
201 -
202 - // Validate nonce size
203 - if encrypted_data.nonce.len() != NONCE_SIZE {
204 - return Err(io::Error::new(
205 - io::ErrorKind::InvalidData,
206 - "invalid nonce size",
207 - ));
208 - }
209 -
210 - // Verify nonce matches our expected counter value
211 - let received_nonce: &[u8; NONCE_SIZE] = encrypted_data.nonce.as_slice().try_into().unwrap();
212 - if received_nonce != &self.decrypt_nonce {
213 - return Err(io::Error::new(
214 - io::ErrorKind::InvalidData,
215 - "nonce mismatch - possible replay attack",
216 - ));
217 - }
218 -
219 - // Decrypt using our tracked nonce
220 - let decrypted = self
221 - .decryptor
222 - .decrypt((&self.decrypt_nonce).into(), encrypted_data.payload.as_slice())
223 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decryption failed"))?;
224 -
225 - // Increment decrypt nonce for next message
226 - increment_nonce(&mut self.decrypt_nonce);
227 -
228 - // Copy to buffer
229 - let to_copy = buf.len().min(decrypted.len());
230 - buf[..to_copy].copy_from_slice(&decrypted[..to_copy]);
231 -
232 - Ok(to_copy)
233 - }
234 -
235 - /// Write encrypted data
236 - pub async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
237 - // Increment nonce
238 - increment_nonce(&mut self.encrypt_nonce);
239 -
240 - // Encrypt
241 - let encrypted = self
242 - .encryptor
243 - .encrypt((&self.encrypt_nonce).into(), buf)
244 - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "encryption failed"))?;
245 -
246 - // Create EncryptedData message
247 - let encrypted_data = proto::EncryptedData {
248 - nonce: self.encrypt_nonce.to_vec(),
249 - payload: encrypted,
250 - };
251 -
252 - // Send length-prefixed
253 - let msg = proto::encode_message(&encrypted_data);
254 - write_length_prefixed(&mut self.conn, &msg).await?;
255 -
256 - Ok(buf.len())
257 - }
258 -}
259 -
260 -/// Write length-prefixed data
261 -async fn write_length_prefixed<W: AsyncWrite + Unpin>(
262 - writer: &mut W,
263 - data: &[u8],
264 -) -> io::Result<()> {
265 - let len = data.len() as u32;
266 - writer.write_all(&len.to_be_bytes()).await?;
267 - writer.write_all(data).await?;
268 - writer.flush().await?;
269 - Ok(())
270 -}
271 -
272 -/// Read length-prefixed data
273 -async fn read_length_prefixed<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Vec<u8>> {
274 - let mut len_buf = [0u8; 4];
275 - reader.read_exact(&mut len_buf).await?;
276 - let len = u32::from_be_bytes(len_buf) as usize;
277 -
278 - if len > MAX_RAW_PACKET_SIZE {
279 - return Err(io::Error::new(
280 - io::ErrorKind::InvalidData,
281 - "packet too large",
282 - ));
283 - }
284 -
285 - let mut data = vec![0u8; len];
286 - reader.read_exact(&mut data).await?;
287 -
288 - Ok(data)
289 -}
portal/wasm/src/lib.rs deleted
-40
@@ -1,40 +0,0 @@
1 -use wasm_bindgen::prelude::*;
2 -
3 -mod adapters;
4 -mod crypto;
5 -mod proto;
6 -mod protocol_codec;
7 -mod proxy_engine;
8 -mod relay_client;
9 -mod tunnel_manager;
10 -mod utils;
11 -mod ws_stream;
12 -
13 -pub use adapters::{DataInterpreter, HttpAdapter, WebSocketAdapter};
14 -pub use proxy_engine::ProxyEngine;
15 -pub use relay_client::RelayClient;
16 -pub use ws_stream::WebSocketStream;
17 -
18 -// Global allocator for smaller binary size
19 -#[global_allocator]
20 -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
21 -
22 -/// Initialize WASM module
23 -#[wasm_bindgen(start)]
24 -pub fn init() {
25 - // Set panic hook for better error messages in console
26 - console_error_panic_hook::set_once();
27 -}
28 -
29 -#[wasm_bindgen]
30 -extern "C" {
31 - #[wasm_bindgen(js_namespace = console, js_name = log)]
32 - pub(crate) fn console_log_impl(s: &str);
33 -}
34 -
35 -#[allow(unused_macros)]
36 -macro_rules! console_log {
37 - ($($t:tt)*) => (crate::console_log_impl(&format_args!($($t)*).to_string()))
38 -}
39 -
40 -pub(crate) use console_log;
portal/wasm/src/proto/mod.rs deleted
-76
@@ -1,76 +0,0 @@
1 -// This module contains generated protobuf code
2 -// Generated files will be placed here by build.rs
3 -
4 -// Include generated proto modules separately to avoid conflicts
5 -#[allow(dead_code)]
6 -pub mod rdverb {
7 - include!(concat!(env!("OUT_DIR"), "/rdverb.rs"));
8 -}
9 -
10 -#[allow(dead_code)]
11 -pub mod rdsec {
12 - include!(concat!(env!("OUT_DIR"), "/rdsec.rs"));
13 -}
14 -
15 -// Re-export commonly used types
16 -pub use rdverb::*;
17 -pub use rdsec::*;
18 -
19 -use prost::Message;
20 -use std::io;
21 -
22 -/// Helper to encode a protobuf message
23 -pub fn encode_message<M: Message>(msg: &M) -> Vec<u8> {
24 - let mut buf = Vec::with_capacity(msg.encoded_len());
25 - msg.encode(&mut buf).expect("failed to encode message");
26 - buf
27 -}
28 -
29 -/// Helper to decode a protobuf message
30 -pub fn decode_message<M: Message + Default>(data: &[u8]) -> Result<M, prost::DecodeError> {
31 - M::decode(data)
32 -}
33 -
34 -
35 -/// Async version: write packet
36 -pub async fn write_packet_async<W: futures::io::AsyncWrite + Unpin>(
37 - writer: &mut W,
38 - packet: &Packet,
39 -) -> io::Result<()> {
40 - use futures::io::AsyncWriteExt;
41 -
42 - let payload = encode_message(packet);
43 - let len = payload.len() as u32;
44 -
45 - writer.write_all(&len.to_be_bytes()).await?;
46 - writer.write_all(&payload).await?;
47 - writer.flush().await?;
48 - Ok(())
49 -}
50 -
51 -/// Async version: read packet
52 -pub async fn read_packet_async<R: futures::io::AsyncRead + Unpin>(
53 - reader: &mut R,
54 -) -> io::Result<Packet> {
55 - use futures::io::AsyncReadExt;
56 -
57 - let mut len_buf = [0u8; 4];
58 - reader.read_exact(&mut len_buf).await?;
59 - let len = u32::from_be_bytes(len_buf) as usize;
60 -
61 - // Packet size limit (64MB)
62 - const MAX_PACKET_SIZE: usize = 1 << 26;
63 - if len > MAX_PACKET_SIZE {
64 - return Err(io::Error::new(
65 - io::ErrorKind::InvalidData,
66 - "packet too large",
67 - ));
68 - }
69 -
70 - let mut payload = vec![0u8; len];
71 - reader.read_exact(&mut payload).await?;
72 -
73 - decode_message(&payload).map_err(|e| {
74 - io::Error::new(io::ErrorKind::InvalidData, e)
75 - })
76 -}
portal/wasm/src/proto/rdsec.rs deleted
-75
@@ -1,75 +0,0 @@
1 -// This file is @generated by prost-build.
2 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
3 -pub struct Identity {
4 - #[prost(string, tag = "1")]
5 - pub id: ::prost::alloc::string::String,
6 - #[prost(bytes = "vec", tag = "2")]
7 - pub public_key: ::prost::alloc::vec::Vec<u8>,
8 -}
9 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
10 -pub struct ClientInitPayload {
11 - #[prost(enumeration = "ProtocolVersion", tag = "1")]
12 - pub version: i32,
13 - #[prost(bytes = "vec", tag = "2")]
14 - pub nonce: ::prost::alloc::vec::Vec<u8>,
15 - #[prost(int64, tag = "3")]
16 - pub timestamp: i64,
17 - #[prost(message, optional, tag = "4")]
18 - pub identity: ::core::option::Option<Identity>,
19 - #[prost(string, tag = "5")]
20 - pub alpn: ::prost::alloc::string::String,
21 - #[prost(bytes = "vec", tag = "6")]
22 - pub session_public_key: ::prost::alloc::vec::Vec<u8>,
23 -}
24 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
25 -pub struct SignedPayload {
26 - #[prost(bytes = "vec", tag = "1")]
27 - pub data: ::prost::alloc::vec::Vec<u8>,
28 - #[prost(bytes = "vec", tag = "2")]
29 - pub signature: ::prost::alloc::vec::Vec<u8>,
30 -}
31 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
32 -pub struct ServerInitPayload {
33 - #[prost(enumeration = "ProtocolVersion", tag = "1")]
34 - pub version: i32,
35 - #[prost(bytes = "vec", tag = "2")]
36 - pub nonce: ::prost::alloc::vec::Vec<u8>,
37 - #[prost(int64, tag = "3")]
38 - pub timestamp: i64,
39 - #[prost(message, optional, tag = "4")]
40 - pub identity: ::core::option::Option<Identity>,
41 - #[prost(string, tag = "5")]
42 - pub alpn: ::prost::alloc::string::String,
43 - #[prost(bytes = "vec", tag = "6")]
44 - pub session_public_key: ::prost::alloc::vec::Vec<u8>,
45 -}
46 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
47 -pub struct EncryptedData {
48 - #[prost(bytes = "vec", tag = "1")]
49 - pub nonce: ::prost::alloc::vec::Vec<u8>,
50 - #[prost(bytes = "vec", tag = "2")]
51 - pub payload: ::prost::alloc::vec::Vec<u8>,
52 -}
53 -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
54 -#[repr(i32)]
55 -pub enum ProtocolVersion {
56 - ProtocolVersion1 = 0,
57 -}
58 -impl ProtocolVersion {
59 - /// String value of the enum field names used in the ProtoBuf definition.
60 - ///
61 - /// The values are not transformed in any way and thus are considered stable
62 - /// (if the ProtoBuf definition does not change) and safe for programmatic use.
63 - pub fn as_str_name(&self) -> &'static str {
64 - match self {
65 - Self::ProtocolVersion1 => "PROTOCOL_VERSION_1",
66 - }
67 - }
68 - /// Creates an enum from field names used in the ProtoBuf definition.
69 - pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
70 - match value {
71 - "PROTOCOL_VERSION_1" => Some(Self::ProtocolVersion1),
72 - _ => None,
73 - }
74 - }
75 -}
portal/wasm/src/proto/rdverb.rs deleted
-162
@@ -1,162 +0,0 @@
1 -// This file is @generated by prost-build.
2 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
3 -pub struct Packet {
4 - #[prost(enumeration = "PacketType", tag = "1")]
5 - pub r#type: i32,
6 - #[prost(bytes = "vec", tag = "2")]
7 - pub payload: ::prost::alloc::vec::Vec<u8>,
8 -}
9 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
10 -pub struct RelayInfo {
11 - #[prost(message, optional, tag = "1")]
12 - pub identity: ::core::option::Option<super::rdsec::Identity>,
13 - #[prost(string, repeated, tag = "2")]
14 - pub address: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
15 - #[prost(string, repeated, tag = "3")]
16 - pub leases: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
17 -}
18 -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
19 -pub struct RelayInfoRequest {}
20 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
21 -pub struct RelayInfoResponse {
22 - #[prost(message, optional, tag = "1")]
23 - pub relay_info: ::core::option::Option<RelayInfo>,
24 -}
25 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
26 -pub struct Lease {
27 - #[prost(message, optional, tag = "1")]
28 - pub identity: ::core::option::Option<super::rdsec::Identity>,
29 - #[prost(int64, tag = "2")]
30 - pub expires: i64,
31 - #[prost(string, tag = "3")]
32 - pub name: ::prost::alloc::string::String,
33 - #[prost(string, repeated, tag = "4")]
34 - pub alpn: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
35 -}
36 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
37 -pub struct LeaseUpdateRequest {
38 - #[prost(message, optional, tag = "1")]
39 - pub lease: ::core::option::Option<Lease>,
40 - #[prost(bytes = "vec", tag = "2")]
41 - pub nonce: ::prost::alloc::vec::Vec<u8>,
42 - #[prost(int64, tag = "3")]
43 - pub timestamp: i64,
44 -}
45 -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
46 -pub struct LeaseUpdateResponse {
47 - #[prost(enumeration = "ResponseCode", tag = "1")]
48 - pub code: i32,
49 -}
50 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
51 -pub struct LeaseDeleteRequest {
52 - #[prost(message, optional, tag = "1")]
53 - pub identity: ::core::option::Option<super::rdsec::Identity>,
54 - #[prost(bytes = "vec", tag = "2")]
55 - pub nonce: ::prost::alloc::vec::Vec<u8>,
56 - #[prost(int64, tag = "3")]
57 - pub timestamp: i64,
58 -}
59 -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
60 -pub struct LeaseDeleteResponse {
61 - #[prost(enumeration = "ResponseCode", tag = "1")]
62 - pub code: i32,
63 -}
64 -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
65 -pub struct ConnectionRequest {
66 - #[prost(string, tag = "1")]
67 - pub lease_id: ::prost::alloc::string::String,
68 - #[prost(message, optional, tag = "2")]
69 - pub client_identity: ::core::option::Option<super::rdsec::Identity>,
70 -}
71 -#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
72 -pub struct ConnectionResponse {
73 - #[prost(enumeration = "ResponseCode", tag = "1")]
74 - pub code: i32,
75 -}
76 -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
77 -#[repr(i32)]
78 -pub enum PacketType {
79 - RelayInfoRequest = 0,
80 - RelayInfoResponse = 1,
81 - /// Authenticated
82 - LeaseUpdateRequest = 2,
83 - LeaseUpdateResponse = 3,
84 - /// Authenticated
85 - LeaseDeleteRequest = 4,
86 - LeaseDeleteResponse = 5,
87 - ConnectionRequest = 6,
88 - ConnectionResponse = 7,
89 -}
90 -impl PacketType {
91 - /// String value of the enum field names used in the ProtoBuf definition.
92 - ///
93 - /// The values are not transformed in any way and thus are considered stable
94 - /// (if the ProtoBuf definition does not change) and safe for programmatic use.
95 - pub fn as_str_name(&self) -> &'static str {
96 - match self {
97 - Self::RelayInfoRequest => "PACKET_TYPE_RELAY_INFO_REQUEST",
98 - Self::RelayInfoResponse => "PACKET_TYPE_RELAY_INFO_RESPONSE",
99 - Self::LeaseUpdateRequest => "PACKET_TYPE_LEASE_UPDATE_REQUEST",
100 - Self::LeaseUpdateResponse => "PACKET_TYPE_LEASE_UPDATE_RESPONSE",
101 - Self::LeaseDeleteRequest => "PACKET_TYPE_LEASE_DELETE_REQUEST",
102 - Self::LeaseDeleteResponse => "PACKET_TYPE_LEASE_DELETE_RESPONSE",
103 - Self::ConnectionRequest => "PACKET_TYPE_CONNECTION_REQUEST",
104 - Self::ConnectionResponse => "PACKET_TYPE_CONNECTION_RESPONSE",
105 - }
106 - }
107 - /// Creates an enum from field names used in the ProtoBuf definition.
108 - pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
109 - match value {
110 - "PACKET_TYPE_RELAY_INFO_REQUEST" => Some(Self::RelayInfoRequest),
111 - "PACKET_TYPE_RELAY_INFO_RESPONSE" => Some(Self::RelayInfoResponse),
112 - "PACKET_TYPE_LEASE_UPDATE_REQUEST" => Some(Self::LeaseUpdateRequest),
113 - "PACKET_TYPE_LEASE_UPDATE_RESPONSE" => Some(Self::LeaseUpdateResponse),
114 - "PACKET_TYPE_LEASE_DELETE_REQUEST" => Some(Self::LeaseDeleteRequest),
115 - "PACKET_TYPE_LEASE_DELETE_RESPONSE" => Some(Self::LeaseDeleteResponse),
116 - "PACKET_TYPE_CONNECTION_REQUEST" => Some(Self::ConnectionRequest),
117 - "PACKET_TYPE_CONNECTION_RESPONSE" => Some(Self::ConnectionResponse),
118 - _ => None,
119 - }
120 - }
121 -}
122 -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
123 -#[repr(i32)]
124 -pub enum ResponseCode {
125 - Unknown = 0,
126 - Accepted = 1,
127 - InvalidExpires = 2,
128 - InvalidIdentity = 3,
129 - InvalidName = 4,
130 - InvalidAlpn = 5,
131 - Rejected = 6,
132 -}
133 -impl ResponseCode {
134 - /// String value of the enum field names used in the ProtoBuf definition.
135 - ///
136 - /// The values are not transformed in any way and thus are considered stable
137 - /// (if the ProtoBuf definition does not change) and safe for programmatic use.
138 - pub fn as_str_name(&self) -> &'static str {
139 - match self {
140 - Self::Unknown => "RESPONSE_CODE_UNKNOWN",
141 - Self::Accepted => "RESPONSE_CODE_ACCEPTED",
142 - Self::InvalidExpires => "RESPONSE_CODE_INVALID_EXPIRES",
143 - Self::InvalidIdentity => "RESPONSE_CODE_INVALID_IDENTITY",
144 - Self::InvalidName => "RESPONSE_CODE_INVALID_NAME",
145 - Self::InvalidAlpn => "RESPONSE_CODE_INVALID_ALPN",
146 - Self::Rejected => "RESPONSE_CODE_REJECTED",
147 - }
148 - }
149 - /// Creates an enum from field names used in the ProtoBuf definition.
150 - pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
151 - match value {
152 - "RESPONSE_CODE_UNKNOWN" => Some(Self::Unknown),
153 - "RESPONSE_CODE_ACCEPTED" => Some(Self::Accepted),
154 - "RESPONSE_CODE_INVALID_EXPIRES" => Some(Self::InvalidExpires),
155 - "RESPONSE_CODE_INVALID_IDENTITY" => Some(Self::InvalidIdentity),
156 - "RESPONSE_CODE_INVALID_NAME" => Some(Self::InvalidName),
157 - "RESPONSE_CODE_INVALID_ALPN" => Some(Self::InvalidAlpn),
158 - "RESPONSE_CODE_REJECTED" => Some(Self::Rejected),
159 - _ => None,
160 - }
161 - }
162 -}
portal/wasm/src/protocol_codec.rs deleted
-274
@@ -1,274 +0,0 @@
1 -/// Protocol codec for converting between browser requests and relay protocol
2 -///
3 -/// This module handles the encoding/decoding of HTTP, WebSocket, and TCP data
4 -/// for transmission through the E2EE tunnel
5 -use serde::{Deserialize, Serialize};
6 -use std::collections::HashMap;
7 -
8 -/// Protocol types supported by the proxy
9 -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10 -pub enum ProtocolType {
11 - Http,
12 - WebSocket,
13 - Tcp,
14 -}
15 -
16 -/// Request from browser to be proxied
17 -#[derive(Debug, Clone, Serialize, Deserialize)]
18 -#[serde(tag = "type")]
19 -pub enum ProxyRequest {
20 - /// HTTP request
21 - #[serde(rename = "http")]
22 - HttpRequest {
23 - method: String,
24 - url: String,
25 - headers: HashMap<String, String>,
26 - body: Option<Vec<u8>>,
27 - },
28 -
29 - /// Open WebSocket connection
30 - #[serde(rename = "ws_open")]
31 - WebSocketOpen { url: String, protocols: Vec<String> },
32 -
33 - /// Send WebSocket message
34 - #[serde(rename = "ws_message")]
35 - WebSocketMessage { tunnel_id: String, data: WsData },
36 -
37 - /// Close WebSocket
38 - #[serde(rename = "ws_close")]
39 - WebSocketClose {
40 - tunnel_id: String,
41 - code: u16,
42 - reason: String,
43 - },
44 -
45 - /// TCP connect
46 - #[serde(rename = "tcp_connect")]
47 - TcpConnect { host: String, port: u16 },
48 -
49 - /// TCP data
50 - #[serde(rename = "tcp_data")]
51 - TcpData { tunnel_id: String, data: Vec<u8> },
52 -
53 - /// TCP close
54 - #[serde(rename = "tcp_close")]
55 - TcpClose { tunnel_id: String },
56 -}
57 -
58 -/// WebSocket data types
59 -#[derive(Debug, Clone, Serialize, Deserialize)]
60 -#[serde(tag = "data_type")]
61 -pub enum WsData {
62 - #[serde(rename = "text")]
63 - Text { content: String },
64 -
65 - #[serde(rename = "binary")]
66 - Binary { content: Vec<u8> },
67 -}
68 -
69 -/// Response from relay back to browser
70 -#[derive(Debug, Clone, Serialize, Deserialize)]
71 -#[serde(tag = "type")]
72 -pub enum ProxyResponse {
73 - /// HTTP response
74 - #[serde(rename = "http")]
75 - HttpResponse {
76 - status: u16,
77 - status_text: String,
78 - headers: HashMap<String, String>,
79 - body: Vec<u8>,
80 - },
81 -
82 - /// WebSocket opened
83 - #[serde(rename = "ws_opened")]
84 - WebSocketOpened {
85 - tunnel_id: String,
86 - protocol: Option<String>,
87 - },
88 -
89 - /// WebSocket message received
90 - #[serde(rename = "ws_message")]
91 - WebSocketMessage { tunnel_id: String, data: WsData },
92 -
93 - /// WebSocket closed
94 - #[serde(rename = "ws_closed")]
95 - WebSocketClosed {
96 - tunnel_id: String,
97 - code: u16,
98 - reason: String,
99 - },
100 -
101 - /// TCP connected
102 - #[serde(rename = "tcp_connected")]
103 - TcpConnected { tunnel_id: String },
104 -
105 - /// TCP data received
106 - #[serde(rename = "tcp_data")]
107 - TcpData { tunnel_id: String, data: Vec<u8> },
108 -
109 - /// TCP closed
110 - #[serde(rename = "tcp_closed")]
111 - TcpClosed { tunnel_id: String },
112 -
113 - /// Error response
114 - #[serde(rename = "error")]
115 - Error { request_id: String, error: String },
116 -}
117 -
118 -/// Wire format for transmission through E2EE tunnel
119 -#[derive(Debug, Clone, Serialize, Deserialize)]
120 -pub struct ProxyPacket {
121 - /// Unique request/response ID
122 - pub id: String,
123 -
124 - /// Protocol version
125 - pub version: u8,
126 -
127 - /// Payload
128 - pub payload: ProxyPayload,
129 -}
130 -
131 -#[derive(Debug, Clone, Serialize, Deserialize)]
132 -#[serde(untagged)]
133 -pub enum ProxyPayload {
134 - Request(ProxyRequest),
135 - Response(ProxyResponse),
136 -}
137 -
138 -impl ProxyPacket {
139 - /// Create a new request packet
140 - pub fn new_request(id: String, request: ProxyRequest) -> Self {
141 - Self {
142 - id,
143 - version: 1,
144 - payload: ProxyPayload::Request(request),
145 - }
146 - }
147 -
148 - /// Create a new response packet
149 - #[allow(dead_code)]
150 - pub fn new_response(id: String, response: ProxyResponse) -> Self {
151 - Self {
152 - id,
153 - version: 1,
154 - payload: ProxyPayload::Response(response),
155 - }
156 - }
157 -
158 - /// Encode packet to bytes
159 - pub fn encode(&self) -> Result<Vec<u8>, String> {
160 - serde_json::to_vec(self).map_err(|e| format!("failed to encode packet: {}", e))
161 - }
162 -
163 - /// Decode packet from bytes
164 - pub fn decode(data: &[u8]) -> Result<Self, String> {
165 - serde_json::from_slice(data).map_err(|e| format!("failed to decode packet: {}", e))
166 - }
167 -}
168 -
169 -/// HTTP request codec
170 -pub struct HttpCodec;
171 -
172 -impl HttpCodec {
173 - /// Parse HTTP method from string
174 - pub fn parse_method(method: &str) -> Result<String, String> {
175 - match method.to_uppercase().as_str() {
176 - "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" => {
177 - Ok(method.to_uppercase())
178 - }
179 - _ => Err(format!("unsupported HTTP method: {}", method)),
180 - }
181 - }
182 -
183 - /// Validate HTTP URL
184 - pub fn validate_url(url: &str) -> Result<(), String> {
185 - if url.starts_with("http://") || url.starts_with("https://") {
186 - Ok(())
187 - } else {
188 - Err(format!("invalid HTTP URL: {}", url))
189 - }
190 - }
191 -
192 - /// Parse headers from key-value pairs
193 - #[allow(dead_code)]
194 - pub fn parse_headers(headers: Vec<(String, String)>) -> HashMap<String, String> {
195 - headers.into_iter().collect()
196 - }
197 -}
198 -
199 -/// WebSocket codec
200 -pub struct WebSocketCodec;
201 -
202 -impl WebSocketCodec {
203 - /// Validate WebSocket URL
204 - pub fn validate_url(url: &str) -> Result<(), String> {
205 - if url.starts_with("ws://") || url.starts_with("wss://") {
206 - Ok(())
207 - } else {
208 - Err(format!("invalid WebSocket URL: {}", url))
209 - }
210 - }
211 -
212 - /// Generate tunnel ID
213 - #[allow(dead_code)]
214 - pub fn generate_tunnel_id() -> String {
215 - use rand_core::RngCore;
216 - let mut rng = rand_core::OsRng;
217 - let mut bytes = [0u8; 16];
218 - rng.fill_bytes(&mut bytes);
219 - hex::encode(bytes)
220 - }
221 -}
222 -
223 -/// TCP codec
224 -pub struct TcpCodec;
225 -
226 -impl TcpCodec {
227 - /// Validate TCP address
228 - pub fn validate_address(host: &str, port: u16) -> Result<(), String> {
229 - if host.is_empty() {
230 - return Err("empty host".to_string());
231 - }
232 - if port == 0 {
233 - return Err("invalid port".to_string());
234 - }
235 - Ok(())
236 - }
237 -}
238 -
239 -#[cfg(test)]
240 -mod tests {
241 - use super::*;
242 -
243 - #[test]
244 - fn test_http_request_encoding() {
245 - let req = ProxyRequest::HttpRequest {
246 - method: "GET".to_string(),
247 - url: "https://example.com".to_string(),
248 - headers: HashMap::new(),
249 - body: None,
250 - };
251 -
252 - let packet = ProxyPacket::new_request("test-id".to_string(), req);
253 - let encoded = packet.encode().unwrap();
254 - let decoded = ProxyPacket::decode(&encoded).unwrap();
255 -
256 - assert_eq!(packet.id, decoded.id);
257 - }
258 -
259 - #[test]
260 - fn test_websocket_message() {
261 - let req = ProxyRequest::WebSocketMessage {
262 - tunnel_id: "tunnel-123".to_string(),
263 - data: WsData::Text {
264 - content: "Hello".to_string(),
265 - },
266 - };
267 -
268 - let packet = ProxyPacket::new_request("msg-id".to_string(), req);
269 - let encoded = packet.encode().unwrap();
270 - let decoded = ProxyPacket::decode(&encoded).unwrap();
271 -
272 - assert_eq!(packet.id, decoded.id);
273 - }
274 -}
portal/wasm/src/proxy_engine.rs deleted
-414
@@ -1,414 +0,0 @@
1 -/// Proxy Engine - Core network interception and E2EE tunneling
2 -///
3 -/// This module intercepts all browser network requests and routes them
4 -/// through encrypted tunnels to the relay server
5 -
6 -use crate::crypto::Credential;
7 -use crate::protocol_codec::{
8 - HttpCodec, ProxyRequest, ProxyResponse, ProtocolType, TcpCodec, WebSocketCodec, WsData,
9 -};
10 -use crate::tunnel_manager::TunnelManager;
11 -use parking_lot::Mutex;
12 -use std::collections::HashMap;
13 -use std::sync::Arc;
14 -use wasm_bindgen::prelude::*;
15 -
16 -/// Main proxy engine that handles all intercepted requests
17 -#[wasm_bindgen]
18 -pub struct ProxyEngine {
19 - inner: Arc<ProxyEngineInner>,
20 -}
21 -
22 -#[allow(dead_code)]
23 -struct ProxyEngineInner {
24 - tunnel_manager: TunnelManager,
25 - pending_requests: Mutex<HashMap<String, PendingRequest>>,
26 - config: ProxyConfig,
27 -}
28 -
29 -#[allow(dead_code)]
30 -struct PendingRequest {
31 - request_id: String,
32 - tunnel_id: String,
33 - protocol: ProtocolType,
34 -}
35 -
36 -/// Configuration for proxy engine
37 -#[derive(Clone)]
38 -pub struct ProxyConfig {
39 - pub server_url: String,
40 - pub enabled: bool,
41 - pub intercept_patterns: Vec<String>,
42 - pub bypass_patterns: Vec<String>,
43 -}
44 -
45 -impl Default for ProxyConfig {
46 - fn default() -> Self {
47 - Self {
48 - server_url: "ws://localhost:9001/ws".to_string(),
49 - enabled: true,
50 - intercept_patterns: vec!["*".to_string()],
51 - bypass_patterns: vec![],
52 - }
53 - }
54 -}
55 -
56 -#[wasm_bindgen]
57 -impl ProxyEngine {
58 - /// Create a new proxy engine
59 - #[wasm_bindgen(constructor)]
60 - pub fn new(server_url: String) -> Self {
61 - let credential = Credential::new();
62 - let tunnel_manager = TunnelManager::new(credential, server_url.clone());
63 -
64 - let config = ProxyConfig {
65 - server_url,
66 - ..Default::default()
67 - };
68 -
69 - Self {
70 - inner: Arc::new(ProxyEngineInner {
71 - tunnel_manager,
72 - pending_requests: Mutex::new(HashMap::new()),
73 - config,
74 - }),
75 - }
76 - }
77 -
78 - /// Check if a URL should be intercepted
79 - #[wasm_bindgen(js_name = shouldIntercept)]
80 - pub fn should_intercept(&self, url: String) -> bool {
81 - // Don't intercept same-origin requests to WASM server
82 - if url.contains("localhost:8000") || url.contains("portal_wasm") {
83 - return false;
84 - }
85 -
86 - // Check bypass patterns
87 - for pattern in &self.inner.config.bypass_patterns {
88 - if url.contains(pattern) {
89 - return false;
90 - }
91 - }
92 -
93 - // Check intercept patterns
94 - if self.inner.config.intercept_patterns.contains(&"*".to_string()) {
95 - return true;
96 - }
97 -
98 - for pattern in &self.inner.config.intercept_patterns {
99 - if url.contains(pattern) {
100 - return true;
101 - }
102 - }
103 -
104 - false
105 - }
106 -
107 - /// Handle HTTP request
108 - #[wasm_bindgen(js_name = handleHttpRequest)]
109 - pub async fn handle_http_request(
110 - &self,
111 - method: String,
112 - url: String,
113 - headers: JsValue,
114 - body: Option<Vec<u8>>,
115 - ) -> Result<JsValue, JsValue> {
116 - // Validate method and URL
117 - HttpCodec::parse_method(&method)
118 - .map_err(|e| JsValue::from_str(&e))?;
119 - HttpCodec::validate_url(&url)
120 - .map_err(|e| JsValue::from_str(&e))?;
121 -
122 - // Parse headers
123 - let headers_map: HashMap<String, String> = serde_wasm_bindgen::from_value(headers)
124 - .unwrap_or_else(|_| HashMap::new());
125 -
126 - // Create tunnel for HTTP
127 - let tunnel = self.inner.tunnel_manager
128 - .create_tunnel(ProtocolType::Http)
129 - .await
130 - .map_err(|e| JsValue::from_str(&format!("failed to create tunnel: {}", e)))?;
131 -
132 - // Create proxy request
133 - let request = ProxyRequest::HttpRequest {
134 - method,
135 - url,
136 - headers: headers_map,
137 - body,
138 - };
139 -
140 - // Send request through tunnel
141 - tunnel
142 - .send_request(request)
143 - .await
144 - .map_err(|e| JsValue::from_str(&format!("failed to send request: {}", e)))?;
145 -
146 - // Wait for response
147 - let response = tunnel
148 - .receive_response()
149 - .await
150 - .map_err(|e| JsValue::from_str(&format!("failed to receive response: {}", e)))?;
151 -
152 - // Remove tunnel
153 - self.inner.tunnel_manager.remove_tunnel(&tunnel.id);
154 -
155 - // Convert response to JS
156 - match response {
157 - ProxyResponse::HttpResponse {
158 - status,
159 - status_text,
160 - headers,
161 - body,
162 - } => {
163 - let obj = js_sys::Object::new();
164 - js_sys::Reflect::set(&obj, &"status".into(), &JsValue::from_f64(status as f64))?;
165 - js_sys::Reflect::set(&obj, &"statusText".into(), &JsValue::from_str(&status_text))?;
166 -
167 - let headers_obj = js_sys::Object::new();
168 - for (key, value) in headers {
169 - js_sys::Reflect::set(&headers_obj, &key.into(), &value.into())?;
170 - }
171 - js_sys::Reflect::set(&obj, &"headers".into(), &headers_obj)?;
172 -
173 - let body_array = js_sys::Uint8Array::from(&body[..]);
174 - js_sys::Reflect::set(&obj, &"body".into(), &body_array)?;
175 -
176 - Ok(obj.into())
177 - }
178 - ProxyResponse::Error { error, .. } => {
179 - Err(JsValue::from_str(&format!("proxy error: {}", error)))
180 - }
181 - _ => Err(JsValue::from_str("unexpected response type")),
182 - }
183 - }
184 -
185 - /// Open WebSocket connection through tunnel
186 - #[wasm_bindgen(js_name = openWebSocket)]
187 - pub async fn open_websocket(
188 - &self,
189 - url: String,
190 - protocols: Vec<String>,
191 - ) -> Result<JsValue, JsValue> {
192 - // Validate URL
193 - WebSocketCodec::validate_url(&url)
194 - .map_err(|e| JsValue::from_str(&e))?;
195 -
196 - // Create tunnel for WebSocket
197 - let tunnel = self.inner.tunnel_manager
198 - .create_tunnel(ProtocolType::WebSocket)
199 - .await
200 - .map_err(|e| JsValue::from_str(&format!("failed to create tunnel: {}", e)))?;
201 -
202 - let tunnel_id = tunnel.id.clone();
203 -
204 - // Create proxy request
205 - let request = ProxyRequest::WebSocketOpen {
206 - url: url.clone(),
207 - protocols,
208 - };
209 -
210 - // Send request
211 - tunnel
212 - .send_request(request)
213 - .await
214 - .map_err(|e| JsValue::from_str(&format!("failed to send request: {}", e)))?;
215 -
216 - // Wait for opened response
217 - let response = tunnel
218 - .receive_response()
219 - .await
220 - .map_err(|e| JsValue::from_str(&format!("failed to receive response: {}", e)))?;
221 -
222 - match response {
223 - ProxyResponse::WebSocketOpened { protocol, .. } => {
224 - // Store tunnel for future messages
225 - let obj = js_sys::Object::new();
226 - js_sys::Reflect::set(&obj, &"tunnelId".into(), &tunnel_id.into())?;
227 - js_sys::Reflect::set(
228 - &obj,
229 - &"protocol".into(),
230 - &protocol.unwrap_or_default().into(),
231 - )?;
232 - Ok(obj.into())
233 - }
234 - ProxyResponse::Error { error, .. } => {
235 - self.inner.tunnel_manager.remove_tunnel(&tunnel_id);
236 - Err(JsValue::from_str(&format!("failed to open websocket: {}", error)))
237 - }
238 - _ => {
239 - self.inner.tunnel_manager.remove_tunnel(&tunnel_id);
240 - Err(JsValue::from_str("unexpected response type"))
241 - }
242 - }
243 - }
244 -
245 - /// Send WebSocket message
246 - #[wasm_bindgen(js_name = sendWebSocketMessage)]
247 - pub async fn send_websocket_message(
248 - &self,
249 - tunnel_id: String,
250 - data: JsValue,
251 - is_binary: bool,
252 - ) -> Result<(), JsValue> {
253 - // Get tunnel
254 - let tunnel = self.inner.tunnel_manager
255 - .get_tunnel(&tunnel_id)
256 - .ok_or_else(|| JsValue::from_str("tunnel not found"))?;
257 -
258 - // Convert data
259 - let ws_data = if is_binary {
260 - let array = js_sys::Uint8Array::from(data);
261 - WsData::Binary {
262 - content: array.to_vec(),
263 - }
264 - } else {
265 - let text = data.as_string().ok_or_else(|| JsValue::from_str("invalid text data"))?;
266 - WsData::Text { content: text }
267 - };
268 -
269 - // Create request
270 - let request = ProxyRequest::WebSocketMessage {
271 - tunnel_id: tunnel_id.clone(),
272 - data: ws_data,
273 - };
274 -
275 - // Send
276 - tunnel
277 - .send_request(request)
278 - .await
279 - .map_err(|e| JsValue::from_str(&format!("failed to send message: {}", e)))?;
280 -
281 - Ok(())
282 - }
283 -
284 - /// Receive WebSocket message
285 - #[wasm_bindgen(js_name = receiveWebSocketMessage)]
286 - pub async fn receive_websocket_message(&self, tunnel_id: String) -> Result<JsValue, JsValue> {
287 - // Get tunnel
288 - let tunnel = self.inner.tunnel_manager
289 - .get_tunnel(&tunnel_id)
290 - .ok_or_else(|| JsValue::from_str("tunnel not found"))?;
291 -
292 - // Receive response
293 - let response = tunnel
294 - .receive_response()
295 - .await
296 - .map_err(|e| JsValue::from_str(&format!("failed to receive: {}", e)))?;
297 -
298 - match response {
299 - ProxyResponse::WebSocketMessage { data, .. } => {
300 - let obj = js_sys::Object::new();
301 - match data {
302 - WsData::Text { content } => {
303 - js_sys::Reflect::set(&obj, &"type".into(), &"text".into())?;
304 - js_sys::Reflect::set(&obj, &"data".into(), &content.into())?;
305 - }
306 - WsData::Binary { content } => {
307 - js_sys::Reflect::set(&obj, &"type".into(), &"binary".into())?;
308 - let array = js_sys::Uint8Array::from(&content[..]);
309 - js_sys::Reflect::set(&obj, &"data".into(), &array)?;
310 - }
311 - }
312 - Ok(obj.into())
313 - }
314 - ProxyResponse::WebSocketClosed { code, reason, .. } => {
315 - let obj = js_sys::Object::new();
316 - js_sys::Reflect::set(&obj, &"type".into(), &"close".into())?;
317 - js_sys::Reflect::set(&obj, &"code".into(), &JsValue::from_f64(code as f64))?;
318 - js_sys::Reflect::set(&obj, &"reason".into(), &reason.into())?;
319 - Ok(obj.into())
320 - }
321 - _ => Err(JsValue::from_str("unexpected response type")),
322 - }
323 - }
324 -
325 - /// Close WebSocket
326 - #[wasm_bindgen(js_name = closeWebSocket)]
327 - pub async fn close_websocket(
328 - &self,
329 - tunnel_id: String,
330 - code: u16,
331 - reason: String,
332 - ) -> Result<(), JsValue> {
333 - // Get tunnel
334 - let tunnel = self.inner.tunnel_manager
335 - .get_tunnel(&tunnel_id)
336 - .ok_or_else(|| JsValue::from_str("tunnel not found"))?;
337 -
338 - // Create close request
339 - let request = ProxyRequest::WebSocketClose {
340 - tunnel_id: tunnel_id.clone(),
341 - code,
342 - reason,
343 - };
344 -
345 - // Send
346 - tunnel
347 - .send_request(request)
348 - .await
349 - .map_err(|e| JsValue::from_str(&format!("failed to close: {}", e)))?;
350 -
351 - // Remove tunnel
352 - self.inner.tunnel_manager.remove_tunnel(&tunnel_id);
353 -
354 - Ok(())
355 - }
356 -
357 - /// Connect to TCP server
358 - #[wasm_bindgen(js_name = connectTcp)]
359 - pub async fn connect_tcp(&self, host: String, port: u16) -> Result<JsValue, JsValue> {
360 - // Validate address
361 - TcpCodec::validate_address(&host, port)
362 - .map_err(|e| JsValue::from_str(&e))?;
363 -
364 - // Create tunnel for TCP
365 - let tunnel = self.inner.tunnel_manager
366 - .create_tunnel(ProtocolType::Tcp)
367 - .await
368 - .map_err(|e| JsValue::from_str(&format!("failed to create tunnel: {}", e)))?;
369 -
370 - let tunnel_id = tunnel.id.clone();
371 -
372 - // Create connect request
373 - let request = ProxyRequest::TcpConnect { host, port };
374 -
375 - // Send
376 - tunnel
377 - .send_request(request)
378 - .await
379 - .map_err(|e| JsValue::from_str(&format!("failed to send: {}", e)))?;
380 -
381 - // Wait for connected response
382 - let response = tunnel
383 - .receive_response()
384 - .await
385 - .map_err(|e| JsValue::from_str(&format!("failed to receive: {}", e)))?;
386 -
387 - match response {
388 - ProxyResponse::TcpConnected { .. } => {
389 - let obj = js_sys::Object::new();
390 - js_sys::Reflect::set(&obj, &"tunnelId".into(), &tunnel_id.into())?;
391 - Ok(obj.into())
392 - }
393 - ProxyResponse::Error { error, .. } => {
394 - self.inner.tunnel_manager.remove_tunnel(&tunnel_id);
395 - Err(JsValue::from_str(&format!("failed to connect: {}", error)))
396 - }
397 - _ => {
398 - self.inner.tunnel_manager.remove_tunnel(&tunnel_id);
399 - Err(JsValue::from_str("unexpected response type"))
400 - }
401 - }
402 - }
403 -
404 - /// Get status information
405 - #[wasm_bindgen(js_name = getStatus)]
406 - pub fn get_status(&self) -> JsValue {
407 - let obj = js_sys::Object::new();
408 - let active = self.inner.tunnel_manager.active_tunnels();
409 - js_sys::Reflect::set(&obj, &"enabled".into(), &self.inner.config.enabled.into()).unwrap();
410 - js_sys::Reflect::set(&obj, &"activeTunnels".into(), &JsValue::from_f64(active.len() as f64)).unwrap();
411 - js_sys::Reflect::set(&obj, &"serverUrl".into(), &self.inner.config.server_url.clone().into()).unwrap();
412 - obj.into()
413 - }
414 -}
portal/wasm/src/relay_client.rs deleted
-283
@@ -1,283 +0,0 @@
1 -use crate::crypto::Credential;
2 -use crate::proto::{self, PacketType, ResponseCode};
3 -use crate::utils;
4 -use crate::ws_stream::WebSocketStream;
5 -use parking_lot::Mutex;
6 -use serde::{Deserialize, Serialize};
7 -use std::collections::HashMap;
8 -use std::sync::Arc;
9 -use wasm_bindgen::prelude::*;
10 -
11 -#[wasm_bindgen]
12 -#[derive(Clone)]
13 -pub struct RelayClient {
14 - inner: Arc<Mutex<RelayClientInner>>,
15 -}
16 -
17 -struct RelayClientInner {
18 - server_url: String,
19 - credential: Credential,
20 - leases: HashMap<String, LeaseInfo>,
21 -}
22 -
23 -#[derive(Clone, Serialize, Deserialize)]
24 -pub struct LeaseInfo {
25 - pub name: String,
26 - pub alpns: Vec<String>,
27 - pub expires: i64,
28 -}
29 -
30 -#[derive(Serialize, Deserialize)]
31 -pub struct RelayInfo {
32 - pub identity: IdentityInfo,
33 - pub address: Vec<String>,
34 - pub leases: Vec<String>,
35 -}
36 -
37 -#[derive(Serialize, Deserialize)]
38 -pub struct IdentityInfo {
39 - pub id: String,
40 - pub public_key: String,
41 -}
42 -
43 -#[wasm_bindgen]
44 -impl RelayClient {
45 - /// Connect to Portal server
46 - pub async fn connect(server_url: String) -> Result<RelayClient, JsValue> {
47 - // Test connection
48 - let _test_ws = WebSocketStream::connect(&server_url)
49 - .await
50 - .map_err(|e| JsValue::from_str(&format!("WebSocket connection failed: {:?}", e)))?;
51 -
52 - // Generate credential
53 - let credential = Credential::new();
54 -
55 - crate::console_log!("RelayClient created with ID: {}", credential.id());
56 -
57 - Ok(Self {
58 - inner: Arc::new(Mutex::new(RelayClientInner {
59 - server_url,
60 - credential,
61 - leases: HashMap::new(),
62 - })),
63 - })
64 - }
65 -
66 - /// Get relay server information
67 - #[wasm_bindgen(js_name = getRelayInfo)]
68 - pub async fn get_relay_info(&self) -> Result<JsValue, JsValue> {
69 - let server_url = {
70 - let inner = self.inner.lock();
71 - inner.server_url.clone()
72 - };
73 -
74 - // Connect WebSocket for this request
75 - let mut ws = WebSocketStream::connect(&server_url)
76 - .await
77 - .map_err(utils::to_js_error)?;
78 -
79 - // Create request
80 - let request = proto::RelayInfoRequest {};
81 - let payload = proto::encode_message(&request);
82 -
83 - let packet = proto::Packet {
84 - r#type: PacketType::RelayInfoRequest as i32,
85 - payload,
86 - };
87 -
88 - // Send request
89 - proto::write_packet_async(&mut ws, &packet)
90 - .await
91 - .map_err(utils::to_js_error)?;
92 -
93 - // Read response
94 - let response_packet = proto::read_packet_async(&mut ws)
95 - .await
96 - .map_err(utils::to_js_error)?;
97 -
98 - if response_packet.r#type != PacketType::RelayInfoResponse as i32 {
99 - return Err(JsValue::from_str("invalid response type"));
100 - }
101 -
102 - let response: proto::RelayInfoResponse =
103 - proto::decode_message(&response_packet.payload).map_err(utils::to_js_error)?;
104 -
105 - let relay_info = response
106 - .relay_info
107 - .ok_or_else(|| JsValue::from_str("missing relay info"))?;
108 -
109 - // Convert to JSON-friendly format
110 - let info = RelayInfo {
111 - identity: IdentityInfo {
112 - id: relay_info
113 - .identity
114 - .as_ref()
115 - .map(|i| i.id.clone())
116 - .unwrap_or_default(),
117 - public_key: relay_info
118 - .identity
119 - .as_ref()
120 - .map(|i| hex::encode(&i.public_key))
121 - .unwrap_or_default(),
122 - },
123 - address: relay_info.address,
124 - leases: relay_info.leases,
125 - };
126 -
127 - serde_wasm_bindgen::to_value(&info).map_err(utils::to_js_error)
128 - }
129 -
130 - /// Register a lease
131 - #[wasm_bindgen(js_name = registerLease)]
132 - pub async fn register_lease(&self, name: String, alpns: Vec<String>) -> Result<(), JsValue> {
133 - let (server_url, credential, lease) = {
134 - let inner = self.inner.lock();
135 -
136 - let expires = utils::unix_timestamp() + 60; // 60 seconds from now
137 - let lease = proto::Lease {
138 - identity: Some(inner.credential.identity()),
139 - expires,
140 - name: name.clone(),
141 - alpn: alpns.clone(),
142 - };
143 -
144 - (inner.server_url.clone(), inner.credential.clone(), lease)
145 - };
146 -
147 - // Connect WebSocket for this request
148 - let mut ws = WebSocketStream::connect(&server_url)
149 - .await
150 - .map_err(utils::to_js_error)?;
151 -
152 - // Create signed request
153 - let nonce = utils::random_bytes(12);
154 - let timestamp = utils::unix_timestamp();
155 -
156 - let request = proto::LeaseUpdateRequest {
157 - lease: Some(lease.clone()),
158 - nonce,
159 - timestamp,
160 - };
161 -
162 - let payload = proto::encode_message(&request);
163 - let signature = credential.sign(&payload);
164 -
165 - let signed = proto::SignedPayload {
166 - data: payload,
167 - signature,
168 - };
169 -
170 - let signed_data = proto::encode_message(&signed);
171 -
172 - let packet = proto::Packet {
173 - r#type: PacketType::LeaseUpdateRequest as i32,
174 - payload: signed_data,
175 - };
176 -
177 - // Send request
178 - proto::write_packet_async(&mut ws, &packet)
179 - .await
180 - .map_err(utils::to_js_error)?;
181 -
182 - // Read response
183 - let response_packet = proto::read_packet_async(&mut ws)
184 - .await
185 - .map_err(utils::to_js_error)?;
186 -
187 - if response_packet.r#type != PacketType::LeaseUpdateResponse as i32 {
188 - return Err(JsValue::from_str("invalid response type"));
189 - }
190 -
191 - let response: proto::LeaseUpdateResponse =
192 - proto::decode_message(&response_packet.payload).map_err(utils::to_js_error)?;
193 -
194 - if response.code != ResponseCode::Accepted as i32 {
195 - return Err(JsValue::from_str(&format!(
196 - "lease registration rejected: code {}",
197 - response.code
198 - )));
199 - }
200 -
201 - // Store lease info
202 - {
203 - let mut inner = self.inner.lock();
204 - let credential_id = inner.credential.id().to_string();
205 - inner.leases.insert(
206 - credential_id,
207 - LeaseInfo {
208 - name,
209 - alpns,
210 - expires: lease.expires,
211 - },
212 - );
213 - }
214 -
215 - crate::console_log!("Lease registered successfully");
216 -
217 - Ok(())
218 - }
219 -
220 - /// Get client credential ID
221 - #[wasm_bindgen(js_name = getCredentialId)]
222 - pub fn get_credential_id(&self) -> String {
223 - let inner = self.inner.lock();
224 - inner.credential.id().to_string()
225 - }
226 -
227 - /// Request connection to another peer
228 - #[wasm_bindgen(js_name = requestConnection)]
229 - pub async fn request_connection(
230 - &self,
231 - lease_id: String,
232 - _alpn: String,
233 - ) -> Result<JsValue, JsValue> {
234 - let (server_url, credential) = {
235 - let inner = self.inner.lock();
236 - (inner.server_url.clone(), inner.credential.clone())
237 - };
238 -
239 - // Connect WebSocket for this request
240 - let mut ws = WebSocketStream::connect(&server_url)
241 - .await
242 - .map_err(utils::to_js_error)?;
243 -
244 - // Create connection request
245 - let request = proto::ConnectionRequest {
246 - lease_id,
247 - client_identity: Some(credential.identity()),
248 - };
249 -
250 - let payload = proto::encode_message(&request);
251 -
252 - let packet = proto::Packet {
253 - r#type: PacketType::ConnectionRequest as i32,
254 - payload,
255 - };
256 -
257 - // Send request
258 - proto::write_packet_async(&mut ws, &packet)
259 - .await
260 - .map_err(utils::to_js_error)?;
261 -
262 - // Read response
263 - let response_packet = proto::read_packet_async(&mut ws)
264 - .await
265 - .map_err(utils::to_js_error)?;
266 -
267 - if response_packet.r#type != PacketType::ConnectionResponse as i32 {
268 - return Err(JsValue::from_str("invalid response type"));
269 - }
270 -
271 - let response: proto::ConnectionResponse =
272 - proto::decode_message(&response_packet.payload).map_err(utils::to_js_error)?;
273 -
274 - if response.code != ResponseCode::Accepted as i32 {
275 - return Err(JsValue::from_str(&format!(
276 - "connection rejected: code {}",
277 - response.code
278 - )));
279 - }
280 -
281 - Ok(JsValue::from_str("connection established"))
282 - }
283 -}
portal/wasm/src/tunnel_manager.rs deleted
-187
@@ -1,187 +0,0 @@
1 -/// Tunnel manager for handling E2EE connections
2 -///
3 -/// Manages the lifecycle of encrypted tunnels between browser and relay server
4 -
5 -use crate::crypto::{Credential, SecureConnection};
6 -use crate::protocol_codec::{ProtocolType, ProxyPacket, ProxyRequest, ProxyResponse};
7 -use crate::ws_stream::WebSocketStream;
8 -use parking_lot::Mutex;
9 -use std::collections::HashMap;
10 -use std::sync::Arc;
11 -
12 -/// Represents a single encrypted tunnel
13 -#[allow(dead_code)]
14 -pub struct Tunnel {
15 - pub id: String,
16 - pub protocol: ProtocolType,
17 - pub connection: Arc<Mutex<SecureConnection<WebSocketStream>>>,
18 - pub credential: Credential,
19 -}
20 -
21 -impl Tunnel {
22 - /// Create a new tunnel
23 - pub fn new(
24 - id: String,
25 - protocol: ProtocolType,
26 - connection: SecureConnection<WebSocketStream>,
27 - credential: Credential,
28 - ) -> Self {
29 - Self {
30 - id,
31 - protocol,
32 - connection: Arc::new(Mutex::new(connection)),
33 - credential,
34 - }
35 - }
36 -
37 - /// Send a request through the tunnel
38 - pub async fn send_request(&self, request: ProxyRequest) -> Result<(), String> {
39 - let packet = ProxyPacket::new_request(self.id.clone(), request);
40 - let data = packet.encode()?;
41 -
42 - // Directly write to connection
43 - {
44 - let mut conn = self.connection.lock();
45 - conn.write(&data)
46 - .await
47 - .map_err(|e| format!("failed to write to tunnel: {}", e))?;
48 - }
49 -
50 - Ok(())
51 - }
52 -
53 - /// Receive a response from the tunnel
54 - pub async fn receive_response(&self) -> Result<ProxyResponse, String> {
55 - // Read data from connection
56 - let data = {
57 - let mut conn = self.connection.lock();
58 - let mut buf = vec![0u8; 65536]; // 64KB buffer
59 -
60 - let n = conn
61 - .read(&mut buf)
62 - .await
63 - .map_err(|e| format!("failed to read from tunnel: {}", e))?;
64 -
65 - if n == 0 {
66 - return Err("tunnel closed".to_string());
67 - }
68 -
69 - buf.truncate(n);
70 - buf
71 - };
72 -
73 - let packet = ProxyPacket::decode(&data)?;
74 -
75 - match packet.payload {
76 - crate::protocol_codec::ProxyPayload::Response(resp) => Ok(resp),
77 - _ => Err("unexpected packet type".to_string()),
78 - }
79 - }
80 -}
81 -
82 -/// Manages multiple tunnels
83 -pub struct TunnelManager {
84 - tunnels: Arc<Mutex<HashMap<String, Arc<Tunnel>>>>,
85 - credential: Credential,
86 - server_url: String,
87 -}
88 -
89 -impl TunnelManager {
90 - /// Create a new tunnel manager
91 - pub fn new(credential: Credential, server_url: String) -> Self {
92 - Self {
93 - tunnels: Arc::new(Mutex::new(HashMap::new())),
94 - credential,
95 - server_url,
96 - }
97 - }
98 -
99 - /// Create a new tunnel for a specific protocol
100 - pub async fn create_tunnel(&self, protocol: ProtocolType) -> Result<Arc<Tunnel>, String> {
101 - // Connect to relay server
102 - let ws = WebSocketStream::connect(&self.server_url)
103 - .await
104 - .map_err(|e| format!("WebSocket connection failed: {:?}", e))?;
105 -
106 - // Perform E2EE handshake
107 - let alpn = match protocol {
108 - ProtocolType::Http => "http",
109 - ProtocolType::WebSocket => "websocket",
110 - ProtocolType::Tcp => "tcp",
111 - };
112 -
113 - let secure_conn = SecureConnection::client_handshake(ws, &self.credential, alpn)
114 - .await
115 - .map_err(|e| format!("E2EE handshake failed: {}", e))?;
116 -
117 - // Generate tunnel ID
118 - let tunnel_id = self.generate_tunnel_id();
119 -
120 - // Create tunnel
121 - let tunnel = Arc::new(Tunnel::new(
122 - tunnel_id.clone(),
123 - protocol,
124 - secure_conn,
125 - self.credential.clone(),
126 - ));
127 -
128 - // Store tunnel
129 - {
130 - let mut tunnels = self.tunnels.lock();
131 - tunnels.insert(tunnel_id, tunnel.clone());
132 - }
133 -
134 - Ok(tunnel)
135 - }
136 -
137 - /// Get an existing tunnel
138 - pub fn get_tunnel(&self, tunnel_id: &str) -> Option<Arc<Tunnel>> {
139 - let tunnels = self.tunnels.lock();
140 - tunnels.get(tunnel_id).cloned()
141 - }
142 -
143 - /// Remove a tunnel
144 - pub fn remove_tunnel(&self, tunnel_id: &str) {
145 - let mut tunnels = self.tunnels.lock();
146 - tunnels.remove(tunnel_id);
147 - }
148 -
149 - /// Get all active tunnel IDs
150 - pub fn active_tunnels(&self) -> Vec<String> {
151 - let tunnels = self.tunnels.lock();
152 - tunnels.keys().cloned().collect()
153 - }
154 -
155 - /// Close all tunnels
156 - #[allow(dead_code)]
157 - pub fn close_all(&self) {
158 - let mut tunnels = self.tunnels.lock();
159 - tunnels.clear();
160 - }
161 -
162 - /// Generate a unique tunnel ID
163 - fn generate_tunnel_id(&self) -> String {
164 - use rand_core::RngCore;
165 - let mut rng = rand_core::OsRng;
166 - let mut bytes = [0u8; 16];
167 - rng.fill_bytes(&mut bytes);
168 - hex::encode(bytes)
169 - }
170 -}
171 -
172 -#[cfg(test)]
173 -mod tests {
174 - use super::*;
175 -
176 - #[test]
177 - fn test_tunnel_id_generation() {
178 - let credential = Credential::new();
179 - let manager = TunnelManager::new(credential, "ws://localhost:9001/ws".to_string());
180 -
181 - let id1 = manager.generate_tunnel_id();
182 - let id2 = manager.generate_tunnel_id();
183 -
184 - assert_ne!(id1, id2);
185 - assert_eq!(id1.len(), 32); // 16 bytes hex = 32 chars
186 - }
187 -}
portal/wasm/src/utils.rs deleted
-18
@@ -1,18 +0,0 @@
1 -use wasm_bindgen::JsValue;
2 -
3 -/// Convert Rust error to JsValue
4 -pub fn to_js_error<E: std::fmt::Debug>(err: E) -> JsValue {
5 - JsValue::from_str(&format!("{:?}", err))
6 -}
7 -
8 -/// Current Unix timestamp in seconds
9 -pub fn unix_timestamp() -> i64 {
10 - (js_sys::Date::now() / 1000.0) as i64
11 -}
12 -
13 -/// Generate random bytes using browser's crypto API
14 -pub fn random_bytes(len: usize) -> Vec<u8> {
15 - let mut buf = vec![0u8; len];
16 - getrandom::getrandom(&mut buf).expect("failed to generate random bytes");
17 - buf
18 -}
portal/wasm/src/ws_stream.rs deleted
-219
@@ -1,219 +0,0 @@
1 -use futures::io::{AsyncRead, AsyncWrite};
2 -use parking_lot::Mutex;
3 -use std::collections::VecDeque;
4 -use std::io;
5 -use std::pin::Pin;
6 -use std::sync::atomic::{AtomicBool, Ordering};
7 -use std::sync::Arc;
8 -use std::task::{Context, Poll, Waker};
9 -use wasm_bindgen::prelude::*;
10 -use wasm_bindgen::JsCast;
11 -use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket};
12 -
13 -/// WebSocket stream adapter that implements AsyncRead + AsyncWrite
14 -pub struct WebSocketStream {
15 - ws: WebSocket,
16 - read_buffer: Arc<Mutex<VecDeque<u8>>>,
17 - read_waker: Arc<Mutex<Option<Waker>>>,
18 - write_waker: Arc<Mutex<Option<Waker>>>,
19 - closed: Arc<AtomicBool>,
20 - error: Arc<Mutex<Option<String>>>,
21 -}
22 -
23 -impl WebSocketStream {
24 - /// Create a new WebSocketStream and wait for connection
25 - pub async fn connect(url: &str) -> Result<Self, JsValue> {
26 - let ws = WebSocket::new(url)?;
27 - ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
28 -
29 - let read_buffer = Arc::new(Mutex::new(VecDeque::new()));
30 - let read_waker: Arc<Mutex<Option<Waker>>> = Arc::new(Mutex::new(None));
31 - let write_waker: Arc<Mutex<Option<Waker>>> = Arc::new(Mutex::new(None));
32 - let closed = Arc::new(AtomicBool::new(false));
33 - let error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
34 -
35 - // Setup onmessage handler
36 - {
37 - let read_buffer = read_buffer.clone();
38 - let read_waker = read_waker.clone();
39 -
40 - let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
41 - if let Ok(array_buffer) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
42 - let array = js_sys::Uint8Array::new(&array_buffer);
43 - let data = array.to_vec();
44 -
45 - // Add data to read buffer
46 - {
47 - let mut buffer = read_buffer.lock();
48 - buffer.extend(data.iter());
49 - }
50 -
51 - // Wake up pending read
52 - if let Some(waker) = read_waker.lock().take() {
53 - waker.wake();
54 - }
55 - }
56 - }) as Box<dyn FnMut(MessageEvent)>);
57 -
58 - ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
59 - onmessage.forget();
60 - }
61 -
62 - // Setup onerror handler
63 - {
64 - let error = error.clone();
65 - let closed = closed.clone();
66 - let read_waker = read_waker.clone();
67 -
68 - let onerror = Closure::wrap(Box::new(move |_e: ErrorEvent| {
69 - *error.lock() = Some("WebSocket error".to_string());
70 - closed.store(true, Ordering::SeqCst);
71 -
72 - if let Some(waker) = read_waker.lock().take() {
73 - waker.wake();
74 - }
75 - }) as Box<dyn FnMut(ErrorEvent)>);
76 -
77 - ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
78 - onerror.forget();
79 - }
80 -
81 - // Setup onclose handler
82 - {
83 - let closed = closed.clone();
84 - let read_waker = read_waker.clone();
85 -
86 - let onclose = Closure::wrap(Box::new(move |_e: CloseEvent| {
87 - closed.store(true, Ordering::SeqCst);
88 -
89 - if let Some(waker) = read_waker.lock().take() {
90 - waker.wake();
91 - }
92 - }) as Box<dyn FnMut(CloseEvent)>);
93 -
94 - ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
95 - onclose.forget();
96 - }
97 -
98 - // Wait for connection to open
99 - let (tx, rx) = futures::channel::oneshot::channel();
100 - let tx = Arc::new(Mutex::new(Some(tx)));
101 -
102 - {
103 - let tx = tx.clone();
104 - let onopen = Closure::wrap(Box::new(move |_| {
105 - if let Some(tx) = tx.lock().take() {
106 - let _ = tx.send(());
107 - }
108 - }) as Box<dyn FnMut(JsValue)>);
109 -
110 - ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));
111 - onopen.forget();
112 - }
113 -
114 - // Wait for open event
115 - rx.await
116 - .map_err(|_| JsValue::from_str("WebSocket connection failed"))?;
117 -
118 - Ok(Self {
119 - ws,
120 - read_buffer,
121 - read_waker,
122 - write_waker,
123 - closed,
124 - error,
125 - })
126 - }
127 -
128 - /// Check if there's an error
129 - fn check_error(&self) -> io::Result<()> {
130 - if let Some(err) = self.error.lock().as_ref() {
131 - return Err(io::Error::new(io::ErrorKind::Other, err.clone()));
132 - }
133 - Ok(())
134 - }
135 -}
136 -
137 -impl AsyncRead for WebSocketStream {
138 - fn poll_read(
139 - self: Pin<&mut Self>,
140 - cx: &mut Context<'_>,
141 - buf: &mut [u8],
142 - ) -> Poll<io::Result<usize>> {
143 - // Check for errors
144 - self.check_error()?;
145 -
146 - // Check if closed
147 - if self.closed.load(Ordering::SeqCst) {
148 - let read_buffer = self.read_buffer.lock();
149 - if read_buffer.is_empty() {
150 - return Poll::Ready(Ok(0)); // EOF
151 - }
152 - }
153 -
154 - let mut read_buffer = self.read_buffer.lock();
155 -
156 - if read_buffer.is_empty() {
157 - // No data available, register waker
158 - *self.read_waker.lock() = Some(cx.waker().clone());
159 - return Poll::Pending;
160 - }
161 -
162 - // Copy data from buffer
163 - let to_copy = buf.len().min(read_buffer.len());
164 - for i in 0..to_copy {
165 - buf[i] = read_buffer.pop_front().unwrap();
166 - }
167 -
168 - Poll::Ready(Ok(to_copy))
169 - }
170 -}
171 -
172 -impl AsyncWrite for WebSocketStream {
173 - fn poll_write(
174 - self: Pin<&mut Self>,
175 - cx: &mut Context<'_>,
176 - buf: &[u8],
177 - ) -> Poll<io::Result<usize>> {
178 - // Check for errors
179 - self.check_error()?;
180 -
181 - // Check if closed
182 - if self.closed.load(Ordering::SeqCst) {
183 - return Poll::Ready(Err(io::Error::new(
184 - io::ErrorKind::BrokenPipe,
185 - "WebSocket closed",
186 - )));
187 - }
188 -
189 - // Check buffered amount (backpressure)
190 - const MAX_BUFFER_SIZE: u32 = 64 * 1024; // 64KB
191 - if self.ws.buffered_amount() > MAX_BUFFER_SIZE {
192 - // Too much buffered data, apply backpressure
193 - *self.write_waker.lock() = Some(cx.waker().clone());
194 - return Poll::Pending;
195 - }
196 -
197 - // Send data
198 - match self.ws.send_with_u8_array(buf) {
199 - Ok(_) => Poll::Ready(Ok(buf.len())),
200 - Err(e) => Poll::Ready(Err(io::Error::new(
201 - io::ErrorKind::Other,
202 - format!("WebSocket send failed: {:?}", e),
203 - ))),
204 - }
205 - }
206 -
207 - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
208 - // WebSocket flushes automatically
209 - Poll::Ready(Ok(()))
210 - }
211 -
212 - fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
213 - if !self.closed.load(Ordering::SeqCst) {
214 - let _ = self.ws.close();
215 - self.closed.store(true, Ordering::SeqCst);
216 - }
217 - Poll::Ready(Ok(()))
218 - }
219 -}
portal/wasm/sw-proxy.js deleted
-285
@@ -1,285 +0,0 @@
1 -// Service Worker for Portal Network Proxy
2 -// WASM must be loaded during install phase
3 -
4 -const CACHE_NAME = 'portal-proxy-v1';
5 -let proxyEngine = null;
6 -let wasmReady = false;
7 -let initializationPromise = null;
8 -
9 -// Dynamic WASM initialization (can be called anytime, not just install)
10 -async function initializeProxyEngine() {
11 - // If already initializing, wait for it
12 - if (initializationPromise) {
13 - return initializationPromise;
14 - }
15 -
16 - // If already initialized, return immediately
17 - if (proxyEngine && wasmReady) {
18 - return proxyEngine;
19 - }
20 -
21 - initializationPromise = (async () => {
22 - try {
23 - console.log('[SW-Proxy] Dynamically initializing ProxyEngine...');
24 -
25 - // Check if wasm_bindgen is available (loaded during install)
26 - if (typeof wasm_bindgen === 'undefined') {
27 - console.log('[SW-Proxy] wasm_bindgen not available, loading via dynamic import...');
28 -
29 - // Use dynamic import for ES6 modules
30 - const wasmModule = await import('/pkg/portal_wasm.js');
31 -
32 - // Make wasm_bindgen available globally
33 - self.wasm_bindgen = wasmModule;
34 -
35 - console.log('[SW-Proxy] ✓ WASM JS module loaded');
36 - }
37 -
38 - // Check if SecureWebSocket SW module is loaded
39 - if (typeof ServiceWorkerWebSocketTunnel === 'undefined') {
40 - console.log('[SW-Proxy] SecureWebSocket SW module not available, loading via fetch...');
41 -
42 - const swWsResponse = await fetch('/secure-websocket-sw.js');
43 - const swWsCode = await swWsResponse.text();
44 -
45 - // Use indirect eval to execute in global scope
46 - (1, eval)(swWsCode);
47 - console.log('[SW-Proxy] ✓ SecureWebSocket SW module loaded');
48 - }
49 -
50 - // Initialize WASM module if not already done
51 - if (!wasmReady) {
52 - console.log('[SW-Proxy] Initializing WASM module...');
53 -
54 - // wasm_bindgen is now the module object from dynamic import
55 - const initWasm = self.wasm_bindgen.default || self.wasm_bindgen;
56 - await initWasm('/pkg/portal_wasm_bg.wasm');
57 -
58 - console.log('[SW-Proxy] ✓ WASM module initialized');
59 - }
60 -
61 - // Get relay URL from API
62 - let relayUrl = 'ws://localhost:4017/relay'; // Default fallback
63 - try {
64 - const relayInfoResponse = await fetch('/api/relay-info');
65 - if (relayInfoResponse.ok) {
66 - const relayInfo = await relayInfoResponse.json();
67 - if (relayInfo.relayUrl) {
68 - relayUrl = relayInfo.relayUrl;
69 - console.log('[SW-Proxy] Got relay URL from server:', relayUrl);
70 - }
71 - }
72 - } catch (e) {
73 - console.warn('[SW-Proxy] Failed to fetch relay URL, using default:', e.message);
74 - }
75 -
76 - // Create ProxyEngine
77 - console.log('[SW-Proxy] Creating ProxyEngine with URL:', relayUrl);
78 - const ProxyEngine = self.wasm_bindgen.ProxyEngine;
79 - proxyEngine = new ProxyEngine(relayUrl);
80 - wasmReady = true;
81 -
82 - console.log('[SW-Proxy] ✓ ProxyEngine ready');
83 - return proxyEngine;
84 -
85 - } catch (error) {
86 - console.error('[SW-Proxy] Failed to initialize ProxyEngine:', error);
87 - initializationPromise = null; // Reset so we can retry
88 - wasmReady = false;
89 - throw error;
90 - }
91 - })();
92 -
93 - return initializationPromise;
94 -}
95 -
96 -// Install event - Skip importScripts since WASM is ES6 module
97 -self.addEventListener('install', (event) => {
98 - console.log('[SW-Proxy] Installing...');
99 -
100 - event.waitUntil(
101 - (async () => {
102 - try {
103 - console.log('[SW-Proxy] Service Worker installing...');
104 -
105 - // Note: We cannot use importScripts with ES6 modules
106 - // WASM will be loaded dynamically on first request via fetch + dynamic import
107 - console.log('[SW-Proxy] WASM will be loaded dynamically on first request');
108 -
109 - } catch (error) {
110 - console.error('[SW-Proxy] Install error:', error);
111 - }
112 -
113 - // Skip waiting to activate immediately
114 - await self.skipWaiting();
115 - })()
116 - );
117 -});
118 -
119 -// Activate event
120 -self.addEventListener('activate', (event) => {
121 - console.log('[SW-Proxy] Activating...');
122 - event.waitUntil(self.clients.claim());
123 -});
124 -
125 -// Check if URL should be proxied
126 -function shouldProxy(url) {
127 - // Don't proxy same-origin requests (relay server itself)
128 - if (url.includes('localhost:4017') ||
129 - url.includes('localhost:8000') ||
130 - url.includes('/pkg/') ||
131 - url.includes('/sw-proxy.js') ||
132 - url.includes('/api/')) {
133 - return false;
134 - }
135 -
136 - // Only proxy /peer/* requests
137 - return url.includes('/peer/');
138 -}
139 -
140 -// Handle HTTP request through WASM proxy
141 -async function proxyHttpRequest(request) {
142 - try {
143 - // Ensure ProxyEngine is initialized (lazy init if needed)
144 - if (!wasmReady || !proxyEngine) {
145 - console.log('[SW-Proxy] ProxyEngine not ready, attempting lazy initialization...');
146 - try {
147 - await initializeProxyEngine();
148 - } catch (initError) {
149 - // Security: Never fallback to direct fetch - this would bypass E2EE!
150 - console.error('[SW-Proxy] Failed to initialize ProxyEngine:', initError);
151 - return new Response(
152 - JSON.stringify({
153 - error: 'E2EE ProxyEngine Initialization Failed',
154 - message: 'Could not initialize secure proxy. Please refresh the page.',
155 - code: 'PROXY_ENGINE_INIT_FAILED',
156 - details: initError.message
157 - }),
158 - {
159 - status: 503,
160 - statusText: 'Service Unavailable',
161 - headers: {
162 - 'Content-Type': 'application/json',
163 - 'X-E2EE-Status': 'init-failed'
164 - }
165 - }
166 - );
167 - }
168 - }
169 -
170 - console.log('[SW-Proxy] Proxying:', request.method, request.url);
171 -
172 - // Extract headers
173 - const headers = {};
174 - for (const [key, value] of request.headers.entries()) {
175 - headers[key] = value;
176 - }
177 -
178 - // Get body if present
179 - let body = null;
180 - if (request.method !== 'GET' && request.method !== 'HEAD') {
181 - try {
182 - const arrayBuffer = await request.arrayBuffer();
183 - body = Array.from(new Uint8Array(arrayBuffer));
184 - } catch (e) {
185 - console.warn('[SW-Proxy] Failed to read body:', e);
186 - }
187 - }
188 -
189 - // Call WASM ProxyEngine
190 - console.log('[SW-Proxy] Calling WASM ProxyEngine...');
191 - const response = await proxyEngine.handleHttpRequest(
192 - request.method,
193 - request.url,
194 - headers,
195 - body
196 - );
197 -
198 - console.log('[SW-Proxy] Got response:', response.status);
199 -
200 - // Reconstruct Response object
201 - const responseHeaders = new Headers();
202 - for (const [key, value] of Object.entries(response.headers || {})) {
203 - responseHeaders.set(key, value);
204 - }
205 -
206 - return new Response(response.body, {
207 - status: response.status,
208 - statusText: response.statusText || 'OK',
209 - headers: responseHeaders
210 - });
211 -
212 - } catch (error) {
213 - console.error('[SW-Proxy] Proxy error:', error);
214 - // Security: Never fallback to direct fetch - return error instead
215 - return new Response(
216 - JSON.stringify({
217 - error: 'E2EE Proxy Error',
218 - message: error.message || 'Failed to proxy request through E2EE tunnel',
219 - code: 'PROXY_ERROR'
220 - }),
221 - {
222 - status: 502,
223 - statusText: 'Bad Gateway',
224 - headers: {
225 - 'Content-Type': 'application/json',
226 - 'X-E2EE-Status': 'error'
227 - }
228 - }
229 - );
230 - }
231 -}
232 -
233 -// Fetch event - main interception point
234 -self.addEventListener('fetch', (event) => {
235 - const url = event.request.url;
236 -
237 - // Check if request should be proxied
238 - if (shouldProxy(url)) {
239 - console.log('[SW-Proxy] Intercepting:', url);
240 - event.respondWith(proxyHttpRequest(event.request));
241 - } else {
242 - // Pass through directly
243 - event.respondWith(fetch(event.request));
244 - }
245 -});
246 -
247 -// Message handler
248 -self.addEventListener('message', (event) => {
249 - const { type } = event.data || {};
250 -
251 - // Try WebSocket handler first
252 - if (typeof handleWebSocketMessage === 'function') {
253 - const handled = handleWebSocketMessage(event);
254 - if (handled instanceof Promise) {
255 - // Async handler
256 - return;
257 - } else if (handled) {
258 - // Synchronously handled
259 - return;
260 - }
261 - }
262 -
263 - // Standard message handling
264 - switch (type) {
265 - case 'GET_STATUS':
266 - event.ports[0]?.postMessage({
267 - success: true,
268 - status: {
269 - wasmReady,
270 - hasEngine: !!proxyEngine,
271 - hasWebSocket: typeof handleWebSocketMessage === 'function'
272 - }
273 - });
274 - break;
275 -
276 - case 'PING':
277 - event.ports[0]?.postMessage({ type: 'PONG', wasmReady });
278 - break;
279 -
280 - default:
281 - console.warn('[SW-Proxy] Unknown message:', type);
282 - }
283 -});
284 -
285 -console.log('[SW-Proxy] Service Worker script loaded');
portal/wasm/sw.js deleted
-120
@@ -1,120 +0,0 @@
1 -// Service Worker for Portal WASM Client
2 -const CACHE_NAME = 'portal-wasm-v1';
3 -
4 -// Files to cache
5 -const urlsToCache = [
6 - '/pkg/portal_wasm.js',
7 - '/pkg/portal_wasm_bg.wasm',
8 - '/example.html',
9 - '/adapter-test.html'
10 -];
11 -
12 -// Install event - cache files
13 -self.addEventListener('install', (event) => {
14 - console.log('[SW] Installing Service Worker...');
15 - event.waitUntil(
16 - caches.open(CACHE_NAME)
17 - .then((cache) => {
18 - console.log('[SW] Caching WASM files');
19 - return cache.addAll(urlsToCache);
20 - })
21 - .then(() => {
22 - console.log('[SW] All files cached successfully');
23 - return self.skipWaiting(); // Activate immediately
24 - })
25 - );
26 -});
27 -
28 -// Activate event - clean up old caches
29 -self.addEventListener('activate', (event) => {
30 - console.log('[SW] Activating Service Worker...');
31 - event.waitUntil(
32 - caches.keys().then((cacheNames) => {
33 - return Promise.all(
34 - cacheNames.map((cacheName) => {
35 - if (cacheName !== CACHE_NAME) {
36 - console.log('[SW] Deleting old cache:', cacheName);
37 - return caches.delete(cacheName);
38 - }
39 - })
40 - );
41 - }).then(() => {
42 - console.log('[SW] Service Worker activated');
43 - return self.clients.claim(); // Take control immediately
44 - })
45 - );
46 -});
47 -
48 -// Fetch event - serve from cache or network
49 -self.addEventListener('fetch', (event) => {
50 - const url = new URL(event.request.url);
51 -
52 - // Handle WASM files with special headers
53 - if (url.pathname.endsWith('.wasm')) {
54 - event.respondWith(
55 - caches.match(event.request)
56 - .then((response) => {
57 - if (response) {
58 - console.log('[SW] Serving WASM from cache:', url.pathname);
59 - return response;
60 - }
61 -
62 - console.log('[SW] Fetching WASM from network:', url.pathname);
63 - return fetch(event.request)
64 - .then((networkResponse) => {
65 - // Clone the response
66 - const responseToCache = networkResponse.clone();
67 -
68 - // Cache the fetched response
69 - caches.open(CACHE_NAME)
70 - .then((cache) => {
71 - cache.put(event.request, responseToCache);
72 - });
73 -
74 - return networkResponse;
75 - });
76 - })
77 - );
78 - }
79 - // Handle JS files
80 - else if (url.pathname.endsWith('portal_wasm.js')) {
81 - event.respondWith(
82 - caches.match(event.request)
83 - .then((response) => {
84 - if (response) {
85 - console.log('[SW] Serving JS from cache:', url.pathname);
86 - return response;
87 - }
88 -
89 - return fetch(event.request)
90 - .then((networkResponse) => {
91 - const responseToCache = networkResponse.clone();
92 - caches.open(CACHE_NAME)
93 - .then((cache) => {
94 - cache.put(event.request, responseToCache);
95 - });
96 - return networkResponse;
97 - });
98 - })
99 - );
100 - }
101 - // All other requests - network first, fallback to cache
102 - else {
103 - event.respondWith(
104 - fetch(event.request)
105 - .catch(() => {
106 - return caches.match(event.request);
107 - })
108 - );
109 - }
110 -});
111 -
112 -// Message handler
113 -self.addEventListener('message', (event) => {
114 - if (event.data && event.data.type === 'SKIP_WAITING') {
115 - console.log('[SW] Received SKIP_WAITING message');
116 - self.skipWaiting();
117 - }
118 -});
119 -
120 -console.log('[SW] Service Worker loaded');
sdk/sdk.go
+5 -5
@@ -12,12 +12,12 @@ import (
12 "time"
13
14 "github.com/gorilla/websocket"
15 - "github.com/gosuda/portal/portal"
16 - "github.com/gosuda/portal/portal/core/cryptoops"
17 - "github.com/gosuda/portal/portal/core/proto/rdsec"
18 - "github.com/gosuda/portal/portal/core/proto/rdverb"
19 - "github.com/gosuda/portal/portal/utils/wsstream"
15 "github.com/rs/zerolog/log"
16 + "gosuda.org/portal/portal"
17 + "gosuda.org/portal/portal/core/cryptoops"
18 + "gosuda.org/portal/portal/core/proto/rdsec"
19 + "gosuda.org/portal/portal/core/proto/rdverb"
20 + "gosuda.org/portal/portal/utils/wsstream"
21 )
22
23 func NewCredential() *cryptoops.Credential {
sdk/sdk_e2e_test.go
+3 -3
@@ -12,11 +12,11 @@ import (
12 "time"
13
14 "github.com/gorilla/websocket"
15 - "github.com/gosuda/portal/portal"
16 - "github.com/gosuda/portal/portal/core/cryptoops"
17 - "github.com/gosuda/portal/portal/utils/wsstream"
15 "github.com/rs/zerolog"
16 "github.com/rs/zerolog/log"
17 + "gosuda.org/portal/portal"
18 + "gosuda.org/portal/portal/core/cryptoops"
19 + "gosuda.org/portal/portal/utils/wsstream"
20 )
21
22 func init() {