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() {