Refactor SDK: Remove end-to-end tests, enhance ClientConfig with TLS options, and clean up WebSocket utility functions
gosunuts committed
Feb 24, 2026 at 17:30 UTC
8ea9ff6f13cf45bcfccbc9e3089e9331ba906ecb
68 files changed
+3473
-23239
Dockerfile
+2
-3
@@ -8,7 +8,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
8
make && rm -rf /var/lib/apt/lists/*
9
10
COPY cmd/relay-server/frontend ./cmd/relay-server/frontend
11
-COPY cmd/webclient ./cmd/webclient
11
COPY Makefile ./
12
13
RUN --mount=type=cache,target=/root/.npm \
@@ -19,7 +18,7 @@ FROM --platform=$BUILDPLATFORM golang:1 AS go-builder
18
WORKDIR /src
19
20
RUN apt-get update && apt-get install -y --no-install-recommends \
22
- brotli make binaryen && rm -rf /var/lib/apt/lists/*
21
+ make && rm -rf /var/lib/apt/lists/*
22
23
COPY go.mod go.sum ./
24
RUN --mount=type=cache,target=/go/pkg/mod go mod download
@@ -32,7 +31,7 @@ ARG TARGETOS
31
ARG TARGETARCH
32
RUN --mount=type=cache,target=/go/pkg/mod \
33
--mount=type=cache,target=/root/.cache/go-build \
35
- make build-wasm build-tunnel && \
34
+ make build-tunnel && \
35
GOOS=${TARGETOS} GOARCH=${TARGETARCH} make build-server
36
37
FROM gcr.io/distroless/static-debian12:nonroot
Makefile
+4
-70
@@ -1,13 +1,13 @@
1
-.PHONY: help fmt vet lint test vuln tidy all run build build-protoc build-wasm compress-wasm build-frontend build-tunnel build-server clean
1
+.PHONY: help fmt vet lint test vuln tidy all run build build-protoc build-frontend build-tunnel build-server clean
2
3
.DEFAULT_GOAL := help
4
5
help:
6
@echo "Available targets:"
7
- @echo " make build - Build everything (protoc, wasm, frontend, server)"
7
+ @echo " make build - Build everything (protoc, frontend, server)"
8
@echo " make build-protoc - Generate Go code from protobuf definitions"
9
- @echo " make build-wasm - Build and compress WASM client with optimization"
9
@echo " make build-frontend - Build React frontend (Tailwind CSS 4)"
10
+ @echo " make build-tunnel - Build portal-tunnel binaries"
11
@echo " make build-server - Build Go relay server (includes frontend build)"
12
@echo " make run - Run relay server"
13
@echo " make clean - Remove build artifacts"
@@ -38,7 +38,7 @@ run:
38
./bin/relay-server
39
40
# Convenience target
41
-build: build-wasm build-frontend build-tunnel build-server
41
+build: build-frontend build-tunnel build-server
42
43
build-protoc:
44
protoc -I . \
@@ -49,71 +49,6 @@ build-protoc:
49
portal/core/proto/rdsec/rdsec.proto \
50
portal/core/proto/rdverb/rdverb.proto
51
52
-# Build WASM artifacts with wasm-opt optimization and generate manifest
53
-# Production build: strips all debug symbols, prioritizes runtime speed
54
-build-wasm:
55
- @echo "[wasm] building webclient WASM (production, stripped symbols)..."
56
- @mkdir -p cmd/relay-server/dist/wasm
57
- @# -trimpath: removes file system paths from stack traces
58
- @# -gcflags="-l=4": inlining optimization (level 4 = aggressive)
59
- @# -ldflags "-s -w": -s strips symbol table, -w strips DWARF debug info
60
- @# -buildid=: removes build ID for reproducible builds
61
- @# -tags=prod: enables production-only code (no logging, no HTML injection in WASM)
62
- GOOS=js GOARCH=wasm go build \
63
- -trimpath \
64
- -gcflags="-l=4 -trimpath" \
65
- -ldflags="-s -w -buildid=" \
66
- -tags=prod \
67
- -o cmd/relay-server/dist/wasm/portal.wasm ./cmd/webclient
68
-
69
- @echo "[wasm] optimizing with wasm-opt (dual-pass: O4 then Oz)..."
70
- @if command -v wasm-opt >/dev/null 2>&1; then \
71
- echo "[wasm] pass 1: -O4 for runtime performance..."; \
72
- wasm-opt -O4 --enable-bulk-memory --enable-sign-ext --enable-reference-types \
73
- cmd/relay-server/dist/wasm/portal.wasm -o cmd/relay-server/dist/wasm/portal.wasm.tmp && \
74
- echo "[wasm] pass 2: -Oz for size optimization..."; \
75
- wasm-opt -Oz --enable-bulk-memory --enable-sign-ext --enable-reference-types \
76
- cmd/relay-server/dist/wasm/portal.wasm.tmp -o cmd/relay-server/dist/wasm/portal.wasm && \
77
- rm -f cmd/relay-server/dist/wasm/portal.wasm.tmp; \
78
- echo "[wasm] dual-pass optimization complete (O4 -> Oz)"; \
79
- else \
80
- echo "[wasm] WARNING: wasm-opt not found, skipping optimization"; \
81
- echo "[wasm] Install binaryen for WASM optimization: brew install binaryen (macOS) or apt-get install binaryen (Linux)"; \
82
- fi
83
-
84
- @echo "[wasm] calculating SHA256 hash..."
85
- @WASM_HASH=$$(shasum -a 256 cmd/relay-server/dist/wasm/portal.wasm | awk '{print $$1}'); \
86
- echo "[wasm] SHA256: $$WASM_HASH"; \
87
- echo "[wasm] cleaning old hash files..."; \
88
- find cmd/relay-server/dist/wasm -name '[0-9a-f]*.wasm' ! -name "$$WASM_HASH.wasm" -type f -delete 2>/dev/null || true; \
89
- cp cmd/relay-server/dist/wasm/portal.wasm cmd/relay-server/dist/wasm/$$WASM_HASH.wasm; \
90
- rm -f cmd/relay-server/dist/wasm/portal.wasm; \
91
- echo "[wasm] content-addressed WASM: dist/wasm/$$WASM_HASH.wasm"
92
-
93
- @echo "[wasm] copying additional resources..."
94
- @cp cmd/webclient/wasm_exec.js cmd/relay-server/dist/wasm/wasm_exec.js
95
- @cp cmd/webclient/service-worker.js cmd/relay-server/dist/wasm/service-worker.js
96
- @cp cmd/webclient/index.html cmd/relay-server/dist/wasm/portal.html
97
- @cp cmd/webclient/portal.mp4 cmd/relay-server/dist/wasm/portal.mp4
98
- @cp cmd/webclient/portal.jpg cmd/relay-server/dist/wasm/portal.jpg
99
- @echo "[wasm] build complete"
100
-
101
- @echo "[wasm] precompressing webclient WASM with brotli..."
102
- @WASM_FILE=$$(ls cmd/relay-server/dist/wasm/[0-9a-f]*.wasm 2>/dev/null | head -n1); \
103
- if [ -z "$$WASM_FILE" ]; then \
104
- echo "[wasm] ERROR: no content-addressed WASM found in cmd/relay-server/dist/wasm; run build-wasm first"; \
105
- exit 1; \
106
- fi; \
107
- WASM_HASH=$$(basename "$$WASM_FILE" .wasm); \
108
- if ! command -v brotli >/dev/null 2>&1; then \
109
- echo "[wasm] ERROR: brotli not found; install brotli to build compressed WASM"; \
110
- exit 1; \
111
- fi; \
112
- brotli -f "$$WASM_FILE" -o "cmd/relay-server/dist/wasm/$$WASM_HASH.wasm.br"; \
113
- rm -f "$$WASM_FILE"; \
114
- echo "[wasm] brotli: cmd/relay-server/dist/wasm/$$WASM_HASH.wasm.br"
115
-
116
-
52
# Build React frontend with Tailwind CSS 4
53
build-frontend:
54
@echo "[frontend] building React frontend..."
@@ -143,5 +78,4 @@ build-server:
78
clean:
79
rm -rf bin
80
rm -rf cmd/relay-server/dist/app
146
- rm -rf cmd/relay-server/dist/wasm
81
rm -rf cmd/relay-server/dist/tunnel
cmd/demo-app/main.go
+5
-42
@@ -14,11 +14,9 @@ import (
14
"syscall"
15
"time"
16
17
- "github.com/gorilla/websocket"
17
"github.com/rs/zerolog/log"
18
19
"gosuda.org/portal/sdk"
21
- "gosuda.org/portal/utils"
20
)
21
22
//go:embed static
@@ -38,7 +36,7 @@ var (
36
)
37
38
func main() {
41
- flag.StringVar(&flagServerURL, "server-url", "ws://localhost:4017/relay", "relay websocket URL")
39
+ flag.StringVar(&flagServerURL, "server-url", "ws://localhost:4017/relay", "relay URL (ws/wss/http/https)")
40
flag.IntVar(&flagPort, "port", 8092, "local demo HTTP port")
41
flag.StringVar(&flagName, "name", "demo-app", "backend display name")
42
flag.StringVar(&flagDesc, "description", "Portal demo connectivity app", "lease description")
@@ -53,52 +51,20 @@ func main() {
51
}
52
}
53
56
-// handleWS is a minimal WebSocket echo handler to verify bidirectional connectivity.
57
-func handleWS(w http.ResponseWriter, r *http.Request) {
58
- conn, err := utils.UpgradeWebSocket(w, r, nil)
59
- if err != nil {
60
- log.Error().Err(err).Msg("upgrade websocket")
61
- return
62
- }
63
- defer conn.Close()
64
-
65
- for {
66
- messageType, data, err := conn.ReadMessage()
67
- if err != nil {
68
- if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
69
- log.Error().Err(err).Msg("read websocket message")
70
- }
71
- break
72
- }
73
-
74
- if err := conn.WriteMessage(messageType, data); err != nil {
75
- log.Error().Err(err).Msg("write websocket message")
76
- break
77
- }
78
- }
79
-}
80
-
54
func runDemo() error {
82
- // 1) Create credential for this demo app
83
- cred := sdk.NewCredential()
84
-
85
- // 2) Create SDK client and connect to relay(s)
86
- client, err := sdk.NewClient(func(c *sdk.ClientConfig) {
87
- c.BootstrapServers = []string{flagServerURL}
88
- })
55
+ // 1) Create SDK client and connect to relay(s)
56
+ client, err := sdk.NewClient(sdk.WithBootstrapServers([]string{flagServerURL}))
57
if err != nil {
58
return fmt.Errorf("new client: %w", err)
59
}
60
defer client.Close()
61
94
- // 3) Register lease
62
+ // 2) Register lease
63
// Create base64 data URI from embedded thumbnail
64
thumbnailDataURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(thumbnailPNG)
65
66
listener, err := client.Listen(
99
- cred,
67
flagName,
101
- []string{"http/1.1"},
68
sdk.WithDescription(flagDesc),
69
sdk.WithTags(strings.Split(flagTags, ",")),
70
sdk.WithOwner(flagOwner),
@@ -132,9 +98,6 @@ func runDemo() error {
98
}
99
})
100
135
- // WebSocket echo endpoint for bidirectional test
136
- mux.HandleFunc("/ws", handleWS)
137
-
101
// Test endpoint for multiple Set-Cookie headers
102
// Note: HttpOnly cookies cannot be set via Service Worker (browser security limitation)
103
mux.HandleFunc("/api/test-cookies", func(w http.ResponseWriter, r *http.Request) {
@@ -169,7 +132,7 @@ func runDemo() error {
132
})
133
134
// 5) Serve HTTP over relay listener
172
- log.Info().Msgf("[demo] serving HTTP over relay; lease=%s id=%s", flagName, cred.ID())
135
+ log.Info().Msgf("[demo] serving HTTP over relay; lease=%s", flagName)
136
137
// Also serve on local port for direct testing
138
go func() {
cmd/portal-tunnel/main.go
+108
-26
@@ -10,6 +10,7 @@ import (
10
"strings"
11
"sync"
12
"syscall"
13
+ "time"
14
15
"github.com/rs/zerolog/log"
16
"gopkg.eu.org/broccoli"
@@ -34,6 +35,13 @@ type Config struct {
35
Host string `flag:"host" env:"APP_HOST" about:"Target host to proxy to (host:port or URL)"`
36
Name string `flag:"name" env:"APP_NAME" about:"Service name"`
37
38
+ // TLS Mode
39
+ TLSEnable bool `flag:"tls" env:"TLS_ENABLE" about:"Enable TLS termination on tunnel client (requires TLS cert)"`
40
+ TLSDomain string `flag:"tls-domain" env:"TLS_DOMAIN" about:"Domain for TLS certificate (e.g., tunnel.example.com)"`
41
+ TLSCert string `flag:"tls-cert" env:"TLS_CERT" about:"Path to TLS certificate file (optional, uses autocert if not set)"`
42
+ TLSKey string `flag:"tls-key" env:"TLS_KEY" about:"Path to TLS key file (optional, uses autocert if not set)"`
43
+ TLSAutocert bool `flag:"tls-autocert" env:"TLS_AUTOCERT" about:"Use Let's Encrypt autocert for TLS (default: true if TLS enabled)"`
44
+
45
// Metadata
46
Protocols string `flag:"protocols" env:"APP_PROTOCOLS" default:"http/1.1,h2" about:"ALPN protocols (comma-separated)"`
47
Description string `flag:"description" env:"APP_DESCRIPTION" about:"Service description metadata"`
@@ -97,38 +105,45 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, cfg Config, origi
105
if len(relayURLs) == 0 {
106
return fmt.Errorf("no relay URLs provided")
107
}
100
- protocols := strings.Split(cfg.Protocols, ",")
101
- if len(protocols) == 0 {
102
- protocols = []string{"http/1.1", "h2"}
103
- }
108
105
- cred := sdk.NewCredential()
106
- leaseID := cred.ID()
107
- if cfg.Name == "" {
108
- cfg.Name = fmt.Sprintf("tunnel-%s", leaseID[:8])
109
- log.Info().Str("service", cfg.Name).Msg("No service name provided; generated automatically")
110
- }
109
log.Info().Str("service", cfg.Name).Msgf("Local service is reachable at %s", cfg.Host)
110
log.Info().Str("service", cfg.Name).Msgf("Starting Portal Tunnel (%s)...", origin)
111
log.Info().Str("service", cfg.Name).Msgf(" Local: %s", cfg.Host)
112
log.Info().Str("service", cfg.Name).Msgf(" Relays: %s", strings.Join(relayURLs, ", "))
115
- log.Info().Str("service", cfg.Name).Msgf(" Lease ID: %s", leaseID)
113
+ log.Info().Str("service", cfg.Name).Msgf(" TLS Mode: %v", cfg.TLSEnable)
114
+
115
+ // Build SDK client options
116
+ var clientOpts []sdk.ClientOption
117
+ clientOpts = append(clientOpts, sdk.WithBootstrapServers(relayURLs))
118
+
119
+ // Configure TLS if enabled
120
+ if cfg.TLSEnable {
121
+ if cfg.TLSDomain == "" {
122
+ return fmt.Errorf("TLS enabled but domain not specified")
123
+ }
124
+ clientOpts = append(clientOpts, sdk.WithTLS(cfg.TLSDomain))
125
+ if cfg.TLSCert != "" && cfg.TLSKey != "" {
126
+ clientOpts = append(clientOpts, sdk.WithTLSCert(cfg.TLSCert, cfg.TLSKey))
127
+ }
128
+ }
129
117
- client, err := sdk.NewClient(func(c *sdk.ClientConfig) {
118
- c.BootstrapServers = relayURLs
119
- })
130
+ client, err := sdk.NewClient(clientOpts...)
131
if err != nil {
121
- return fmt.Errorf("service %s: failed to connect to relay: %w", cfg.Name, err)
132
+ return fmt.Errorf("service %s: failed to create client: %w", cfg.Name, err)
133
}
134
defer client.Close()
135
125
- listener, err := client.Listen(cred, cfg.Name, protocols,
136
+ // Create metadata options
137
+ metadataOptions := []sdk.MetadataOption{
138
sdk.WithDescription(cfg.Description),
127
- sdk.WithTags(strings.Split(cfg.Tags, ",")),
139
+ sdk.WithTags(splitCSV(cfg.Tags)),
140
sdk.WithOwner(cfg.Owner),
141
sdk.WithThumbnail(cfg.Thumbnail),
142
sdk.WithHide(cfg.Hide),
131
- )
143
+ }
144
+
145
+ // Create listener (with or without TLS based on config)
146
+ listener, err := client.Listen(cfg.Name, metadataOptions...)
147
if err != nil {
148
return fmt.Errorf("service %s: failed to register service: %w", cfg.Name, err)
149
}
@@ -141,9 +156,13 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, cfg Config, origi
156
157
log.Info().Str("service", cfg.Name).Msg("")
158
log.Info().Str("service", cfg.Name).Msg("Access via:")
144
- log.Info().Str("service", cfg.Name).Msgf("- Name: /peer/%s", cfg.Name)
145
- log.Info().Str("service", cfg.Name).Msgf("- Lease ID: /peer/%s", leaseID)
146
- log.Info().Str("service", cfg.Name).Msgf("- Example: %s/peer/%s", relayURLs[0], cfg.Name)
159
+ log.Info().Str("service", cfg.Name).Msgf("- Relay: %s", relayURLs[0])
160
+ if leaseAware, ok := listener.(interface{ LeaseID() string }); ok {
161
+ log.Info().Str("service", cfg.Name).Msgf("- Lease ID: %s", leaseAware.LeaseID())
162
+ }
163
+ if cfg.TLSEnable {
164
+ log.Info().Str("service", cfg.Name).Msgf("- TLS: Enabled (%s)", cfg.TLSDomain)
165
+ }
166
167
log.Info().Str("service", cfg.Name).Msg("")
168
@@ -174,24 +193,87 @@ func runServiceTunnel(ctx context.Context, relayURLs []string, cfg Config, origi
193
connWG.Add(1)
194
go func(relayConn net.Conn) {
195
defer connWG.Done()
196
+ proxyType := "TCP"
197
+ if cfg.TLSEnable {
198
+ proxyType = "TLS→TCP"
199
+ }
200
if err := proxyConnection(ctx, cfg.Host, relayConn); err != nil {
178
- log.Error().Str("service", cfg.Name).Err(err).Msg("Proxy error")
201
+ log.Error().Str("service", cfg.Name).Str("proxy", proxyType).Err(err).Msg("Proxy error")
202
}
180
- log.Info().Str("service", cfg.Name).Msg("Connection closed")
203
+ log.Info().Str("service", cfg.Name).Str("proxy", proxyType).Msg("Connection closed")
204
}(relayConn)
205
}
206
}
207
208
+func splitCSV(raw string) []string {
209
+ parts := strings.Split(raw, ",")
210
+ out := make([]string, 0, len(parts))
211
+ for _, part := range parts {
212
+ part = strings.TrimSpace(part)
213
+ if part != "" {
214
+ out = append(out, part)
215
+ }
216
+ }
217
+ return out
218
+}
219
+
220
+// proxyConnection proxies data between relay and local service.
221
+// If local service is not available, it retries with backoff instead of failing immediately.
222
func proxyConnection(ctx context.Context, localAddr string, relayConn net.Conn) error {
223
defer relayConn.Close()
224
188
- dialer := new(net.Dialer)
189
- localConn, err := dialer.DialContext(ctx, "tcp", localAddr)
225
+ // Try to connect to local service with retry
226
+ var localConn net.Conn
227
+ var err error
228
+
229
+ maxRetries := 30
230
+ retryDelay := 500 * time.Millisecond
231
+
232
+ for i := 0; i < maxRetries; i++ {
233
+ select {
234
+ case <-ctx.Done():
235
+ return ctx.Err()
236
+ default:
237
+ }
238
+
239
+ dialer := &net.Dialer{Timeout: 5 * time.Second}
240
+ localConn, err = dialer.DialContext(ctx, "tcp", localAddr)
241
+ if err == nil {
242
+ break
243
+ }
244
+
245
+ if i == 0 {
246
+ log.Warn().
247
+ Str("local_addr", localAddr).
248
+ Err(err).
249
+ Msg("Local service not ready, retrying...")
250
+ }
251
+
252
+ time.Sleep(retryDelay)
253
+ }
254
+
255
if err != nil {
191
- return fmt.Errorf("failed to connect to local service %s: %w", localAddr, err)
256
+ // Return HTTP 503 response to relay instead of closing connection
257
+ log.Error().
258
+ Str("local_addr", localAddr).
259
+ Err(err).
260
+ Msg("Failed to connect to local service after retries")
261
+
262
+ // Send HTTP 503 response
263
+ httpResponse := "HTTP/1.1 503 Service Unavailable\r\n" +
264
+ "Content-Type: text/plain\r\n" +
265
+ "Content-Length: 29\r\n" +
266
+ "Connection: close\r\n" +
267
+ "\r\n" +
268
+ "Local service not available"
269
+ relayConn.Write([]byte(httpResponse))
270
+ return fmt.Errorf("local service unavailable: %w", err)
271
}
272
+
273
defer localConn.Close()
274
275
+ log.Info().Str("local_addr", localAddr).Msg("Connected to local service")
276
+
277
errCh := make(chan error, 2)
278
stopCh := make(chan struct{})
279
go func() {
cmd/relay-server/admin.go
+7
-6
@@ -590,7 +590,7 @@ func (a *Admin) convertLeaseEntriesToAdminRows(serv *portal.RelayServer) []lease
590
}
591
592
lease := leaseEntry.Lease
593
- identityID := string(lease.Identity.Id)
593
+ identityID := lease.ID
594
595
ttl := time.Until(leaseEntry.Expires)
596
ttlStr := ""
@@ -630,16 +630,17 @@ func (a *Admin) convertLeaseEntriesToAdminRows(serv *portal.RelayServer) []lease
630
lastSeenISO := leaseEntry.LastSeen.UTC().Format(time.RFC3339)
631
firstSeenISO := leaseEntry.FirstSeen.UTC().Format(time.RFC3339)
632
633
- connected := serv.IsConnectionActive(leaseEntry.ConnectionID)
633
+ connected := since < 15*time.Second
634
635
name := lease.Name
636
if name == "" {
637
name = "(unnamed)"
638
}
639
640
- kind := "client"
641
- if len(lease.Alpn) > 0 {
642
- kind = lease.Alpn[0]
640
+ // Determine protocol based on TLS setting
641
+ kind := "http"
642
+ if lease.TLSEnabled {
643
+ kind = "https"
644
}
645
646
dnsLabel := identityID
@@ -681,7 +682,7 @@ func (a *Admin) convertLeaseEntriesToAdminRows(serv *portal.RelayServer) []lease
682
Link: link,
683
StaleRed: !connected && since >= 15*time.Second,
684
Hide: leaseEntry.ParsedMetadata != nil && leaseEntry.ParsedMetadata.Hide,
684
- Metadata: lease.Metadata,
685
+ Metadata: "", // TODO: Convert portal.Metadata to string if needed
686
BPS: bps,
687
IsApproved: a.approveManager.GetApprovalMode() == manager.ApprovalModeAuto || a.approveManager.IsLeaseApproved(identityID),
688
IsDenied: a.approveManager.IsLeaseDenied(identityID),
cmd/relay-server/frontend.go
+9
-423
@@ -7,7 +7,6 @@ import (
7
"io/fs"
8
"net/http"
9
"path"
10
- "strconv"
10
"strings"
11
"sync"
12
"time"
@@ -15,7 +14,6 @@ import (
14
"github.com/rs/zerolog/log"
15
"gosuda.org/portal/cmd/relay-server/manager"
16
"gosuda.org/portal/portal"
18
- "gosuda.org/portal/sdk"
17
"gosuda.org/portal/utils"
18
)
19
@@ -31,15 +29,11 @@ type Frontend struct {
29
30
cachedPortalHTML []byte
31
cachedPortalHTMLOnce sync.Once
34
-
35
- wasmCache map[string]*wasmCacheEntry
36
- wasmCacheMu sync.RWMutex
32
}
33
34
func NewFrontend() *Frontend {
35
return &Frontend{
41
- distFS: distFS,
42
- wasmCache: make(map[string]*wasmCacheEntry),
36
+ distFS: distFS,
37
}
38
}
39
@@ -103,58 +97,6 @@ func (f *Frontend) servePortalHTMLWithSSR(w http.ResponseWriter, r *http.Request
97
log.Debug().Msg("Served portal.html with SSR data")
98
}
99
106
-// ServePortalHTMLWithSSR serves portal.html for subdomain requests with SSR OG metadata.
107
-func (f *Frontend) ServePortalHTMLWithSSR(w http.ResponseWriter, r *http.Request, serv *portal.RelayServer) {
108
- utils.SetCORSHeaders(w)
109
-
110
- // Read portal.html from dist/wasm
111
- data, err := f.distFS.ReadFile("dist/wasm/portal.html")
112
- if err != nil {
113
- log.Error().Err(err).Msg("Failed to read dist/wasm/portal.html")
114
- http.NotFound(w, r)
115
- return
116
- }
117
-
118
- htmlContent := string(data)
119
- title := ""
120
- description := ""
121
- imageURL := ""
122
-
123
- // Extract lease name from host
124
- leaseName := ""
125
- h := strings.ToLower(utils.StripPort(utils.StripScheme(r.Host)))
126
- p := strings.ToLower(utils.StripPort(utils.StripScheme(flagPortalAppURL)))
127
- if strings.HasPrefix(p, "*.") {
128
- suffix := p[1:] // .example.com
129
- if strings.HasSuffix(h, suffix) {
130
- leaseName = h[:len(h)-len(suffix)]
131
- }
132
- }
133
-
134
- if leaseName != "" {
135
- if lease, ok := serv.GetLeaseByName(leaseName); ok {
136
- title = lease.Lease.Name
137
- if lease.ParsedMetadata != nil {
138
- description = lease.ParsedMetadata.Description
139
- imageURL = lease.ParsedMetadata.Thumbnail
140
- }
141
- }
142
- }
143
-
144
- // Inject OG metadata
145
- htmlContent = f.injectOGMetadata(htmlContent, title, description, imageURL)
146
-
147
- // Set headers
148
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
149
- w.Header().Set("Cache-Control", "no-cache, must-revalidate")
150
-
151
- // Send response
152
- w.WriteHeader(http.StatusOK)
153
- w.Write([]byte(htmlContent))
154
-
155
- log.Debug().Str("lease", leaseName).Msg("Served portal.html with subdomain SSR OG metadata")
156
-}
157
-
100
// injectOGMetadata replaces OG placeholders with actual values.
101
func (f *Frontend) injectOGMetadata(htmlContent, title, description, imageURL string) string {
102
if title == "" {
@@ -228,10 +170,9 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin) []leaseRo
170
}
171
172
lease := leaseEntry.Lease
231
- identityID := string(lease.Identity.Id)
173
+ identityID := lease.ID
174
233
- var metadata sdk.Metadata
234
- _ = json.Unmarshal([]byte(lease.Metadata), &metadata)
175
+ metadata := lease.Metadata
176
177
if _, banned := bannedMap[identityID]; banned {
178
continue
@@ -286,7 +227,7 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin) []leaseRo
227
lastSeenISO := leaseEntry.LastSeen.UTC().Format(time.RFC3339)
228
firstSeenISO := leaseEntry.FirstSeen.UTC().Format(time.RFC3339)
229
289
- connected := serv.IsConnectionActive(leaseEntry.ConnectionID)
230
+ connected := since < 15*time.Second
231
232
if !connected && since >= 3*time.Minute {
233
continue
@@ -297,9 +238,10 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin) []leaseRo
238
name = "(unnamed)"
239
}
240
300
- kind := "client"
301
- if len(lease.Alpn) > 0 {
302
- kind = lease.Alpn[0]
241
+ // Determine protocol based on TLS setting
242
+ kind := "http"
243
+ if lease.TLSEnabled {
244
+ kind = "https"
245
}
246
247
dnsLabel := identityID
@@ -331,7 +273,7 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin) []leaseRo
273
Link: link,
274
StaleRed: !connected && since >= 15*time.Second,
275
Hide: leaseEntry.ParsedMetadata != nil && leaseEntry.ParsedMetadata.Hide,
334
- Metadata: lease.Metadata,
276
+ Metadata: "",
277
BPS: bps,
278
}
279
@@ -343,22 +285,6 @@ func convertLeaseEntriesToRows(serv *portal.RelayServer, admin *Admin) []leaseRo
285
return rows
286
}
287
346
-// ServePortalStaticFile serves static files for portal frontend with caching.
347
-func (f *Frontend) ServePortalStaticFile(w http.ResponseWriter, r *http.Request, filePath string) {
348
- // Check if this is a content-addressed WASM file
349
- if strings.HasSuffix(filePath, ".wasm") {
350
- hash := strings.TrimSuffix(filePath, ".wasm")
351
- if utils.IsHexString(hash) {
352
- f.serveCompressedWasm(w, r, filePath)
353
- return
354
- }
355
- }
356
-
357
- // Regular static file serving
358
- w.Header().Set("Cache-Control", "public, max-age=3600")
359
- f.ServeStaticFile(w, r, filePath, "")
360
-}
361
-
288
// ServeAppStatic serves static files for app UI (React app) from embedded FS.
289
// Falls back to portal.html with SSR when path is root or file not found.
290
func (f *Frontend) ServeAppStatic(w http.ResponseWriter, r *http.Request, appPath string, serv *portal.RelayServer) {
@@ -402,343 +328,3 @@ func (f *Frontend) ServeAppStatic(w http.ResponseWriter, r *http.Request, appPat
328
Int("size", len(data)).
329
Msg("served app static file")
330
}
405
-
406
-type wasmCacheEntry struct {
407
- brotli []byte
408
- hash string
409
-}
410
-
411
-// initWasmCache loads pre-built WASM artifacts (precompressed) into memory on startup.
412
-func (f *Frontend) InitWasmCache() error {
413
- // Read all files in embedded dist/wasm directory
414
- entries, err := f.distFS.ReadDir("dist/wasm")
415
- if err != nil {
416
- return err
417
- }
418
-
419
- for _, entry := range entries {
420
- if entry.IsDir() {
421
- continue
422
- }
423
-
424
- name := entry.Name()
425
- // Look for content-addressed WASM files: <hex>.wasm.br
426
- if strings.HasSuffix(name, ".wasm.br") {
427
- hash := strings.TrimSuffix(name, ".wasm.br")
428
- if utils.IsHexString(hash) {
429
- fullPath := path.Join("dist", "wasm", name)
430
- // Cache under the URL path (<hash>.wasm) while reading the
431
- // brotli-compressed artifact (<hash>.wasm.br) from embed.FS.
432
- cacheKey := hash + ".wasm"
433
- if err := f.cacheWasmFile(cacheKey, fullPath); err != nil {
434
- log.Warn().Err(err).Str("file", name).Msg("failed to cache WASM file")
435
- } else {
436
- log.Info().Str("file", cacheKey).Msg("cached WASM file")
437
- }
438
- }
439
- }
440
- }
441
-
442
- return nil
443
-}
444
-
445
-// cacheWasmFile reads and caches a WASM file and its pre-compressed variant (brotli).
446
-func (f *Frontend) cacheWasmFile(name, fullPath string) error {
447
- // Verify name looks like a hex hash (name is <hash>.wasm).
448
- hashHex := strings.TrimSuffix(name, ".wasm")
449
- if !utils.IsHexString(hashHex) {
450
- log.Warn().Str("file", name).Msg("WASM file name is not a valid SHA256 hex string")
451
- }
452
-
453
- // Load precompressed variant (brotli) from embed.FS (<hash>.wasm.br)
454
- var brData []byte
455
- data, err := f.distFS.ReadFile(fullPath)
456
- if err != nil {
457
- log.Warn().Err(err).Str("file", fullPath).Msg("failed to read brotli-compressed WASM")
458
- } else {
459
- brData = data
460
- }
461
-
462
- entry := &wasmCacheEntry{
463
- brotli: brData,
464
- hash: hashHex,
465
- }
466
-
467
- f.wasmCacheMu.Lock()
468
- f.wasmCache[name] = entry
469
- f.wasmCacheMu.Unlock()
470
-
471
- log.Debug().
472
- Str("file", name).
473
- Int("brotli", len(entry.brotli)).
474
- Msg("WASM file cached")
475
-
476
- return nil
477
-}
478
-
479
-// serveCompressedWasm serves pre-compressed WASM files from memory cache
480
-func (f *Frontend) serveCompressedWasm(w http.ResponseWriter, r *http.Request, filePath string) {
481
- f.wasmCacheMu.RLock()
482
- entry, ok := f.wasmCache[filePath]
483
- f.wasmCacheMu.RUnlock()
484
-
485
- if !ok {
486
- log.Debug().Str("path", filePath).Msg("WASM file not in cache")
487
- // Fallback: try to serve uncompressed WASM from embedded FS
488
- fullPath := path.Join("dist", "wasm", filePath)
489
- data, err := f.distFS.ReadFile(fullPath)
490
- if err != nil {
491
- log.Debug().Err(err).Str("path", fullPath).Msg("WASM file not found in embedded FS")
492
- http.NotFound(w, r)
493
- return
494
- }
495
-
496
- // Serve uncompressed WASM
497
- utils.SetCORSHeaders(w)
498
- w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
499
- w.Header().Set("Content-Type", "application/wasm")
500
- w.Header().Set("Content-Length", strconv.Itoa(len(data)))
501
- w.WriteHeader(http.StatusOK)
502
- w.Write(data)
503
- log.Debug().
504
- Str("path", filePath).
505
- Int("size", len(data)).
506
- Msg("served uncompressed WASM from embedded FS")
507
- return
508
- }
509
-
510
- // Set immutable cache headers for content-addressed files
511
- utils.SetCORSHeaders(w)
512
- w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
513
- w.Header().Set("Content-Type", "application/wasm")
514
-
515
- // Check Accept-Encoding header for brotli support
516
- acceptEncoding := r.Header.Get("Accept-Encoding")
517
-
518
- // Require brotli-compressed WASM
519
- if !strings.Contains(acceptEncoding, "br") || len(entry.brotli) == 0 {
520
- log.Warn().
521
- Str("path", filePath).
522
- Str("acceptEncoding", acceptEncoding).
523
- Msg("client does not support brotli or brotli variant missing for WASM")
524
- http.Error(w, "brotli-compressed WASM required", http.StatusNotAcceptable)
525
- return
526
- }
527
-
528
- w.Header().Set("Content-Encoding", "br")
529
- w.Header().Set("Content-Length", strconv.Itoa(len(entry.brotli)))
530
- w.WriteHeader(http.StatusOK)
531
- w.Write(entry.brotli)
532
- log.Debug().
533
- Str("path", filePath).
534
- Int("size", len(entry.brotli)).
535
- Str("encoding", "brotli").
536
- Msg("served compressed WASM")
537
-}
538
-
539
-// ServePortalStatic serves static files for portal frontend with appropriate cache headers.
540
-// Falls back to portal.html for SPA routing (404 -> portal.html).
541
-func (f *Frontend) ServePortalStatic(w http.ResponseWriter, r *http.Request) {
542
- staticPath := strings.TrimPrefix(r.URL.Path, "/")
543
-
544
- // Prevent directory traversal
545
- if strings.Contains(staticPath, "..") {
546
- http.Error(w, "Invalid path", http.StatusBadRequest)
547
- return
548
- }
549
-
550
- // Special handling for specific files
551
- switch staticPath {
552
- case "manifest.json":
553
- // Serve dynamic manifest regardless of static presence
554
- f.ServeDynamicManifest(w, r)
555
- return
556
-
557
- case "service-worker.js":
558
- f.ServeDynamicServiceWorker(w, r)
559
- return
560
-
561
- case "wasm_exec.js":
562
- w.Header().Set("Cache-Control", "public, max-age=86400")
563
- w.Header().Set("Content-Type", "application/javascript")
564
- f.serveStaticFileWithFallback(w, r, staticPath, "application/javascript")
565
- return
566
-
567
- case "portal.mp4":
568
- w.Header().Set("Cache-Control", "public, max-age=604800")
569
- w.Header().Set("Content-Type", "video/mp4")
570
- f.serveStaticFileWithFallback(w, r, staticPath, "video/mp4")
571
- return
572
-
573
- case "portal.jpg":
574
- w.Header().Set("Cache-Control", "public, max-age=604800")
575
- w.Header().Set("Content-Type", "image/jpeg")
576
- f.serveStaticFileWithFallback(w, r, staticPath, "image/jpeg")
577
- return
578
- }
579
-
580
- // Default caching for other files
581
- w.Header().Set("Cache-Control", "public, max-age=3600")
582
- f.serveStaticFileWithFallback(w, r, staticPath, "")
583
-}
584
-
585
-// ServeStaticFile reads and serves a file from the static directory.
586
-func (f *Frontend) ServeStaticFile(w http.ResponseWriter, r *http.Request, filePath string, contentType string) {
587
- utils.SetCORSHeaders(w)
588
-
589
- fullPath := path.Join("dist", "wasm", filePath)
590
- data, err := f.distFS.ReadFile(fullPath)
591
- if err != nil {
592
- log.Debug().Err(err).Str("path", filePath).Msg("static file not found")
593
- http.NotFound(w, r)
594
- return
595
- }
596
-
597
- // Set content type
598
- if contentType != "" {
599
- w.Header().Set("Content-Type", contentType)
600
- } else {
601
- ext := path.Ext(filePath)
602
- ct := utils.GetContentType(ext)
603
- if ct != "" {
604
- w.Header().Set("Content-Type", ct)
605
- }
606
- }
607
-
608
- log.Debug().
609
- Str("path", filePath).
610
- Int("size", len(data)).
611
- Msg("served static file")
612
-
613
- w.WriteHeader(http.StatusOK)
614
- w.Write(data)
615
-}
616
-
617
-// serveStaticFileWithFallback reads and serves a file from the static directory
618
-// If the file is not found, it falls back to portal.html for SPA routing
619
-func (f *Frontend) serveStaticFileWithFallback(w http.ResponseWriter, r *http.Request, filePath string, contentType string) {
620
- utils.SetCORSHeaders(w)
621
-
622
- fullPath := path.Join("dist", "wasm", filePath)
623
- data, err := f.distFS.ReadFile(fullPath)
624
- if err != nil {
625
- // File not found - fallback to portal.html for SPA routing
626
- log.Debug().Err(err).Str("path", filePath).Msg("static file not found, serving portal.html")
627
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
628
- f.ServeStaticFile(w, r, "portal.html", "text/html; charset=utf-8")
629
- return
630
- }
631
-
632
- // Set content type
633
- if contentType != "" {
634
- w.Header().Set("Content-Type", contentType)
635
- } else {
636
- ext := path.Ext(filePath)
637
- ct := utils.GetContentType(ext)
638
- if ct != "" {
639
- w.Header().Set("Content-Type", ct)
640
- }
641
- }
642
-
643
- log.Debug().
644
- Str("path", filePath).
645
- Int("size", len(data)).
646
- Msg("served static file")
647
-
648
- w.WriteHeader(http.StatusOK)
649
- w.Write(data)
650
-}
651
-
652
-// serveDynamicManifest generates and serves manifest.json dynamically
653
-func (f *Frontend) ServeDynamicManifest(w http.ResponseWriter, _ *http.Request) {
654
- utils.SetCORSHeaders(w)
655
-
656
- // Find the content-addressed WASM file
657
- f.wasmCacheMu.RLock()
658
- var wasmHash string
659
- var wasmFile string
660
- for filename, entry := range f.wasmCache {
661
- wasmHash = entry.hash
662
- wasmFile = filename
663
- break // Use the first (and should be only) WASM file
664
- }
665
- f.wasmCacheMu.RUnlock()
666
-
667
- // Fallback: scan embedded WASM directory if cache is empty
668
- if wasmHash == "" {
669
- entries, err := f.distFS.ReadDir("dist/wasm")
670
- if err == nil {
671
- for _, entry := range entries {
672
- if entry.IsDir() {
673
- continue
674
- }
675
- name := entry.Name()
676
- // Look for content-addressed WASM files: <hex>.wasm.br
677
- if strings.HasSuffix(name, ".wasm.br") {
678
- hash := strings.TrimSuffix(name, ".wasm.br")
679
- if utils.IsHexString(hash) {
680
- wasmHash = hash
681
- wasmFile = hash + ".wasm"
682
- break
683
- }
684
- }
685
- }
686
- }
687
- }
688
-
689
- // Generate WASM URL
690
- wasmURL := flagPortalURL + "/frontend/" + wasmFile
691
-
692
- // Create manifest structure
693
- manifest := map[string]string{
694
- "wasmFile": wasmFile,
695
- "wasmUrl": wasmURL,
696
- "hash": wasmHash,
697
- "bootstraps": strings.Join(flagBootstraps, ","),
698
- }
699
-
700
- // Set headers for no caching
701
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
702
- w.Header().Set("Pragma", "no-cache")
703
- w.Header().Set("Expires", "0")
704
- w.Header().Set("Content-Type", "application/json")
705
-
706
- // Encode and send
707
- w.WriteHeader(http.StatusOK)
708
- if err := json.NewEncoder(w).Encode(manifest); err != nil {
709
- log.Error().Err(err).Msg("Failed to encode manifest")
710
- }
711
-
712
- log.Debug().
713
- Str("wasmFile", wasmFile).
714
- Str("wasmUrl", wasmURL).
715
- Str("hash", wasmHash).
716
- Str("bootstraps", strings.Join(flagBootstraps, ",")).
717
- Msg("Served dynamic manifest")
718
-}
719
-
720
-// ServeDynamicServiceWorker serves service-worker.js with injected manifest and config.
721
-func (f *Frontend) ServeDynamicServiceWorker(w http.ResponseWriter, r *http.Request) {
722
- utils.SetCORSHeaders(w)
723
-
724
- // Read the service-worker.js template
725
- fullPath := path.Join("dist", "wasm", "service-worker.js")
726
- content, err := f.distFS.ReadFile(fullPath)
727
- if err != nil {
728
- log.Error().Err(err).Msg("Failed to read service-worker.js")
729
- http.NotFound(w, r)
730
- return
731
- }
732
-
733
- // Set headers for no caching
734
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
735
- w.Header().Set("Pragma", "no-cache")
736
- w.Header().Set("Expires", "0")
737
- w.Header().Set("Content-Type", "application/javascript")
738
-
739
- // Send response
740
- w.WriteHeader(http.StatusOK)
741
- w.Write(content)
742
-
743
- log.Debug().Msg("Served service-worker.js")
744
-}
cmd/relay-server/frontend/package-lock.json
+717
-681
@@ -43,13 +43,13 @@
43
}
44
},
45
"node_modules/@babel/code-frame": {
46
- "version": "7.27.1",
47
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
48
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
46
+ "version": "7.29.0",
47
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
48
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
49
"dev": true,
50
"license": "MIT",
51
"dependencies": {
52
- "@babel/helper-validator-identifier": "^7.27.1",
52
+ "@babel/helper-validator-identifier": "^7.28.5",
53
"js-tokens": "^4.0.0",
54
"picocolors": "^1.1.1"
55
},
@@ -58,9 +58,9 @@
58
}
59
},
60
"node_modules/@babel/compat-data": {
61
- "version": "7.28.5",
62
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
63
- "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
61
+ "version": "7.29.0",
62
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
63
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
64
"dev": true,
65
"license": "MIT",
66
"engines": {
@@ -68,21 +68,21 @@
68
}
69
},
70
"node_modules/@babel/core": {
71
- "version": "7.28.5",
72
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
73
- "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
71
+ "version": "7.29.0",
72
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
73
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
74
"dev": true,
75
"license": "MIT",
76
"dependencies": {
77
- "@babel/code-frame": "^7.27.1",
78
- "@babel/generator": "^7.28.5",
79
- "@babel/helper-compilation-targets": "^7.27.2",
80
- "@babel/helper-module-transforms": "^7.28.3",
81
- "@babel/helpers": "^7.28.4",
82
- "@babel/parser": "^7.28.5",
83
- "@babel/template": "^7.27.2",
84
- "@babel/traverse": "^7.28.5",
85
- "@babel/types": "^7.28.5",
77
+ "@babel/code-frame": "^7.29.0",
78
+ "@babel/generator": "^7.29.0",
79
+ "@babel/helper-compilation-targets": "^7.28.6",
80
+ "@babel/helper-module-transforms": "^7.28.6",
81
+ "@babel/helpers": "^7.28.6",
82
+ "@babel/parser": "^7.29.0",
83
+ "@babel/template": "^7.28.6",
84
+ "@babel/traverse": "^7.29.0",
85
+ "@babel/types": "^7.29.0",
86
"@jridgewell/remapping": "^2.3.5",
87
"convert-source-map": "^2.0.0",
88
"debug": "^4.1.0",
@@ -109,14 +109,14 @@
109
}
110
},
111
"node_modules/@babel/generator": {
112
- "version": "7.28.5",
113
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
114
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
112
+ "version": "7.29.1",
113
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
114
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
115
"dev": true,
116
"license": "MIT",
117
"dependencies": {
118
- "@babel/parser": "^7.28.5",
119
- "@babel/types": "^7.28.5",
118
+ "@babel/parser": "^7.29.0",
119
+ "@babel/types": "^7.29.0",
120
"@jridgewell/gen-mapping": "^0.3.12",
121
"@jridgewell/trace-mapping": "^0.3.28",
122
"jsesc": "^3.0.2"
@@ -126,13 +126,13 @@
126
}
127
},
128
"node_modules/@babel/helper-compilation-targets": {
129
- "version": "7.27.2",
130
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
131
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
129
+ "version": "7.28.6",
130
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
131
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
132
"dev": true,
133
"license": "MIT",
134
"dependencies": {
135
- "@babel/compat-data": "^7.27.2",
135
+ "@babel/compat-data": "^7.28.6",
136
"@babel/helper-validator-option": "^7.27.1",
137
"browserslist": "^4.24.0",
138
"lru-cache": "^5.1.1",
@@ -163,29 +163,29 @@
163
}
164
},
165
"node_modules/@babel/helper-module-imports": {
166
- "version": "7.27.1",
167
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
168
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
166
+ "version": "7.28.6",
167
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
168
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
169
"dev": true,
170
"license": "MIT",
171
"dependencies": {
172
- "@babel/traverse": "^7.27.1",
173
- "@babel/types": "^7.27.1"
172
+ "@babel/traverse": "^7.28.6",
173
+ "@babel/types": "^7.28.6"
174
},
175
"engines": {
176
"node": ">=6.9.0"
177
}
178
},
179
"node_modules/@babel/helper-module-transforms": {
180
- "version": "7.28.3",
181
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
182
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
180
+ "version": "7.28.6",
181
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
182
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
183
"dev": true,
184
"license": "MIT",
185
"dependencies": {
186
- "@babel/helper-module-imports": "^7.27.1",
187
- "@babel/helper-validator-identifier": "^7.27.1",
188
- "@babel/traverse": "^7.28.3"
186
+ "@babel/helper-module-imports": "^7.28.6",
187
+ "@babel/helper-validator-identifier": "^7.28.5",
188
+ "@babel/traverse": "^7.28.6"
189
},
190
"engines": {
191
"node": ">=6.9.0"
@@ -195,9 +195,9 @@
195
}
196
},
197
"node_modules/@babel/helper-plugin-utils": {
198
- "version": "7.27.1",
199
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
200
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
198
+ "version": "7.28.6",
199
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
200
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
201
"dev": true,
202
"license": "MIT",
203
"engines": {
@@ -235,27 +235,27 @@
235
}
236
},
237
"node_modules/@babel/helpers": {
238
- "version": "7.28.4",
239
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
240
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
238
+ "version": "7.28.6",
239
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
240
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
241
"dev": true,
242
"license": "MIT",
243
"dependencies": {
244
- "@babel/template": "^7.27.2",
245
- "@babel/types": "^7.28.4"
244
+ "@babel/template": "^7.28.6",
245
+ "@babel/types": "^7.28.6"
246
},
247
"engines": {
248
"node": ">=6.9.0"
249
}
250
},
251
"node_modules/@babel/parser": {
252
- "version": "7.28.5",
253
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
254
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
252
+ "version": "7.29.0",
253
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
254
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
255
"dev": true,
256
"license": "MIT",
257
"dependencies": {
258
- "@babel/types": "^7.28.5"
258
+ "@babel/types": "^7.29.0"
259
},
260
"bin": {
261
"parser": "bin/babel-parser.js"
@@ -297,33 +297,33 @@
297
}
298
},
299
"node_modules/@babel/template": {
300
- "version": "7.27.2",
301
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
302
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
300
+ "version": "7.28.6",
301
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
302
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
303
"dev": true,
304
"license": "MIT",
305
"dependencies": {
306
- "@babel/code-frame": "^7.27.1",
307
- "@babel/parser": "^7.27.2",
308
- "@babel/types": "^7.27.1"
306
+ "@babel/code-frame": "^7.28.6",
307
+ "@babel/parser": "^7.28.6",
308
+ "@babel/types": "^7.28.6"
309
},
310
"engines": {
311
"node": ">=6.9.0"
312
}
313
},
314
"node_modules/@babel/traverse": {
315
- "version": "7.28.5",
316
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
317
- "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
315
+ "version": "7.29.0",
316
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
317
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
318
"dev": true,
319
"license": "MIT",
320
"dependencies": {
321
- "@babel/code-frame": "^7.27.1",
322
- "@babel/generator": "^7.28.5",
321
+ "@babel/code-frame": "^7.29.0",
322
+ "@babel/generator": "^7.29.0",
323
"@babel/helper-globals": "^7.28.0",
324
- "@babel/parser": "^7.28.5",
325
- "@babel/template": "^7.27.2",
326
- "@babel/types": "^7.28.5",
324
+ "@babel/parser": "^7.29.0",
325
+ "@babel/template": "^7.28.6",
326
+ "@babel/types": "^7.29.0",
327
"debug": "^4.3.1"
328
},
329
"engines": {
@@ -331,9 +331,9 @@
331
}
332
},
333
"node_modules/@babel/types": {
334
- "version": "7.28.5",
335
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
336
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
334
+ "version": "7.29.0",
335
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
336
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
337
"dev": true,
338
"license": "MIT",
339
"dependencies": {
@@ -345,9 +345,9 @@
345
}
346
},
347
"node_modules/@esbuild/aix-ppc64": {
348
- "version": "0.25.12",
349
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
350
- "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
348
+ "version": "0.27.3",
349
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
350
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
351
"cpu": [
352
"ppc64"
353
],
@@ -362,9 +362,9 @@
362
}
363
},
364
"node_modules/@esbuild/android-arm": {
365
- "version": "0.25.12",
366
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
367
- "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
365
+ "version": "0.27.3",
366
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
367
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
368
"cpu": [
369
"arm"
370
],
@@ -379,9 +379,9 @@
379
}
380
},
381
"node_modules/@esbuild/android-arm64": {
382
- "version": "0.25.12",
383
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
384
- "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
382
+ "version": "0.27.3",
383
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
384
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
385
"cpu": [
386
"arm64"
387
],
@@ -396,9 +396,9 @@
396
}
397
},
398
"node_modules/@esbuild/android-x64": {
399
- "version": "0.25.12",
400
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
401
- "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
399
+ "version": "0.27.3",
400
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
401
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
402
"cpu": [
403
"x64"
404
],
@@ -413,9 +413,9 @@
413
}
414
},
415
"node_modules/@esbuild/darwin-arm64": {
416
- "version": "0.25.12",
417
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
418
- "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
416
+ "version": "0.27.3",
417
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
418
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
419
"cpu": [
420
"arm64"
421
],
@@ -430,9 +430,9 @@
430
}
431
},
432
"node_modules/@esbuild/darwin-x64": {
433
- "version": "0.25.12",
434
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
435
- "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
433
+ "version": "0.27.3",
434
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
435
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
436
"cpu": [
437
"x64"
438
],
@@ -447,9 +447,9 @@
447
}
448
},
449
"node_modules/@esbuild/freebsd-arm64": {
450
- "version": "0.25.12",
451
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
452
- "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
450
+ "version": "0.27.3",
451
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
452
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
453
"cpu": [
454
"arm64"
455
],
@@ -464,9 +464,9 @@
464
}
465
},
466
"node_modules/@esbuild/freebsd-x64": {
467
- "version": "0.25.12",
468
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
469
- "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
467
+ "version": "0.27.3",
468
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
469
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
470
"cpu": [
471
"x64"
472
],
@@ -481,9 +481,9 @@
481
}
482
},
483
"node_modules/@esbuild/linux-arm": {
484
- "version": "0.25.12",
485
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
486
- "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
484
+ "version": "0.27.3",
485
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
486
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
487
"cpu": [
488
"arm"
489
],
@@ -498,9 +498,9 @@
498
}
499
},
500
"node_modules/@esbuild/linux-arm64": {
501
- "version": "0.25.12",
502
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
503
- "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
501
+ "version": "0.27.3",
502
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
503
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
504
"cpu": [
505
"arm64"
506
],
@@ -515,9 +515,9 @@
515
}
516
},
517
"node_modules/@esbuild/linux-ia32": {
518
- "version": "0.25.12",
519
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
520
- "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
518
+ "version": "0.27.3",
519
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
520
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
521
"cpu": [
522
"ia32"
523
],
@@ -532,9 +532,9 @@
532
}
533
},
534
"node_modules/@esbuild/linux-loong64": {
535
- "version": "0.25.12",
536
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
537
- "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
535
+ "version": "0.27.3",
536
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
537
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
538
"cpu": [
539
"loong64"
540
],
@@ -549,9 +549,9 @@
549
}
550
},
551
"node_modules/@esbuild/linux-mips64el": {
552
- "version": "0.25.12",
553
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
554
- "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
552
+ "version": "0.27.3",
553
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
554
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
555
"cpu": [
556
"mips64el"
557
],
@@ -566,9 +566,9 @@
566
}
567
},
568
"node_modules/@esbuild/linux-ppc64": {
569
- "version": "0.25.12",
570
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
571
- "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
569
+ "version": "0.27.3",
570
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
571
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
572
"cpu": [
573
"ppc64"
574
],
@@ -583,9 +583,9 @@
583
}
584
},
585
"node_modules/@esbuild/linux-riscv64": {
586
- "version": "0.25.12",
587
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
588
- "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
586
+ "version": "0.27.3",
587
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
588
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
589
"cpu": [
590
"riscv64"
591
],
@@ -600,9 +600,9 @@
600
}
601
},
602
"node_modules/@esbuild/linux-s390x": {
603
- "version": "0.25.12",
604
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
605
- "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
603
+ "version": "0.27.3",
604
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
605
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
606
"cpu": [
607
"s390x"
608
],
@@ -617,9 +617,9 @@
617
}
618
},
619
"node_modules/@esbuild/linux-x64": {
620
- "version": "0.25.12",
621
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
622
- "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
620
+ "version": "0.27.3",
621
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
622
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
623
"cpu": [
624
"x64"
625
],
@@ -634,9 +634,9 @@
634
}
635
},
636
"node_modules/@esbuild/netbsd-arm64": {
637
- "version": "0.25.12",
638
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
639
- "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
637
+ "version": "0.27.3",
638
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
639
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
640
"cpu": [
641
"arm64"
642
],
@@ -651,9 +651,9 @@
651
}
652
},
653
"node_modules/@esbuild/netbsd-x64": {
654
- "version": "0.25.12",
655
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
656
- "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
654
+ "version": "0.27.3",
655
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
656
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
657
"cpu": [
658
"x64"
659
],
@@ -668,9 +668,9 @@
668
}
669
},
670
"node_modules/@esbuild/openbsd-arm64": {
671
- "version": "0.25.12",
672
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
673
- "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
671
+ "version": "0.27.3",
672
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
673
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
674
"cpu": [
675
"arm64"
676
],
@@ -685,9 +685,9 @@
685
}
686
},
687
"node_modules/@esbuild/openbsd-x64": {
688
- "version": "0.25.12",
689
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
690
- "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
688
+ "version": "0.27.3",
689
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
690
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
691
"cpu": [
692
"x64"
693
],
@@ -702,9 +702,9 @@
702
}
703
},
704
"node_modules/@esbuild/openharmony-arm64": {
705
- "version": "0.25.12",
706
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
707
- "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
705
+ "version": "0.27.3",
706
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
707
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
708
"cpu": [
709
"arm64"
710
],
@@ -719,9 +719,9 @@
719
}
720
},
721
"node_modules/@esbuild/sunos-x64": {
722
- "version": "0.25.12",
723
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
724
- "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
722
+ "version": "0.27.3",
723
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
724
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
725
"cpu": [
726
"x64"
727
],
@@ -736,9 +736,9 @@
736
}
737
},
738
"node_modules/@esbuild/win32-arm64": {
739
- "version": "0.25.12",
740
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
741
- "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
739
+ "version": "0.27.3",
740
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
741
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
742
"cpu": [
743
"arm64"
744
],
@@ -753,9 +753,9 @@
753
}
754
},
755
"node_modules/@esbuild/win32-ia32": {
756
- "version": "0.25.12",
757
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
758
- "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
756
+ "version": "0.27.3",
757
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
758
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
759
"cpu": [
760
"ia32"
761
],
@@ -770,9 +770,9 @@
770
}
771
},
772
"node_modules/@esbuild/win32-x64": {
773
- "version": "0.25.12",
774
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
775
- "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
773
+ "version": "0.27.3",
774
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
775
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
776
"cpu": [
777
"x64"
778
],
@@ -787,9 +787,9 @@
787
}
788
},
789
"node_modules/@eslint-community/eslint-utils": {
790
- "version": "4.9.0",
791
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
792
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
790
+ "version": "4.9.1",
791
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
792
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
793
"dev": true,
794
"license": "MIT",
795
"dependencies": {
@@ -830,6 +830,13 @@
830
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
831
}
832
},
833
+ "node_modules/@eslint/config-array/node_modules/balanced-match": {
834
+ "version": "1.0.2",
835
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
836
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
837
+ "dev": true,
838
+ "license": "MIT"
839
+ },
840
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
841
"version": "1.1.12",
842
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -842,9 +849,9 @@
849
}
850
},
851
"node_modules/@eslint/config-array/node_modules/minimatch": {
845
- "version": "3.1.2",
846
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
847
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
852
+ "version": "3.1.3",
853
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
854
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
855
"dev": true,
856
"license": "ISC",
857
"dependencies": {
@@ -881,20 +888,20 @@
888
}
889
},
890
"node_modules/@eslint/eslintrc": {
884
- "version": "3.3.1",
885
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
886
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
891
+ "version": "3.3.4",
892
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz",
893
+ "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==",
894
"dev": true,
895
"license": "MIT",
896
"dependencies": {
890
- "ajv": "^6.12.4",
897
+ "ajv": "^6.14.0",
898
"debug": "^4.3.2",
899
"espree": "^10.0.1",
900
"globals": "^14.0.0",
901
"ignore": "^5.2.0",
902
"import-fresh": "^3.2.1",
896
- "js-yaml": "^4.1.0",
897
- "minimatch": "^3.1.2",
903
+ "js-yaml": "^4.1.1",
904
+ "minimatch": "^3.1.3",
905
"strip-json-comments": "^3.1.1"
906
},
907
"engines": {
@@ -904,6 +911,13 @@
911
"url": "https://opencollective.com/eslint"
912
}
913
},
914
+ "node_modules/@eslint/eslintrc/node_modules/balanced-match": {
915
+ "version": "1.0.2",
916
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
917
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
918
+ "dev": true,
919
+ "license": "MIT"
920
+ },
921
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
922
"version": "1.1.12",
923
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -926,9 +940,9 @@
940
}
941
},
942
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
929
- "version": "3.1.2",
930
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
931
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
943
+ "version": "3.1.3",
944
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
945
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
946
"dev": true,
947
"license": "ISC",
948
"dependencies": {
@@ -939,9 +953,9 @@
953
}
954
},
955
"node_modules/@eslint/js": {
942
- "version": "9.39.1",
943
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
944
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
956
+ "version": "9.39.3",
957
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
958
+ "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
959
"dev": true,
960
"license": "MIT",
961
"engines": {
@@ -976,31 +990,31 @@
990
}
991
},
992
"node_modules/@floating-ui/core": {
979
- "version": "1.7.3",
980
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
981
- "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
993
+ "version": "1.7.4",
994
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
995
+ "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
996
"license": "MIT",
997
"dependencies": {
998
"@floating-ui/utils": "^0.2.10"
999
}
1000
},
1001
"node_modules/@floating-ui/dom": {
988
- "version": "1.7.4",
989
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
990
- "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
1002
+ "version": "1.7.5",
1003
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
1004
+ "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
1005
"license": "MIT",
1006
"dependencies": {
993
- "@floating-ui/core": "^1.7.3",
1007
+ "@floating-ui/core": "^1.7.4",
1008
"@floating-ui/utils": "^0.2.10"
1009
}
1010
},
1011
"node_modules/@floating-ui/react-dom": {
998
- "version": "2.1.6",
999
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
1000
- "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
1012
+ "version": "2.1.7",
1013
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz",
1014
+ "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==",
1015
"license": "MIT",
1016
"dependencies": {
1003
- "@floating-ui/dom": "^1.7.4"
1017
+ "@floating-ui/dom": "^1.7.5"
1018
},
1019
"peerDependencies": {
1020
"react": ">=16.8.0",
@@ -1891,16 +1905,16 @@
1905
"license": "MIT"
1906
},
1907
"node_modules/@rolldown/pluginutils": {
1894
- "version": "1.0.0-beta.47",
1895
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz",
1896
- "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==",
1908
+ "version": "1.0.0-rc.3",
1909
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
1910
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
1911
"dev": true,
1912
"license": "MIT"
1913
},
1914
"node_modules/@rollup/rollup-android-arm-eabi": {
1901
- "version": "4.53.2",
1902
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
1903
- "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
1915
+ "version": "4.59.0",
1916
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
1917
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
1918
"cpu": [
1919
"arm"
1920
],
@@ -1912,9 +1926,9 @@
1926
]
1927
},
1928
"node_modules/@rollup/rollup-android-arm64": {
1915
- "version": "4.53.2",
1916
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
1917
- "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
1929
+ "version": "4.59.0",
1930
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
1931
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
1932
"cpu": [
1933
"arm64"
1934
],
@@ -1926,9 +1940,9 @@
1940
]
1941
},
1942
"node_modules/@rollup/rollup-darwin-arm64": {
1929
- "version": "4.53.2",
1930
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
1931
- "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
1943
+ "version": "4.59.0",
1944
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
1945
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
1946
"cpu": [
1947
"arm64"
1948
],
@@ -1940,9 +1954,9 @@
1954
]
1955
},
1956
"node_modules/@rollup/rollup-darwin-x64": {
1943
- "version": "4.53.2",
1944
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
1945
- "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
1957
+ "version": "4.59.0",
1958
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
1959
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
1960
"cpu": [
1961
"x64"
1962
],
@@ -1954,9 +1968,9 @@
1968
]
1969
},
1970
"node_modules/@rollup/rollup-freebsd-arm64": {
1957
- "version": "4.53.2",
1958
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
1959
- "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
1971
+ "version": "4.59.0",
1972
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
1973
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
1974
"cpu": [
1975
"arm64"
1976
],
@@ -1968,9 +1982,9 @@
1982
]
1983
},
1984
"node_modules/@rollup/rollup-freebsd-x64": {
1971
- "version": "4.53.2",
1972
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
1973
- "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
1985
+ "version": "4.59.0",
1986
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
1987
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
1988
"cpu": [
1989
"x64"
1990
],
@@ -1982,9 +1996,9 @@
1996
]
1997
},
1998
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1985
- "version": "4.53.2",
1986
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
1987
- "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
1999
+ "version": "4.59.0",
2000
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
2001
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
2002
"cpu": [
2003
"arm"
2004
],
@@ -1996,9 +2010,9 @@
2010
]
2011
},
2012
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
1999
- "version": "4.53.2",
2000
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
2001
- "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
2013
+ "version": "4.59.0",
2014
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
2015
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
2016
"cpu": [
2017
"arm"
2018
],
@@ -2010,9 +2024,9 @@
2024
]
2025
},
2026
"node_modules/@rollup/rollup-linux-arm64-gnu": {
2013
- "version": "4.53.2",
2014
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
2015
- "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
2027
+ "version": "4.59.0",
2028
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
2029
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
2030
"cpu": [
2031
"arm64"
2032
],
@@ -2024,9 +2038,9 @@
2038
]
2039
},
2040
"node_modules/@rollup/rollup-linux-arm64-musl": {
2027
- "version": "4.53.2",
2028
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
2029
- "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
2041
+ "version": "4.59.0",
2042
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
2043
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
2044
"cpu": [
2045
"arm64"
2046
],
@@ -2038,9 +2052,23 @@
2052
]
2053
},
2054
"node_modules/@rollup/rollup-linux-loong64-gnu": {
2041
- "version": "4.53.2",
2042
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
2043
- "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
2055
+ "version": "4.59.0",
2056
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
2057
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
2058
+ "cpu": [
2059
+ "loong64"
2060
+ ],
2061
+ "dev": true,
2062
+ "license": "MIT",
2063
+ "optional": true,
2064
+ "os": [
2065
+ "linux"
2066
+ ]
2067
+ },
2068
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
2069
+ "version": "4.59.0",
2070
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
2071
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
2072
"cpu": [
2073
"loong64"
2074
],
@@ -2052,9 +2080,23 @@
2080
]
2081
},
2082
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
2055
- "version": "4.53.2",
2056
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
2057
- "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
2083
+ "version": "4.59.0",
2084
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
2085
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
2086
+ "cpu": [
2087
+ "ppc64"
2088
+ ],
2089
+ "dev": true,
2090
+ "license": "MIT",
2091
+ "optional": true,
2092
+ "os": [
2093
+ "linux"
2094
+ ]
2095
+ },
2096
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
2097
+ "version": "4.59.0",
2098
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
2099
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
2100
"cpu": [
2101
"ppc64"
2102
],
@@ -2066,9 +2108,9 @@
2108
]
2109
},
2110
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
2069
- "version": "4.53.2",
2070
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
2071
- "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
2111
+ "version": "4.59.0",
2112
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
2113
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
2114
"cpu": [
2115
"riscv64"
2116
],
@@ -2080,9 +2122,9 @@
2122
]
2123
},
2124
"node_modules/@rollup/rollup-linux-riscv64-musl": {
2083
- "version": "4.53.2",
2084
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
2085
- "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
2125
+ "version": "4.59.0",
2126
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
2127
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
2128
"cpu": [
2129
"riscv64"
2130
],
@@ -2094,9 +2136,9 @@
2136
]
2137
},
2138
"node_modules/@rollup/rollup-linux-s390x-gnu": {
2097
- "version": "4.53.2",
2098
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
2099
- "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
2139
+ "version": "4.59.0",
2140
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
2141
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
2142
"cpu": [
2143
"s390x"
2144
],
@@ -2108,9 +2150,9 @@
2150
]
2151
},
2152
"node_modules/@rollup/rollup-linux-x64-gnu": {
2111
- "version": "4.53.2",
2112
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
2113
- "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
2153
+ "version": "4.59.0",
2154
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
2155
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
2156
"cpu": [
2157
"x64"
2158
],
@@ -2122,9 +2164,9 @@
2164
]
2165
},
2166
"node_modules/@rollup/rollup-linux-x64-musl": {
2125
- "version": "4.53.2",
2126
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
2127
- "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
2167
+ "version": "4.59.0",
2168
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
2169
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
2170
"cpu": [
2171
"x64"
2172
],
@@ -2135,10 +2177,24 @@
2177
"linux"
2178
]
2179
},
2180
+ "node_modules/@rollup/rollup-openbsd-x64": {
2181
+ "version": "4.59.0",
2182
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
2183
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
2184
+ "cpu": [
2185
+ "x64"
2186
+ ],
2187
+ "dev": true,
2188
+ "license": "MIT",
2189
+ "optional": true,
2190
+ "os": [
2191
+ "openbsd"
2192
+ ]
2193
+ },
2194
"node_modules/@rollup/rollup-openharmony-arm64": {
2139
- "version": "4.53.2",
2140
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
2141
- "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
2195
+ "version": "4.59.0",
2196
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
2197
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
2198
"cpu": [
2199
"arm64"
2200
],
@@ -2150,9 +2206,9 @@
2206
]
2207
},
2208
"node_modules/@rollup/rollup-win32-arm64-msvc": {
2153
- "version": "4.53.2",
2154
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
2155
- "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
2209
+ "version": "4.59.0",
2210
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
2211
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
2212
"cpu": [
2213
"arm64"
2214
],
@@ -2164,9 +2220,9 @@
2220
]
2221
},
2222
"node_modules/@rollup/rollup-win32-ia32-msvc": {
2167
- "version": "4.53.2",
2168
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
2169
- "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
2223
+ "version": "4.59.0",
2224
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
2225
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
2226
"cpu": [
2227
"ia32"
2228
],
@@ -2178,9 +2234,9 @@
2234
]
2235
},
2236
"node_modules/@rollup/rollup-win32-x64-gnu": {
2181
- "version": "4.53.2",
2182
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
2183
- "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
2237
+ "version": "4.59.0",
2238
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
2239
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
2240
"cpu": [
2241
"x64"
2242
],
@@ -2192,9 +2248,9 @@
2248
]
2249
},
2250
"node_modules/@rollup/rollup-win32-x64-msvc": {
2195
- "version": "4.53.2",
2196
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
2197
- "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
2251
+ "version": "4.59.0",
2252
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
2253
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
2254
"cpu": [
2255
"x64"
2256
],
@@ -2206,21 +2262,21 @@
2262
]
2263
},
2264
"node_modules/@ssgoi/core": {
2209
- "version": "2.5.5",
2210
- "resolved": "https://registry.npmjs.org/@ssgoi/core/-/core-2.5.5.tgz",
2211
- "integrity": "sha512-BBPD4q7PweQk0j0sxOuipFSMZ5Ty7JQ4RalqrP64abQU7wJkps2YA9eO+2+AgH5O/E9CXF0H/IB7OjmT1zEeoQ==",
2265
+ "version": "2.5.6",
2266
+ "resolved": "https://registry.npmjs.org/@ssgoi/core/-/core-2.5.6.tgz",
2267
+ "integrity": "sha512-vd7ouk93Ykl5EEn7rjvOQQO9hNlGj2fjFxP/UU0C7ZQBEOCz5pEmYn0tt/CyUTB+RjQOAafrGeSdcedUQ0xk1A==",
2268
"license": "MIT",
2269
"engines": {
2270
"node": ">=18.0.0"
2271
}
2272
},
2273
"node_modules/@ssgoi/react": {
2218
- "version": "2.5.5",
2219
- "resolved": "https://registry.npmjs.org/@ssgoi/react/-/react-2.5.5.tgz",
2220
- "integrity": "sha512-GRw5/VEheQguvGJ46r633GwlbQqaiV6cLfsBb9cPAhT6EvoxAHx13i6h2BuShyiY4kSf62hI/HF3lXqRAHifDQ==",
2274
+ "version": "2.5.6",
2275
+ "resolved": "https://registry.npmjs.org/@ssgoi/react/-/react-2.5.6.tgz",
2276
+ "integrity": "sha512-lGVajsIcIUX6h/opjYLu7Ia+GArmE1gL527tufbteEx9h7FUcl2CfR5pZnvL0TOC1Ls6ttbfozDPuhQRx4OyzQ==",
2277
"license": "MIT",
2278
"dependencies": {
2223
- "@ssgoi/core": "^2.5.5"
2279
+ "@ssgoi/core": "^2.5.6"
2280
},
2281
"engines": {
2282
"node": ">=18.0.0"
@@ -2231,49 +2287,49 @@
2287
}
2288
},
2289
"node_modules/@tailwindcss/node": {
2234
- "version": "4.1.17",
2235
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
2236
- "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
2290
+ "version": "4.2.1",
2291
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
2292
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
2293
"dev": true,
2294
"license": "MIT",
2295
"dependencies": {
2240
- "@jridgewell/remapping": "^2.3.4",
2241
- "enhanced-resolve": "^5.18.3",
2296
+ "@jridgewell/remapping": "^2.3.5",
2297
+ "enhanced-resolve": "^5.19.0",
2298
"jiti": "^2.6.1",
2243
- "lightningcss": "1.30.2",
2299
+ "lightningcss": "1.31.1",
2300
"magic-string": "^0.30.21",
2301
"source-map-js": "^1.2.1",
2246
- "tailwindcss": "4.1.17"
2302
+ "tailwindcss": "4.2.1"
2303
}
2304
},
2305
"node_modules/@tailwindcss/oxide": {
2250
- "version": "4.1.17",
2251
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
2252
- "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
2306
+ "version": "4.2.1",
2307
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
2308
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
2309
"dev": true,
2310
"license": "MIT",
2311
"engines": {
2256
- "node": ">= 10"
2312
+ "node": ">= 20"
2313
},
2314
"optionalDependencies": {
2259
- "@tailwindcss/oxide-android-arm64": "4.1.17",
2260
- "@tailwindcss/oxide-darwin-arm64": "4.1.17",
2261
- "@tailwindcss/oxide-darwin-x64": "4.1.17",
2262
- "@tailwindcss/oxide-freebsd-x64": "4.1.17",
2263
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
2264
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
2265
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
2266
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
2267
- "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
2268
- "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
2269
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
2270
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
2315
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
2316
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
2317
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
2318
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
2319
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
2320
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
2321
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
2322
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
2323
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
2324
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
2325
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
2326
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
2327
}
2328
},
2329
"node_modules/@tailwindcss/oxide-android-arm64": {
2274
- "version": "4.1.17",
2275
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
2276
- "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
2330
+ "version": "4.2.1",
2331
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
2332
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
2333
"cpu": [
2334
"arm64"
2335
],
@@ -2284,13 +2340,13 @@
2340
"android"
2341
],
2342
"engines": {
2287
- "node": ">= 10"
2343
+ "node": ">= 20"
2344
}
2345
},
2346
"node_modules/@tailwindcss/oxide-darwin-arm64": {
2291
- "version": "4.1.17",
2292
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
2293
- "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
2347
+ "version": "4.2.1",
2348
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
2349
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
2350
"cpu": [
2351
"arm64"
2352
],
@@ -2301,13 +2357,13 @@
2357
"darwin"
2358
],
2359
"engines": {
2304
- "node": ">= 10"
2360
+ "node": ">= 20"
2361
}
2362
},
2363
"node_modules/@tailwindcss/oxide-darwin-x64": {
2308
- "version": "4.1.17",
2309
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
2310
- "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
2364
+ "version": "4.2.1",
2365
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
2366
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
2367
"cpu": [
2368
"x64"
2369
],
@@ -2318,13 +2374,13 @@
2374
"darwin"
2375
],
2376
"engines": {
2321
- "node": ">= 10"
2377
+ "node": ">= 20"
2378
}
2379
},
2380
"node_modules/@tailwindcss/oxide-freebsd-x64": {
2325
- "version": "4.1.17",
2326
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
2327
- "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
2381
+ "version": "4.2.1",
2382
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
2383
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
2384
"cpu": [
2385
"x64"
2386
],
@@ -2335,13 +2391,13 @@
2391
"freebsd"
2392
],
2393
"engines": {
2338
- "node": ">= 10"
2394
+ "node": ">= 20"
2395
}
2396
},
2397
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
2342
- "version": "4.1.17",
2343
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
2344
- "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
2398
+ "version": "4.2.1",
2399
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
2400
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
2401
"cpu": [
2402
"arm"
2403
],
@@ -2352,13 +2408,13 @@
2408
"linux"
2409
],
2410
"engines": {
2355
- "node": ">= 10"
2411
+ "node": ">= 20"
2412
}
2413
},
2414
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
2359
- "version": "4.1.17",
2360
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
2361
- "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
2415
+ "version": "4.2.1",
2416
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
2417
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
2418
"cpu": [
2419
"arm64"
2420
],
@@ -2369,13 +2425,13 @@
2425
"linux"
2426
],
2427
"engines": {
2372
- "node": ">= 10"
2428
+ "node": ">= 20"
2429
}
2430
},
2431
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
2376
- "version": "4.1.17",
2377
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
2378
- "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
2432
+ "version": "4.2.1",
2433
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
2434
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
2435
"cpu": [
2436
"arm64"
2437
],
@@ -2386,13 +2442,13 @@
2442
"linux"
2443
],
2444
"engines": {
2389
- "node": ">= 10"
2445
+ "node": ">= 20"
2446
}
2447
},
2448
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
2393
- "version": "4.1.17",
2394
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
2395
- "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
2449
+ "version": "4.2.1",
2450
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
2451
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
2452
"cpu": [
2453
"x64"
2454
],
@@ -2403,13 +2459,13 @@
2459
"linux"
2460
],
2461
"engines": {
2406
- "node": ">= 10"
2462
+ "node": ">= 20"
2463
}
2464
},
2465
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
2410
- "version": "4.1.17",
2411
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
2412
- "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
2466
+ "version": "4.2.1",
2467
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
2468
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
2469
"cpu": [
2470
"x64"
2471
],
@@ -2420,13 +2476,13 @@
2476
"linux"
2477
],
2478
"engines": {
2423
- "node": ">= 10"
2479
+ "node": ">= 20"
2480
}
2481
},
2482
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
2427
- "version": "4.1.17",
2428
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
2429
- "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
2483
+ "version": "4.2.1",
2484
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
2485
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
2486
"bundleDependencies": [
2487
"@napi-rs/wasm-runtime",
2488
"@emnapi/core",
@@ -2442,21 +2498,21 @@
2498
"license": "MIT",
2499
"optional": true,
2500
"dependencies": {
2445
- "@emnapi/core": "^1.6.0",
2446
- "@emnapi/runtime": "^1.6.0",
2501
+ "@emnapi/core": "^1.8.1",
2502
+ "@emnapi/runtime": "^1.8.1",
2503
"@emnapi/wasi-threads": "^1.1.0",
2448
- "@napi-rs/wasm-runtime": "^1.0.7",
2504
+ "@napi-rs/wasm-runtime": "^1.1.1",
2505
"@tybys/wasm-util": "^0.10.1",
2450
- "tslib": "^2.4.0"
2506
+ "tslib": "^2.8.1"
2507
},
2508
"engines": {
2509
"node": ">=14.0.0"
2510
}
2511
},
2512
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
2457
- "version": "4.1.17",
2458
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
2459
- "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
2513
+ "version": "4.2.1",
2514
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
2515
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
2516
"cpu": [
2517
"arm64"
2518
],
@@ -2467,13 +2523,13 @@
2523
"win32"
2524
],
2525
"engines": {
2470
- "node": ">= 10"
2526
+ "node": ">= 20"
2527
}
2528
},
2529
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
2474
- "version": "4.1.17",
2475
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
2476
- "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
2530
+ "version": "4.2.1",
2531
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
2532
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
2533
"cpu": [
2534
"x64"
2535
],
@@ -2484,19 +2540,19 @@
2540
"win32"
2541
],
2542
"engines": {
2487
- "node": ">= 10"
2543
+ "node": ">= 20"
2544
}
2545
},
2546
"node_modules/@tailwindcss/vite": {
2491
- "version": "4.1.17",
2492
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz",
2493
- "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==",
2547
+ "version": "4.2.1",
2548
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
2549
+ "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
2550
"dev": true,
2551
"license": "MIT",
2552
"dependencies": {
2497
- "@tailwindcss/node": "4.1.17",
2498
- "@tailwindcss/oxide": "4.1.17",
2499
- "tailwindcss": "4.1.17"
2553
+ "@tailwindcss/node": "4.2.1",
2554
+ "@tailwindcss/oxide": "4.2.1",
2555
+ "tailwindcss": "4.2.1"
2556
},
2557
"peerDependencies": {
2558
"vite": "^5.2.0 || ^6 || ^7"
@@ -2562,9 +2618,9 @@
2618
"license": "MIT"
2619
},
2620
"node_modules/@types/node": {
2565
- "version": "24.10.1",
2566
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
2567
- "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
2621
+ "version": "24.10.13",
2622
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
2623
+ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
2624
"dev": true,
2625
"license": "MIT",
2626
"dependencies": {
@@ -2572,9 +2628,9 @@
2628
}
2629
},
2630
"node_modules/@types/react": {
2575
- "version": "19.2.7",
2576
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
2577
- "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
2631
+ "version": "19.2.14",
2632
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
2633
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
2634
"devOptional": true,
2635
"license": "MIT",
2636
"dependencies": {
@@ -2592,21 +2648,20 @@
2648
}
2649
},
2650
"node_modules/@typescript-eslint/eslint-plugin": {
2595
- "version": "8.48.1",
2596
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
2597
- "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==",
2651
+ "version": "8.56.1",
2652
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
2653
+ "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
2654
"dev": true,
2655
"license": "MIT",
2656
"dependencies": {
2601
- "@eslint-community/regexpp": "^4.10.0",
2602
- "@typescript-eslint/scope-manager": "8.48.1",
2603
- "@typescript-eslint/type-utils": "8.48.1",
2604
- "@typescript-eslint/utils": "8.48.1",
2605
- "@typescript-eslint/visitor-keys": "8.48.1",
2606
- "graphemer": "^1.4.0",
2607
- "ignore": "^7.0.0",
2657
+ "@eslint-community/regexpp": "^4.12.2",
2658
+ "@typescript-eslint/scope-manager": "8.56.1",
2659
+ "@typescript-eslint/type-utils": "8.56.1",
2660
+ "@typescript-eslint/utils": "8.56.1",
2661
+ "@typescript-eslint/visitor-keys": "8.56.1",
2662
+ "ignore": "^7.0.5",
2663
"natural-compare": "^1.4.0",
2609
- "ts-api-utils": "^2.1.0"
2664
+ "ts-api-utils": "^2.4.0"
2665
},
2666
"engines": {
2667
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2616,23 +2671,23 @@
2671
"url": "https://opencollective.com/typescript-eslint"
2672
},
2673
"peerDependencies": {
2619
- "@typescript-eslint/parser": "^8.48.1",
2620
- "eslint": "^8.57.0 || ^9.0.0",
2674
+ "@typescript-eslint/parser": "^8.56.1",
2675
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
2676
"typescript": ">=4.8.4 <6.0.0"
2677
}
2678
},
2679
"node_modules/@typescript-eslint/parser": {
2625
- "version": "8.48.1",
2626
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz",
2627
- "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==",
2680
+ "version": "8.56.1",
2681
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
2682
+ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
2683
"dev": true,
2684
"license": "MIT",
2685
"dependencies": {
2631
- "@typescript-eslint/scope-manager": "8.48.1",
2632
- "@typescript-eslint/types": "8.48.1",
2633
- "@typescript-eslint/typescript-estree": "8.48.1",
2634
- "@typescript-eslint/visitor-keys": "8.48.1",
2635
- "debug": "^4.3.4"
2686
+ "@typescript-eslint/scope-manager": "8.56.1",
2687
+ "@typescript-eslint/types": "8.56.1",
2688
+ "@typescript-eslint/typescript-estree": "8.56.1",
2689
+ "@typescript-eslint/visitor-keys": "8.56.1",
2690
+ "debug": "^4.4.3"
2691
},
2692
"engines": {
2693
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2642,20 +2697,20 @@
2697
"url": "https://opencollective.com/typescript-eslint"
2698
},
2699
"peerDependencies": {
2645
- "eslint": "^8.57.0 || ^9.0.0",
2700
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
2701
"typescript": ">=4.8.4 <6.0.0"
2702
}
2703
},
2704
"node_modules/@typescript-eslint/project-service": {
2650
- "version": "8.48.1",
2651
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz",
2652
- "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==",
2705
+ "version": "8.56.1",
2706
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
2707
+ "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
2708
"dev": true,
2709
"license": "MIT",
2710
"dependencies": {
2656
- "@typescript-eslint/tsconfig-utils": "^8.48.1",
2657
- "@typescript-eslint/types": "^8.48.1",
2658
- "debug": "^4.3.4"
2711
+ "@typescript-eslint/tsconfig-utils": "^8.56.1",
2712
+ "@typescript-eslint/types": "^8.56.1",
2713
+ "debug": "^4.4.3"
2714
},
2715
"engines": {
2716
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2669,14 +2724,14 @@
2724
}
2725
},
2726
"node_modules/@typescript-eslint/scope-manager": {
2672
- "version": "8.48.1",
2673
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz",
2674
- "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==",
2727
+ "version": "8.56.1",
2728
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
2729
+ "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
2730
"dev": true,
2731
"license": "MIT",
2732
"dependencies": {
2678
- "@typescript-eslint/types": "8.48.1",
2679
- "@typescript-eslint/visitor-keys": "8.48.1"
2733
+ "@typescript-eslint/types": "8.56.1",
2734
+ "@typescript-eslint/visitor-keys": "8.56.1"
2735
},
2736
"engines": {
2737
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2687,9 +2742,9 @@
2742
}
2743
},
2744
"node_modules/@typescript-eslint/tsconfig-utils": {
2690
- "version": "8.48.1",
2691
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz",
2692
- "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==",
2745
+ "version": "8.56.1",
2746
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
2747
+ "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
2748
"dev": true,
2749
"license": "MIT",
2750
"engines": {
@@ -2704,17 +2759,17 @@
2759
}
2760
},
2761
"node_modules/@typescript-eslint/type-utils": {
2707
- "version": "8.48.1",
2708
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz",
2709
- "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==",
2762
+ "version": "8.56.1",
2763
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
2764
+ "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
2765
"dev": true,
2766
"license": "MIT",
2767
"dependencies": {
2713
- "@typescript-eslint/types": "8.48.1",
2714
- "@typescript-eslint/typescript-estree": "8.48.1",
2715
- "@typescript-eslint/utils": "8.48.1",
2716
- "debug": "^4.3.4",
2717
- "ts-api-utils": "^2.1.0"
2768
+ "@typescript-eslint/types": "8.56.1",
2769
+ "@typescript-eslint/typescript-estree": "8.56.1",
2770
+ "@typescript-eslint/utils": "8.56.1",
2771
+ "debug": "^4.4.3",
2772
+ "ts-api-utils": "^2.4.0"
2773
},
2774
"engines": {
2775
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2724,14 +2779,14 @@
2779
"url": "https://opencollective.com/typescript-eslint"
2780
},
2781
"peerDependencies": {
2727
- "eslint": "^8.57.0 || ^9.0.0",
2782
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
2783
"typescript": ">=4.8.4 <6.0.0"
2784
}
2785
},
2786
"node_modules/@typescript-eslint/types": {
2732
- "version": "8.48.1",
2733
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz",
2734
- "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==",
2787
+ "version": "8.56.1",
2788
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
2789
+ "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
2790
"dev": true,
2791
"license": "MIT",
2792
"engines": {
@@ -2743,21 +2798,21 @@
2798
}
2799
},
2800
"node_modules/@typescript-eslint/typescript-estree": {
2746
- "version": "8.48.1",
2747
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz",
2748
- "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==",
2801
+ "version": "8.56.1",
2802
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
2803
+ "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
2804
"dev": true,
2805
"license": "MIT",
2806
"dependencies": {
2752
- "@typescript-eslint/project-service": "8.48.1",
2753
- "@typescript-eslint/tsconfig-utils": "8.48.1",
2754
- "@typescript-eslint/types": "8.48.1",
2755
- "@typescript-eslint/visitor-keys": "8.48.1",
2756
- "debug": "^4.3.4",
2757
- "minimatch": "^9.0.4",
2758
- "semver": "^7.6.0",
2807
+ "@typescript-eslint/project-service": "8.56.1",
2808
+ "@typescript-eslint/tsconfig-utils": "8.56.1",
2809
+ "@typescript-eslint/types": "8.56.1",
2810
+ "@typescript-eslint/visitor-keys": "8.56.1",
2811
+ "debug": "^4.4.3",
2812
+ "minimatch": "^10.2.2",
2813
+ "semver": "^7.7.3",
2814
"tinyglobby": "^0.2.15",
2760
- "ts-api-utils": "^2.1.0"
2815
+ "ts-api-utils": "^2.4.0"
2816
},
2817
"engines": {
2818
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2771,16 +2826,16 @@
2826
}
2827
},
2828
"node_modules/@typescript-eslint/utils": {
2774
- "version": "8.48.1",
2775
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz",
2776
- "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==",
2829
+ "version": "8.56.1",
2830
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
2831
+ "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
2832
"dev": true,
2833
"license": "MIT",
2834
"dependencies": {
2780
- "@eslint-community/eslint-utils": "^4.7.0",
2781
- "@typescript-eslint/scope-manager": "8.48.1",
2782
- "@typescript-eslint/types": "8.48.1",
2783
- "@typescript-eslint/typescript-estree": "8.48.1"
2835
+ "@eslint-community/eslint-utils": "^4.9.1",
2836
+ "@typescript-eslint/scope-manager": "8.56.1",
2837
+ "@typescript-eslint/types": "8.56.1",
2838
+ "@typescript-eslint/typescript-estree": "8.56.1"
2839
},
2840
"engines": {
2841
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2790,19 +2845,19 @@
2845
"url": "https://opencollective.com/typescript-eslint"
2846
},
2847
"peerDependencies": {
2793
- "eslint": "^8.57.0 || ^9.0.0",
2848
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
2849
"typescript": ">=4.8.4 <6.0.0"
2850
}
2851
},
2852
"node_modules/@typescript-eslint/visitor-keys": {
2798
- "version": "8.48.1",
2799
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz",
2800
- "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==",
2853
+ "version": "8.56.1",
2854
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
2855
+ "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
2856
"dev": true,
2857
"license": "MIT",
2858
"dependencies": {
2804
- "@typescript-eslint/types": "8.48.1",
2805
- "eslint-visitor-keys": "^4.2.1"
2859
+ "@typescript-eslint/types": "8.56.1",
2860
+ "eslint-visitor-keys": "^5.0.0"
2861
},
2862
"engines": {
2863
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2813,29 +2868,29 @@
2868
}
2869
},
2870
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
2816
- "version": "4.2.1",
2817
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
2818
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
2871
+ "version": "5.0.1",
2872
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
2873
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
2874
"dev": true,
2875
"license": "Apache-2.0",
2876
"engines": {
2822
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2877
+ "node": "^20.19.0 || ^22.13.0 || >=24"
2878
},
2879
"funding": {
2880
"url": "https://opencollective.com/eslint"
2881
}
2882
},
2883
"node_modules/@vitejs/plugin-react": {
2829
- "version": "5.1.1",
2830
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz",
2831
- "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==",
2884
+ "version": "5.1.4",
2885
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
2886
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
2887
"dev": true,
2888
"license": "MIT",
2889
"dependencies": {
2835
- "@babel/core": "^7.28.5",
2890
+ "@babel/core": "^7.29.0",
2891
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
2892
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
2838
- "@rolldown/pluginutils": "1.0.0-beta.47",
2893
+ "@rolldown/pluginutils": "1.0.0-rc.3",
2894
"@types/babel__core": "^7.20.5",
2895
"react-refresh": "^0.18.0"
2896
},
@@ -2847,9 +2902,9 @@
2902
}
2903
},
2904
"node_modules/acorn": {
2850
- "version": "8.15.0",
2851
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
2852
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
2905
+ "version": "8.16.0",
2906
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
2907
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
2908
"dev": true,
2909
"license": "MIT",
2910
"bin": {
@@ -2870,9 +2925,9 @@
2925
}
2926
},
2927
"node_modules/ajv": {
2873
- "version": "6.12.6",
2874
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
2875
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
2928
+ "version": "6.14.0",
2929
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
2930
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
2931
"dev": true,
2932
"license": "MIT",
2933
"dependencies": {
@@ -2932,36 +2987,45 @@
2987
}
2988
},
2989
"node_modules/balanced-match": {
2935
- "version": "1.0.2",
2936
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
2937
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
2990
+ "version": "4.0.4",
2991
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
2992
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
2993
"dev": true,
2939
- "license": "MIT"
2994
+ "license": "MIT",
2995
+ "engines": {
2996
+ "node": "18 || 20 || >=22"
2997
+ }
2998
},
2999
"node_modules/baseline-browser-mapping": {
2942
- "version": "2.8.28",
2943
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz",
2944
- "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
3000
+ "version": "2.10.0",
3001
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
3002
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
3003
"dev": true,
3004
"license": "Apache-2.0",
3005
"bin": {
2948
- "baseline-browser-mapping": "dist/cli.js"
3006
+ "baseline-browser-mapping": "dist/cli.cjs"
3007
+ },
3008
+ "engines": {
3009
+ "node": ">=6.0.0"
3010
}
3011
},
3012
"node_modules/brace-expansion": {
2952
- "version": "2.0.2",
2953
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
2954
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
3013
+ "version": "5.0.3",
3014
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
3015
+ "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
3016
"dev": true,
3017
"license": "MIT",
3018
"dependencies": {
2958
- "balanced-match": "^1.0.0"
3019
+ "balanced-match": "^4.0.2"
3020
+ },
3021
+ "engines": {
3022
+ "node": "18 || 20 || >=22"
3023
}
3024
},
3025
"node_modules/browserslist": {
2962
- "version": "4.28.0",
2963
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
2964
- "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
3026
+ "version": "4.28.1",
3027
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
3028
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
3029
"dev": true,
3030
"funding": [
3031
{
@@ -2979,11 +3043,11 @@
3043
],
3044
"license": "MIT",
3045
"dependencies": {
2982
- "baseline-browser-mapping": "^2.8.25",
2983
- "caniuse-lite": "^1.0.30001754",
2984
- "electron-to-chromium": "^1.5.249",
3046
+ "baseline-browser-mapping": "^2.9.0",
3047
+ "caniuse-lite": "^1.0.30001759",
3048
+ "electron-to-chromium": "^1.5.263",
3049
"node-releases": "^2.0.27",
2986
- "update-browserslist-db": "^1.1.4"
3050
+ "update-browserslist-db": "^1.2.0"
3051
},
3052
"bin": {
3053
"browserslist": "cli.js"
@@ -3003,9 +3067,9 @@
3067
}
3068
},
3069
"node_modules/caniuse-lite": {
3006
- "version": "1.0.30001754",
3007
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
3008
- "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
3070
+ "version": "1.0.30001774",
3071
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz",
3072
+ "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==",
3073
"dev": true,
3074
"funding": [
3075
{
@@ -3188,30 +3252,30 @@
3252
"license": "MIT"
3253
},
3254
"node_modules/electron-to-chromium": {
3191
- "version": "1.5.254",
3192
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.254.tgz",
3193
- "integrity": "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==",
3255
+ "version": "1.5.302",
3256
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz",
3257
+ "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==",
3258
"dev": true,
3259
"license": "ISC"
3260
},
3261
"node_modules/enhanced-resolve": {
3198
- "version": "5.18.3",
3199
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
3200
- "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
3262
+ "version": "5.19.0",
3263
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
3264
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
3265
"dev": true,
3266
"license": "MIT",
3267
"dependencies": {
3268
"graceful-fs": "^4.2.4",
3205
- "tapable": "^2.2.0"
3269
+ "tapable": "^2.3.0"
3270
},
3271
"engines": {
3272
"node": ">=10.13.0"
3273
}
3274
},
3275
"node_modules/esbuild": {
3212
- "version": "0.25.12",
3213
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
3214
- "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
3276
+ "version": "0.27.3",
3277
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
3278
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
3279
"dev": true,
3280
"hasInstallScript": true,
3281
"license": "MIT",
@@ -3222,32 +3286,32 @@
3286
"node": ">=18"
3287
},
3288
"optionalDependencies": {
3225
- "@esbuild/aix-ppc64": "0.25.12",
3226
- "@esbuild/android-arm": "0.25.12",
3227
- "@esbuild/android-arm64": "0.25.12",
3228
- "@esbuild/android-x64": "0.25.12",
3229
- "@esbuild/darwin-arm64": "0.25.12",
3230
- "@esbuild/darwin-x64": "0.25.12",
3231
- "@esbuild/freebsd-arm64": "0.25.12",
3232
- "@esbuild/freebsd-x64": "0.25.12",
3233
- "@esbuild/linux-arm": "0.25.12",
3234
- "@esbuild/linux-arm64": "0.25.12",
3235
- "@esbuild/linux-ia32": "0.25.12",
3236
- "@esbuild/linux-loong64": "0.25.12",
3237
- "@esbuild/linux-mips64el": "0.25.12",
3238
- "@esbuild/linux-ppc64": "0.25.12",
3239
- "@esbuild/linux-riscv64": "0.25.12",
3240
- "@esbuild/linux-s390x": "0.25.12",
3241
- "@esbuild/linux-x64": "0.25.12",
3242
- "@esbuild/netbsd-arm64": "0.25.12",
3243
- "@esbuild/netbsd-x64": "0.25.12",
3244
- "@esbuild/openbsd-arm64": "0.25.12",
3245
- "@esbuild/openbsd-x64": "0.25.12",
3246
- "@esbuild/openharmony-arm64": "0.25.12",
3247
- "@esbuild/sunos-x64": "0.25.12",
3248
- "@esbuild/win32-arm64": "0.25.12",
3249
- "@esbuild/win32-ia32": "0.25.12",
3250
- "@esbuild/win32-x64": "0.25.12"
3289
+ "@esbuild/aix-ppc64": "0.27.3",
3290
+ "@esbuild/android-arm": "0.27.3",
3291
+ "@esbuild/android-arm64": "0.27.3",
3292
+ "@esbuild/android-x64": "0.27.3",
3293
+ "@esbuild/darwin-arm64": "0.27.3",
3294
+ "@esbuild/darwin-x64": "0.27.3",
3295
+ "@esbuild/freebsd-arm64": "0.27.3",
3296
+ "@esbuild/freebsd-x64": "0.27.3",
3297
+ "@esbuild/linux-arm": "0.27.3",
3298
+ "@esbuild/linux-arm64": "0.27.3",
3299
+ "@esbuild/linux-ia32": "0.27.3",
3300
+ "@esbuild/linux-loong64": "0.27.3",
3301
+ "@esbuild/linux-mips64el": "0.27.3",
3302
+ "@esbuild/linux-ppc64": "0.27.3",
3303
+ "@esbuild/linux-riscv64": "0.27.3",
3304
+ "@esbuild/linux-s390x": "0.27.3",
3305
+ "@esbuild/linux-x64": "0.27.3",
3306
+ "@esbuild/netbsd-arm64": "0.27.3",
3307
+ "@esbuild/netbsd-x64": "0.27.3",
3308
+ "@esbuild/openbsd-arm64": "0.27.3",
3309
+ "@esbuild/openbsd-x64": "0.27.3",
3310
+ "@esbuild/openharmony-arm64": "0.27.3",
3311
+ "@esbuild/sunos-x64": "0.27.3",
3312
+ "@esbuild/win32-arm64": "0.27.3",
3313
+ "@esbuild/win32-ia32": "0.27.3",
3314
+ "@esbuild/win32-x64": "0.27.3"
3315
}
3316
},
3317
"node_modules/escalade": {
@@ -3274,9 +3338,9 @@
3338
}
3339
},
3340
"node_modules/eslint": {
3277
- "version": "9.39.1",
3278
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
3279
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
3341
+ "version": "9.39.3",
3342
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz",
3343
+ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
3344
"dev": true,
3345
"license": "MIT",
3346
"dependencies": {
@@ -3286,7 +3350,7 @@
3350
"@eslint/config-helpers": "^0.4.2",
3351
"@eslint/core": "^0.17.0",
3352
"@eslint/eslintrc": "^3.3.1",
3289
- "@eslint/js": "9.39.1",
3353
+ "@eslint/js": "9.39.3",
3354
"@eslint/plugin-kit": "^0.4.1",
3355
"@humanfs/node": "^0.16.6",
3356
"@humanwhocodes/module-importer": "^1.0.1",
@@ -3354,9 +3418,9 @@
3418
}
3419
},
3420
"node_modules/eslint-plugin-react-refresh": {
3357
- "version": "0.4.24",
3358
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
3359
- "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
3421
+ "version": "0.4.26",
3422
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
3423
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
3424
"dev": true,
3425
"license": "MIT",
3426
"peerDependencies": {
@@ -3393,6 +3457,13 @@
3457
"url": "https://opencollective.com/eslint"
3458
}
3459
},
3460
+ "node_modules/eslint/node_modules/balanced-match": {
3461
+ "version": "1.0.2",
3462
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
3463
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
3464
+ "dev": true,
3465
+ "license": "MIT"
3466
+ },
3467
"node_modules/eslint/node_modules/brace-expansion": {
3468
"version": "1.1.12",
3469
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -3428,9 +3499,9 @@
3499
}
3500
},
3501
"node_modules/eslint/node_modules/minimatch": {
3431
- "version": "3.1.2",
3432
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
3433
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
3502
+ "version": "3.1.3",
3503
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
3504
+ "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
3505
"dev": true,
3506
"license": "ISC",
3507
"dependencies": {
@@ -3472,9 +3543,9 @@
3543
}
3544
},
3545
"node_modules/esquery": {
3475
- "version": "1.6.0",
3476
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
3477
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
3546
+ "version": "1.7.0",
3547
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
3548
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
3549
"dev": true,
3550
"license": "BSD-3-Clause",
3551
"dependencies": {
@@ -3538,6 +3609,24 @@
3609
"dev": true,
3610
"license": "MIT"
3611
},
3612
+ "node_modules/fdir": {
3613
+ "version": "6.5.0",
3614
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
3615
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
3616
+ "dev": true,
3617
+ "license": "MIT",
3618
+ "engines": {
3619
+ "node": ">=12.0.0"
3620
+ },
3621
+ "peerDependencies": {
3622
+ "picomatch": "^3 || ^4"
3623
+ },
3624
+ "peerDependenciesMeta": {
3625
+ "picomatch": {
3626
+ "optional": true
3627
+ }
3628
+ }
3629
+ },
3630
"node_modules/file-entry-cache": {
3631
"version": "8.0.0",
3632
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3656,13 +3745,6 @@
3745
"dev": true,
3746
"license": "ISC"
3747
},
3659
- "node_modules/graphemer": {
3660
- "version": "1.4.0",
3661
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
3662
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
3663
- "dev": true,
3664
- "license": "MIT"
3665
- },
3748
"node_modules/has-flag": {
3749
"version": "4.0.0",
3750
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3859,9 +3941,9 @@
3941
}
3942
},
3943
"node_modules/lightningcss": {
3862
- "version": "1.30.2",
3863
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
3864
- "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
3944
+ "version": "1.31.1",
3945
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
3946
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
3947
"dev": true,
3948
"license": "MPL-2.0",
3949
"dependencies": {
@@ -3875,23 +3957,23 @@
3957
"url": "https://opencollective.com/parcel"
3958
},
3959
"optionalDependencies": {
3878
- "lightningcss-android-arm64": "1.30.2",
3879
- "lightningcss-darwin-arm64": "1.30.2",
3880
- "lightningcss-darwin-x64": "1.30.2",
3881
- "lightningcss-freebsd-x64": "1.30.2",
3882
- "lightningcss-linux-arm-gnueabihf": "1.30.2",
3883
- "lightningcss-linux-arm64-gnu": "1.30.2",
3884
- "lightningcss-linux-arm64-musl": "1.30.2",
3885
- "lightningcss-linux-x64-gnu": "1.30.2",
3886
- "lightningcss-linux-x64-musl": "1.30.2",
3887
- "lightningcss-win32-arm64-msvc": "1.30.2",
3888
- "lightningcss-win32-x64-msvc": "1.30.2"
3960
+ "lightningcss-android-arm64": "1.31.1",
3961
+ "lightningcss-darwin-arm64": "1.31.1",
3962
+ "lightningcss-darwin-x64": "1.31.1",
3963
+ "lightningcss-freebsd-x64": "1.31.1",
3964
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
3965
+ "lightningcss-linux-arm64-gnu": "1.31.1",
3966
+ "lightningcss-linux-arm64-musl": "1.31.1",
3967
+ "lightningcss-linux-x64-gnu": "1.31.1",
3968
+ "lightningcss-linux-x64-musl": "1.31.1",
3969
+ "lightningcss-win32-arm64-msvc": "1.31.1",
3970
+ "lightningcss-win32-x64-msvc": "1.31.1"
3971
}
3972
},
3973
"node_modules/lightningcss-android-arm64": {
3892
- "version": "1.30.2",
3893
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
3894
- "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
3974
+ "version": "1.31.1",
3975
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
3976
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
3977
"cpu": [
3978
"arm64"
3979
],
@@ -3910,9 +3992,9 @@
3992
}
3993
},
3994
"node_modules/lightningcss-darwin-arm64": {
3913
- "version": "1.30.2",
3914
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
3915
- "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
3995
+ "version": "1.31.1",
3996
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
3997
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
3998
"cpu": [
3999
"arm64"
4000
],
@@ -3931,9 +4013,9 @@
4013
}
4014
},
4015
"node_modules/lightningcss-darwin-x64": {
3934
- "version": "1.30.2",
3935
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
3936
- "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
4016
+ "version": "1.31.1",
4017
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
4018
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
4019
"cpu": [
4020
"x64"
4021
],
@@ -3952,9 +4034,9 @@
4034
}
4035
},
4036
"node_modules/lightningcss-freebsd-x64": {
3955
- "version": "1.30.2",
3956
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
3957
- "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
4037
+ "version": "1.31.1",
4038
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
4039
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
4040
"cpu": [
4041
"x64"
4042
],
@@ -3973,9 +4055,9 @@
4055
}
4056
},
4057
"node_modules/lightningcss-linux-arm-gnueabihf": {
3976
- "version": "1.30.2",
3977
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
3978
- "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
4058
+ "version": "1.31.1",
4059
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
4060
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
4061
"cpu": [
4062
"arm"
4063
],
@@ -3994,9 +4076,9 @@
4076
}
4077
},
4078
"node_modules/lightningcss-linux-arm64-gnu": {
3997
- "version": "1.30.2",
3998
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
3999
- "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
4079
+ "version": "1.31.1",
4080
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
4081
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
4082
"cpu": [
4083
"arm64"
4084
],
@@ -4015,9 +4097,9 @@
4097
}
4098
},
4099
"node_modules/lightningcss-linux-arm64-musl": {
4018
- "version": "1.30.2",
4019
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
4020
- "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
4100
+ "version": "1.31.1",
4101
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
4102
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
4103
"cpu": [
4104
"arm64"
4105
],
@@ -4036,9 +4118,9 @@
4118
}
4119
},
4120
"node_modules/lightningcss-linux-x64-gnu": {
4039
- "version": "1.30.2",
4040
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
4041
- "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
4121
+ "version": "1.31.1",
4122
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
4123
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
4124
"cpu": [
4125
"x64"
4126
],
@@ -4057,9 +4139,9 @@
4139
}
4140
},
4141
"node_modules/lightningcss-linux-x64-musl": {
4060
- "version": "1.30.2",
4061
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
4062
- "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
4142
+ "version": "1.31.1",
4143
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
4144
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
4145
"cpu": [
4146
"x64"
4147
],
@@ -4078,9 +4160,9 @@
4160
}
4161
},
4162
"node_modules/lightningcss-win32-arm64-msvc": {
4081
- "version": "1.30.2",
4082
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
4083
- "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
4163
+ "version": "1.31.1",
4164
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
4165
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
4166
"cpu": [
4167
"arm64"
4168
],
@@ -4099,9 +4181,9 @@
4181
}
4182
},
4183
"node_modules/lightningcss-win32-x64-msvc": {
4102
- "version": "1.30.2",
4103
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
4104
- "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
4184
+ "version": "1.31.1",
4185
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
4186
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
4187
"cpu": [
4188
"x64"
4189
],
@@ -4172,16 +4254,16 @@
4254
}
4255
},
4256
"node_modules/minimatch": {
4175
- "version": "9.0.5",
4176
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
4177
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
4257
+ "version": "10.2.2",
4258
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
4259
+ "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
4260
"dev": true,
4179
- "license": "ISC",
4261
+ "license": "BlueOak-1.0.0",
4262
"dependencies": {
4181
- "brace-expansion": "^2.0.1"
4263
+ "brace-expansion": "^5.0.2"
4264
},
4265
"engines": {
4184
- "node": ">=16 || 14 >=14.17"
4266
+ "node": "18 || 20 || >=22"
4267
},
4268
"funding": {
4269
"url": "https://github.com/sponsors/isaacs"
@@ -4317,6 +4399,19 @@
4399
"dev": true,
4400
"license": "ISC"
4401
},
4402
+ "node_modules/picomatch": {
4403
+ "version": "4.0.3",
4404
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
4405
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
4406
+ "dev": true,
4407
+ "license": "MIT",
4408
+ "engines": {
4409
+ "node": ">=12"
4410
+ },
4411
+ "funding": {
4412
+ "url": "https://github.com/sponsors/jonschlinkert"
4413
+ }
4414
+ },
4415
"node_modules/postcss": {
4416
"version": "8.5.6",
4417
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@@ -4367,24 +4462,24 @@
4462
}
4463
},
4464
"node_modules/react": {
4370
- "version": "19.2.1",
4371
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
4372
- "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
4465
+ "version": "19.2.4",
4466
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
4467
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
4468
"license": "MIT",
4469
"engines": {
4470
"node": ">=0.10.0"
4471
}
4472
},
4473
"node_modules/react-dom": {
4379
- "version": "19.2.1",
4380
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
4381
- "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
4474
+ "version": "19.2.4",
4475
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
4476
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
4477
"license": "MIT",
4478
"dependencies": {
4479
"scheduler": "^0.27.0"
4480
},
4481
"peerDependencies": {
4387
- "react": "^19.2.1"
4482
+ "react": "^19.2.4"
4483
}
4484
},
4485
"node_modules/react-refresh": {
@@ -4398,9 +4493,9 @@
4493
}
4494
},
4495
"node_modules/react-remove-scroll": {
4401
- "version": "2.7.1",
4402
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
4403
- "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
4496
+ "version": "2.7.2",
4497
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
4498
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
4499
"license": "MIT",
4500
"dependencies": {
4501
"react-remove-scroll-bar": "^2.3.7",
@@ -4445,9 +4540,9 @@
4540
}
4541
},
4542
"node_modules/react-router": {
4448
- "version": "7.10.1",
4449
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
4450
- "integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
4543
+ "version": "7.13.1",
4544
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
4545
+ "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
4546
"license": "MIT",
4547
"dependencies": {
4548
"cookie": "^1.0.1",
@@ -4467,12 +4562,12 @@
4562
}
4563
},
4564
"node_modules/react-router-dom": {
4470
- "version": "7.10.1",
4471
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz",
4472
- "integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==",
4565
+ "version": "7.13.1",
4566
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
4567
+ "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
4568
"license": "MIT",
4569
"dependencies": {
4475
- "react-router": "7.10.1"
4570
+ "react-router": "7.13.1"
4571
},
4572
"engines": {
4573
"node": ">=20.0.0"
@@ -4515,9 +4610,9 @@
4610
}
4611
},
4612
"node_modules/rollup": {
4518
- "version": "4.53.2",
4519
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
4520
- "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
4613
+ "version": "4.59.0",
4614
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
4615
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
4616
"dev": true,
4617
"license": "MIT",
4618
"dependencies": {
@@ -4531,28 +4626,31 @@
4626
"npm": ">=8.0.0"
4627
},
4628
"optionalDependencies": {
4534
- "@rollup/rollup-android-arm-eabi": "4.53.2",
4535
- "@rollup/rollup-android-arm64": "4.53.2",
4536
- "@rollup/rollup-darwin-arm64": "4.53.2",
4537
- "@rollup/rollup-darwin-x64": "4.53.2",
4538
- "@rollup/rollup-freebsd-arm64": "4.53.2",
4539
- "@rollup/rollup-freebsd-x64": "4.53.2",
4540
- "@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
4541
- "@rollup/rollup-linux-arm-musleabihf": "4.53.2",
4542
- "@rollup/rollup-linux-arm64-gnu": "4.53.2",
4543
- "@rollup/rollup-linux-arm64-musl": "4.53.2",
4544
- "@rollup/rollup-linux-loong64-gnu": "4.53.2",
4545
- "@rollup/rollup-linux-ppc64-gnu": "4.53.2",
4546
- "@rollup/rollup-linux-riscv64-gnu": "4.53.2",
4547
- "@rollup/rollup-linux-riscv64-musl": "4.53.2",
4548
- "@rollup/rollup-linux-s390x-gnu": "4.53.2",
4549
- "@rollup/rollup-linux-x64-gnu": "4.53.2",
4550
- "@rollup/rollup-linux-x64-musl": "4.53.2",
4551
- "@rollup/rollup-openharmony-arm64": "4.53.2",
4552
- "@rollup/rollup-win32-arm64-msvc": "4.53.2",
4553
- "@rollup/rollup-win32-ia32-msvc": "4.53.2",
4554
- "@rollup/rollup-win32-x64-gnu": "4.53.2",
4555
- "@rollup/rollup-win32-x64-msvc": "4.53.2",
4629
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
4630
+ "@rollup/rollup-android-arm64": "4.59.0",
4631
+ "@rollup/rollup-darwin-arm64": "4.59.0",
4632
+ "@rollup/rollup-darwin-x64": "4.59.0",
4633
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
4634
+ "@rollup/rollup-freebsd-x64": "4.59.0",
4635
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
4636
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
4637
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
4638
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
4639
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
4640
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
4641
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
4642
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
4643
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
4644
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
4645
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
4646
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
4647
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
4648
+ "@rollup/rollup-openbsd-x64": "4.59.0",
4649
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
4650
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
4651
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
4652
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
4653
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
4654
"fsevents": "~2.3.2"
4655
}
4656
},
@@ -4563,9 +4661,9 @@
4661
"license": "MIT"
4662
},
4663
"node_modules/semver": {
4566
- "version": "7.7.3",
4567
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
4568
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
4664
+ "version": "7.7.4",
4665
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
4666
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
4667
"dev": true,
4668
"license": "ISC",
4669
"bin": {
@@ -4641,9 +4739,9 @@
4739
}
4740
},
4741
"node_modules/tailwind-merge": {
4644
- "version": "3.4.0",
4645
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
4646
- "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
4742
+ "version": "3.5.0",
4743
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
4744
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
4745
"license": "MIT",
4746
"funding": {
4747
"type": "github",
@@ -4651,9 +4749,9 @@
4749
}
4750
},
4751
"node_modules/tailwindcss": {
4654
- "version": "4.1.17",
4655
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
4656
- "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
4752
+ "version": "4.2.1",
4753
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
4754
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
4755
"dev": true,
4756
"license": "MIT"
4757
},
@@ -4688,41 +4786,10 @@
4786
"url": "https://github.com/sponsors/SuperchupuDev"
4787
}
4788
},
4691
- "node_modules/tinyglobby/node_modules/fdir": {
4692
- "version": "6.5.0",
4693
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
4694
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
4695
- "dev": true,
4696
- "license": "MIT",
4697
- "engines": {
4698
- "node": ">=12.0.0"
4699
- },
4700
- "peerDependencies": {
4701
- "picomatch": "^3 || ^4"
4702
- },
4703
- "peerDependenciesMeta": {
4704
- "picomatch": {
4705
- "optional": true
4706
- }
4707
- }
4708
- },
4709
- "node_modules/tinyglobby/node_modules/picomatch": {
4710
- "version": "4.0.3",
4711
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
4712
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
4713
- "dev": true,
4714
- "license": "MIT",
4715
- "engines": {
4716
- "node": ">=12"
4717
- },
4718
- "funding": {
4719
- "url": "https://github.com/sponsors/jonschlinkert"
4720
- }
4721
- },
4789
"node_modules/ts-api-utils": {
4723
- "version": "2.1.0",
4724
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
4725
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
4790
+ "version": "2.4.0",
4791
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
4792
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
4793
"dev": true,
4794
"license": "MIT",
4795
"engines": {
@@ -4773,9 +4840,9 @@
4840
"license": "MIT"
4841
},
4842
"node_modules/update-browserslist-db": {
4776
- "version": "1.1.4",
4777
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
4778
- "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
4843
+ "version": "1.2.3",
4844
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
4845
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
4846
"dev": true,
4847
"funding": [
4848
{
@@ -4857,13 +4924,13 @@
4924
}
4925
},
4926
"node_modules/vite": {
4860
- "version": "7.2.6",
4861
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
4862
- "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
4927
+ "version": "7.3.1",
4928
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
4929
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
4930
"dev": true,
4931
"license": "MIT",
4932
"dependencies": {
4866
- "esbuild": "^0.25.0",
4933
+ "esbuild": "^0.27.0",
4934
"fdir": "^6.5.0",
4935
"picomatch": "^4.0.3",
4936
"postcss": "^8.5.6",
@@ -4931,37 +4998,6 @@
4998
}
4999
}
5000
},
4934
- "node_modules/vite/node_modules/fdir": {
4935
- "version": "6.5.0",
4936
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
4937
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
4938
- "dev": true,
4939
- "license": "MIT",
4940
- "engines": {
4941
- "node": ">=12.0.0"
4942
- },
4943
- "peerDependencies": {
4944
- "picomatch": "^3 || ^4"
4945
- },
4946
- "peerDependenciesMeta": {
4947
- "picomatch": {
4948
- "optional": true
4949
- }
4950
- }
4951
- },
4952
- "node_modules/vite/node_modules/picomatch": {
4953
- "version": "4.0.3",
4954
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
4955
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
4956
- "dev": true,
4957
- "license": "MIT",
4958
- "engines": {
4959
- "node": ">=12"
4960
- },
4961
- "funding": {
4962
- "url": "https://github.com/sponsors/jonschlinkert"
4963
- }
4964
- },
5001
"node_modules/which": {
5002
"version": "2.0.2",
5003
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5009,9 +5045,9 @@
5045
}
5046
},
5047
"node_modules/zod": {
5012
- "version": "4.1.12",
5013
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
5014
- "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
5048
+ "version": "4.3.6",
5049
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
5050
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
5051
"dev": true,
5052
"license": "MIT",
5053
"funding": {
cmd/relay-server/main.go
+55
-18
@@ -6,19 +6,19 @@ import (
6
"encoding/hex"
7
"flag"
8
"fmt"
9
+ "net"
10
"os"
11
"os/signal"
12
"strings"
13
"syscall"
14
"time"
15
15
- "github.com/hashicorp/yamux"
16
"github.com/rs/zerolog"
17
"github.com/rs/zerolog/log"
18
19
"gosuda.org/portal/cmd/relay-server/manager"
20
"gosuda.org/portal/portal"
21
- "gosuda.org/portal/sdk"
21
+ "gosuda.org/portal/portal/utils/sni"
22
"gosuda.org/portal/utils"
23
)
24
@@ -83,12 +83,7 @@ func runServer() error {
83
Str("bootstrap_uris", strings.Join(flagBootstraps, ",")).
84
Msg("[server] frontend configuration")
85
86
- cred := sdk.NewCredential()
87
-
88
- serv := portal.NewRelayServer(cred, flagBootstraps)
89
- if flagMaxLease > 0 {
90
- serv.SetMaxRelayedPerLease(flagMaxLease)
91
- }
86
+ serv := portal.NewRelayServer(flagBootstraps)
87
88
// Create AuthManager for admin authentication
89
// Auto-generate secret key if not provided
@@ -112,23 +107,65 @@ func runServer() error {
107
// Load persisted admin settings (ban list, BPS limits, IP bans)
108
admin.LoadSettings(serv)
109
115
- // Register relay callback for BPS handling and IP tracking
116
- serv.SetEstablishRelayCallback(func(clientStream, leaseStream *yamux.Stream, leaseID string) {
117
- // Associate pending IP with this lease
118
- ipManager := admin.GetIPManager()
119
- if ipManager != nil {
120
- if ip := ipManager.PopPendingIP(); ip != "" {
121
- ipManager.RegisterLeaseIP(leaseID, ip)
122
- }
110
+ // Start SNI-based TCP router for TLS passthrough
111
+ sniRouter := sni.NewRouter()
112
+
113
+ // Set up connection callback to route to tunnel backends
114
+ sniRouter.SetConnectionCallback(func(clientConn net.Conn, route *sni.Route) {
115
+ if _, ok := serv.GetLeaseManager().GetLeaseByID(route.LeaseID); !ok {
116
+ log.Warn().
117
+ Str("lease_id", route.LeaseID).
118
+ Str("sni", route.SNI).
119
+ Msg("[SNI] Lease not active; dropping connection and unregistering route")
120
+ sniRouter.UnregisterRouteByLeaseID(route.LeaseID)
121
+ clientConn.Close()
122
+ return
123
}
124
+
125
+ // Get BPS manager for rate limiting
126
bpsManager := admin.GetBPSManager()
125
- manager.EstablishRelayWithBPS(clientStream, leaseStream, leaseID, bpsManager)
127
+
128
+ // Prefer reverse tunnel (NAT-friendly) if available.
129
+ if reverseConn, err := serv.GetReverseHub().AcquireStarted(route.LeaseID, portal.ReverseSNIAcquireWait); err == nil {
130
+ defer reverseConn.Close()
131
+ manager.EstablishRelayWithBPS(clientConn, reverseConn.Conn, route.LeaseID, bpsManager)
132
+ return
133
+ }
134
+
135
+ // Connect to tunnel backend
136
+ tunnelConn, err := net.DialTimeout("tcp", route.TargetAddr, 10*time.Second)
137
+ if err != nil {
138
+ log.Error().
139
+ Err(err).
140
+ Str("target", route.TargetAddr).
141
+ Str("sni", route.SNI).
142
+ Msg("[SNI] Failed to connect to tunnel backend")
143
+ clientConn.Close()
144
+ return
145
+ }
146
+
147
+ // Establish relay with BPS limiting
148
+ manager.EstablishRelayWithBPS(clientConn, tunnelConn, route.LeaseID, bpsManager)
149
})
150
151
+ // Start SNI router on port 443 (or configurable port)
152
+ sniPort := ":443"
153
+ if envPort := os.Getenv("SNI_PORT"); envPort != "" {
154
+ sniPort = envPort
155
+ }
156
+
157
+ if err := sniRouter.Start(sniPort); err != nil {
158
+ log.Error().Err(err).Str("port", sniPort).Msg("[server] Failed to start SNI router")
159
+ // Continue without SNI router - HTTP proxy still works
160
+ } else {
161
+ log.Info().Str("port", sniPort).Msg("[server] SNI router started")
162
+ defer sniRouter.Stop()
163
+ }
164
+
165
serv.Start()
166
defer serv.Stop()
167
131
- httpSrv := serveHTTP(fmt.Sprintf(":%d", flagPort), serv, admin, frontend, flagNoIndex, stop)
168
+ httpSrv := serveHTTP(fmt.Sprintf(":%d", flagPort), serv, sniRouter, admin, frontend, flagNoIndex, stop)
169
170
<-ctx.Done()
171
log.Info().Msg("[server] shutting down...")
cmd/relay-server/manager/bps_manager.go
+7
-7
@@ -2,11 +2,11 @@ package manager
2
3
import (
4
"io"
5
+ "net"
6
"sync"
7
"sync/atomic"
8
"time"
9
9
- "github.com/hashicorp/yamux"
10
"github.com/rs/zerolog/log"
11
)
12
@@ -118,8 +118,8 @@ func (m *BPSManager) Copy(dst io.Writer, src io.Reader, leaseID string) (int64,
118
}
119
120
// EstablishRelayWithBPS sets up bidirectional relay with BPS limiting.
121
-// Connection tracking is handled by RelayServer's event loop (cmdCheckAndIncLimit/cmdDecLimit).
122
-func EstablishRelayWithBPS(clientStream, leaseStream *yamux.Stream, leaseID string, bpsManager *BPSManager) {
121
+// In the new TLS passthrough architecture, this uses net.Conn instead of yamux.Stream.
122
+func EstablishRelayWithBPS(clientConn, leaseConn net.Conn, leaseID string, bpsManager *BPSManager) {
123
bpsLimit := bpsManager.GetBPSLimit(leaseID)
124
log.Info().
125
Str("lease_id", leaseID).
@@ -138,15 +138,15 @@ func EstablishRelayWithBPS(clientStream, leaseStream *yamux.Stream, leaseID stri
138
// Client -> Lease
139
go func() {
140
defer wg.Done()
141
- bpsManager.Copy(leaseStream, clientStream, leaseID)
142
- leaseStream.Close()
141
+ bpsManager.Copy(leaseConn, clientConn, leaseID)
142
+ leaseConn.Close()
143
}()
144
145
// Lease -> Client
146
go func() {
147
defer wg.Done()
148
- bpsManager.Copy(clientStream, leaseStream, leaseID)
149
- clientStream.Close()
148
+ bpsManager.Copy(clientConn, leaseConn, leaseID)
149
+ clientConn.Close()
150
}()
151
152
wg.Wait()
cmd/relay-server/proxy.go
deleted
-223
@@ -1,223 +0,0 @@
1
-package main
2
-
3
-import (
4
- "bufio"
5
- "context"
6
- "io"
7
- "net"
8
- "net/http"
9
- "net/http/httputil"
10
- "net/url"
11
- "strings"
12
- "time"
13
-
14
- "github.com/rs/zerolog/log"
15
- "golang.org/x/net/idna"
16
- "gosuda.org/portal/portal"
17
- "gosuda.org/portal/utils"
18
-)
19
-
20
-type contextKey string
21
-
22
-const leaseIDContextKey contextKey = "leaseID"
23
-
24
-// HTTPProxy is a server-side HTTP reverse proxy that tunnels requests
25
-// to backend apps connected via portal tunnel. This makes all traffic
26
-// same-origin, enabling native Set-Cookie header support.
27
-type HTTPProxy struct {
28
- server *portal.RelayServer
29
- reverseProxy *httputil.ReverseProxy
30
-}
31
-
32
-// NewHTTPProxy creates a new HTTP reverse proxy for subdomain tunneling.
33
-func NewHTTPProxy(server *portal.RelayServer) *HTTPProxy {
34
- p := &HTTPProxy{server: server}
35
-
36
- transport := &http.Transport{
37
- MaxIdleConns: 100,
38
- MaxIdleConnsPerHost: 10,
39
- IdleConnTimeout: 90 * time.Second,
40
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
41
- // addr is "leaseID:80" from the rewritten URL
42
- host, _, err := net.SplitHostPort(addr)
43
- if err != nil {
44
- host = addr
45
- }
46
- return server.DialLease(host, "http/1.1")
47
- },
48
- }
49
-
50
- p.reverseProxy = &httputil.ReverseProxy{
51
- Rewrite: func(pr *httputil.ProxyRequest) {
52
- leaseID := pr.In.Context().Value(leaseIDContextKey).(string)
53
- pr.SetURL(&url.URL{
54
- Scheme: "http",
55
- Host: leaseID,
56
- })
57
- pr.SetXForwarded()
58
- },
59
- Transport: transport,
60
- FlushInterval: -1, // stream responses immediately
61
- ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
62
- log.Error().Err(err).
63
- Str("path", r.URL.Path).
64
- Str("host", r.Host).
65
- Msg("[HTTPProxy] reverse proxy error")
66
- http.Error(w, "Bad Gateway", http.StatusBadGateway)
67
- },
68
- }
69
-
70
- return p
71
-}
72
-
73
-// extractLeaseName extracts the lease name from the subdomain of the Host header.
74
-// Example: "demo-app.portal.example.com:4017" -> "demo-app"
75
-// Handles punycode/IDN domains.
76
-func extractLeaseName(host string) string {
77
- h := strings.ToLower(utils.StripPort(utils.StripScheme(host)))
78
- p := strings.ToLower(utils.StripPort(utils.StripScheme(flagPortalAppURL)))
79
-
80
- if strings.HasPrefix(p, "*.") {
81
- suffix := p[1:] // ".example.com"
82
- if len(h) > len(suffix) && strings.HasSuffix(h, suffix) {
83
- name := h[:len(h)-len(suffix)]
84
- // Handle URL-encoded characters
85
- if decoded, err := url.QueryUnescape(name); err == nil {
86
- name = decoded
87
- }
88
- // Handle punycode/IDN
89
- if unicode, err := idna.ToUnicode(name); err == nil {
90
- name = unicode
91
- }
92
- return name
93
- }
94
- }
95
-
96
- // Handle non-wildcard patterns (e.g., "sub.example.com" as base)
97
- if len(h) > len(p)+1 && strings.HasSuffix(h, "."+p) {
98
- name := h[:len(h)-len(p)-1]
99
- if decoded, err := url.QueryUnescape(name); err == nil {
100
- name = decoded
101
- }
102
- if unicode, err := idna.ToUnicode(name); err == nil {
103
- name = unicode
104
- }
105
- return name
106
- }
107
-
108
- return ""
109
-}
110
-
111
-// resolveLease resolves a lease name to a lease ID using case-insensitive matching.
112
-func (p *HTTPProxy) resolveLease(name string) (string, bool) {
113
- entry, ok := p.server.GetLeaseByNameFold(name)
114
- if !ok {
115
- return "", false
116
- }
117
- return entry.Lease.Identity.Id, true
118
-}
119
-
120
-// isWebSocketUpgrade checks if the request is a WebSocket upgrade request.
121
-func isWebSocketUpgrade(r *http.Request) bool {
122
- return strings.EqualFold(r.Header.Get("Upgrade"), "websocket") &&
123
- strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
124
-}
125
-
126
-// TryProxy attempts to reverse-proxy the request to a tunnel backend.
127
-// Returns true if the request was handled (proxied or WebSocket), false if
128
-// no matching lease was found (caller should fall back to portal HTML).
129
-func (p *HTTPProxy) TryProxy(w http.ResponseWriter, r *http.Request) bool {
130
- leaseName := extractLeaseName(r.Host)
131
- if leaseName == "" {
132
- return false
133
- }
134
-
135
- leaseID, ok := p.resolveLease(leaseName)
136
- if !ok {
137
- return false
138
- }
139
-
140
- if isWebSocketUpgrade(r) {
141
- p.handleWebSocket(w, r, leaseID)
142
- return true
143
- }
144
-
145
- // HTTP reverse proxy with lease ID in context
146
- ctx := context.WithValue(r.Context(), leaseIDContextKey, leaseID)
147
- p.reverseProxy.ServeHTTP(w, r.WithContext(ctx))
148
- return true
149
-}
150
-
151
-// handleWebSocket proxies a WebSocket upgrade request through the tunnel.
152
-func (p *HTTPProxy) handleWebSocket(w http.ResponseWriter, r *http.Request, leaseID string) {
153
- // 1. Dial backend through tunnel
154
- backendConn, err := p.server.DialLease(leaseID, "http/1.1")
155
- if err != nil {
156
- log.Error().Err(err).Str("lease_id", leaseID).Msg("[HTTPProxy] WebSocket: failed to dial lease")
157
- http.Error(w, "Bad Gateway", http.StatusBadGateway)
158
- return
159
- }
160
-
161
- // 2. Hijack client's TCP connection
162
- hijacker, ok := w.(http.Hijacker)
163
- if !ok {
164
- log.Error().Msg("[HTTPProxy] WebSocket: response writer does not support hijacking")
165
- backendConn.Close()
166
- http.Error(w, "Internal Server Error", http.StatusInternalServerError)
167
- return
168
- }
169
-
170
- clientConn, _, err := hijacker.Hijack()
171
- if err != nil {
172
- log.Error().Err(err).Msg("[HTTPProxy] WebSocket: failed to hijack connection")
173
- backendConn.Close()
174
- return
175
- }
176
-
177
- // 3. Write the original upgrade request to backend
178
- if err := r.Write(backendConn); err != nil {
179
- log.Error().Err(err).Msg("[HTTPProxy] WebSocket: failed to write upgrade request to backend")
180
- clientConn.Close()
181
- backendConn.Close()
182
- return
183
- }
184
-
185
- // 4. Read backend response and forward to client
186
- backendBuf := bufio.NewReader(backendConn)
187
- resp, err := http.ReadResponse(backendBuf, r)
188
- if err != nil {
189
- log.Error().Err(err).Msg("[HTTPProxy] WebSocket: failed to read backend response")
190
- clientConn.Close()
191
- backendConn.Close()
192
- return
193
- }
194
-
195
- if err := resp.Write(clientConn); err != nil {
196
- log.Error().Err(err).Msg("[HTTPProxy] WebSocket: failed to write response to client")
197
- clientConn.Close()
198
- backendConn.Close()
199
- return
200
- }
201
-
202
- if resp.StatusCode != http.StatusSwitchingProtocols {
203
- clientConn.Close()
204
- backendConn.Close()
205
- return
206
- }
207
-
208
- // 5. Bidirectional relay
209
- errc := make(chan error, 2)
210
- go func() {
211
- _, err := io.Copy(backendConn, clientConn)
212
- errc <- err
213
- }()
214
- go func() {
215
- // Use backendBuf to drain any data buffered during ReadResponse
216
- _, err := io.Copy(clientConn, backendBuf)
217
- errc <- err
218
- }()
219
-
220
- <-errc
221
- clientConn.Close()
222
- backendConn.Close()
223
-}
cmd/relay-server/registry.go
new
+344
@@ -0,0 +1,344 @@
1
+package main
2
+
3
+import (
4
+ "crypto/subtle"
5
+ "encoding/json"
6
+ "fmt"
7
+ "net"
8
+ "net/http"
9
+ "strings"
10
+ "time"
11
+
12
+ "github.com/rs/zerolog/log"
13
+ "gosuda.org/portal/cmd/relay-server/manager"
14
+ "gosuda.org/portal/portal"
15
+ "gosuda.org/portal/portal/utils/sni"
16
+ "gosuda.org/portal/utils"
17
+)
18
+
19
+// SDKRegistry handles HTTP API for SDK lease registration
20
+// Used by both tunnel clients and native applications
21
+type SDKRegistry struct {
22
+ server *portal.RelayServer
23
+ sniRouter *sni.Router
24
+ baseHost string
25
+}
26
+
27
+// NewSDKRegistry creates a new SDK registry
28
+func NewSDKRegistry(server *portal.RelayServer, sniRouter *sni.Router, appURL string) *SDKRegistry {
29
+ baseHost := strings.ToLower(strings.TrimSpace(
30
+ utils.StripPort(utils.StripWildCard(utils.StripScheme(appURL))),
31
+ ))
32
+ return &SDKRegistry{
33
+ server: server,
34
+ sniRouter: sniRouter,
35
+ baseHost: baseHost,
36
+ }
37
+}
38
+
39
+// RegisterRequest represents an SDK lease registration request
40
+type RegisterRequest struct {
41
+ LeaseID string `json:"lease_id"`
42
+ Name string `json:"name"`
43
+ Address string `json:"address"` // Backend address for TCP connection
44
+ Metadata portal.Metadata `json:"metadata"`
45
+ TLSEnabled bool `json:"tls_enabled"` // Whether the backend handles TLS termination
46
+ ReverseToken string `json:"reverse_token"`
47
+}
48
+
49
+// RegisterResponse represents an SDK lease registration response
50
+type RegisterResponse struct {
51
+ Success bool `json:"success"`
52
+ Message string `json:"message,omitempty"`
53
+ LeaseID string `json:"lease_id,omitempty"`
54
+ PublicURL string `json:"public_url,omitempty"`
55
+}
56
+
57
+// HandleRegister handles SDK lease registration requests
58
+func (r *SDKRegistry) HandleRegister(w http.ResponseWriter, req *http.Request) {
59
+ if req.Method != http.MethodPost {
60
+ w.Header().Set("Allow", http.MethodPost)
61
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
62
+ return
63
+ }
64
+
65
+ var registerReq RegisterRequest
66
+ if err := json.NewDecoder(req.Body).Decode(®isterReq); err != nil {
67
+ log.Error().Err(err).Msg("[Registry] Failed to decode registration request")
68
+ writeJSON(w, RegisterResponse{
69
+ Success: false,
70
+ Message: "invalid request body",
71
+ })
72
+ return
73
+ }
74
+
75
+ // Validate request
76
+ if registerReq.LeaseID == "" {
77
+ writeJSON(w, RegisterResponse{
78
+ Success: false,
79
+ Message: "lease_id is required",
80
+ })
81
+ return
82
+ }
83
+
84
+ if registerReq.Name == "" {
85
+ writeJSON(w, RegisterResponse{
86
+ Success: false,
87
+ Message: "name is required",
88
+ })
89
+ return
90
+ }
91
+
92
+ if registerReq.Address == "" {
93
+ writeJSON(w, RegisterResponse{
94
+ Success: false,
95
+ Message: "address is required",
96
+ })
97
+ return
98
+ }
99
+ if strings.TrimSpace(registerReq.ReverseToken) == "" {
100
+ writeJSON(w, RegisterResponse{
101
+ Success: false,
102
+ Message: "reverse_token is required",
103
+ })
104
+ return
105
+ }
106
+
107
+ resolvedAddr, err := resolveLeaseAddress(req, registerReq.Address)
108
+ if err != nil {
109
+ writeJSON(w, RegisterResponse{
110
+ Success: false,
111
+ Message: err.Error(),
112
+ })
113
+ return
114
+ }
115
+
116
+ // Create lease
117
+ lease := &portal.Lease{
118
+ ID: registerReq.LeaseID,
119
+ Name: registerReq.Name,
120
+ Address: resolvedAddr,
121
+ Metadata: registerReq.Metadata,
122
+ Expires: time.Now().Add(30 * time.Second),
123
+ TLSEnabled: registerReq.TLSEnabled,
124
+ ReverseToken: strings.TrimSpace(registerReq.ReverseToken),
125
+ }
126
+
127
+ // Register with lease manager
128
+ if !r.server.GetLeaseManager().UpdateLease(lease) {
129
+ writeJSON(w, RegisterResponse{
130
+ Success: false,
131
+ Message: "failed to register lease (name conflict or policy violation)",
132
+ })
133
+ return
134
+ }
135
+
136
+ if err := r.registerSNIRoute(registerReq.LeaseID, registerReq.Name, resolvedAddr); err != nil {
137
+ // Keep lease and route state consistent on partial failure.
138
+ r.server.GetLeaseManager().DeleteLease(registerReq.LeaseID)
139
+ writeJSON(w, RegisterResponse{
140
+ Success: false,
141
+ Message: fmt.Sprintf("failed to register SNI route: %v", err),
142
+ })
143
+ return
144
+ }
145
+
146
+ log.Info().
147
+ Str("lease_id", registerReq.LeaseID).
148
+ Str("name", registerReq.Name).
149
+ Str("address", resolvedAddr).
150
+ Str("address_advertised", registerReq.Address).
151
+ Bool("tls_enabled", registerReq.TLSEnabled).
152
+ Msg("[Registry] Lease registered")
153
+
154
+ // Build public URL
155
+ publicURL := ""
156
+ if flagPortalAppURL != "" {
157
+ publicURL = "https://" + registerReq.Name + "." + stripWildcard(flagPortalAppURL)
158
+ }
159
+
160
+ writeJSON(w, RegisterResponse{
161
+ Success: true,
162
+ LeaseID: registerReq.LeaseID,
163
+ PublicURL: publicURL,
164
+ })
165
+}
166
+
167
+// HandleUnregister handles SDK lease unregistration requests
168
+func (r *SDKRegistry) HandleUnregister(w http.ResponseWriter, req *http.Request) {
169
+ if req.Method != http.MethodPost {
170
+ w.Header().Set("Allow", http.MethodPost)
171
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
172
+ return
173
+ }
174
+
175
+ var unregisterReq struct {
176
+ LeaseID string `json:"lease_id"`
177
+ }
178
+
179
+ if err := json.NewDecoder(req.Body).Decode(&unregisterReq); err != nil {
180
+ log.Error().Err(err).Msg("[Registry] Failed to decode unregistration request")
181
+ writeJSON(w, map[string]interface{}{
182
+ "success": false,
183
+ "message": "invalid request body",
184
+ })
185
+ return
186
+ }
187
+
188
+ if unregisterReq.LeaseID == "" {
189
+ writeJSON(w, map[string]interface{}{
190
+ "success": false,
191
+ "message": "lease_id is required",
192
+ })
193
+ return
194
+ }
195
+
196
+ // Delete from lease manager
197
+ if r.server.GetLeaseManager().DeleteLease(unregisterReq.LeaseID) {
198
+ log.Info().
199
+ Str("lease_id", unregisterReq.LeaseID).
200
+ Msg("[Registry] Lease unregistered")
201
+ }
202
+ r.unregisterSNIRoute(unregisterReq.LeaseID)
203
+ r.server.GetReverseHub().DropLease(unregisterReq.LeaseID)
204
+
205
+ writeJSON(w, map[string]interface{}{
206
+ "success": true,
207
+ })
208
+}
209
+
210
+// HandleRenew handles SDK lease renewal requests (keepalive)
211
+func (r *SDKRegistry) HandleRenew(w http.ResponseWriter, req *http.Request) {
212
+ if req.Method != http.MethodPost {
213
+ w.Header().Set("Allow", http.MethodPost)
214
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
215
+ return
216
+ }
217
+
218
+ var renewReq struct {
219
+ LeaseID string `json:"lease_id"`
220
+ ReverseToken string `json:"reverse_token"`
221
+ }
222
+
223
+ if err := json.NewDecoder(req.Body).Decode(&renewReq); err != nil {
224
+ log.Error().Err(err).Msg("[Registry] Failed to decode renewal request")
225
+ writeJSON(w, map[string]interface{}{
226
+ "success": false,
227
+ "message": "invalid request body",
228
+ })
229
+ return
230
+ }
231
+
232
+ if renewReq.LeaseID == "" {
233
+ writeJSON(w, map[string]interface{}{
234
+ "success": false,
235
+ "message": "lease_id is required",
236
+ })
237
+ return
238
+ }
239
+ if strings.TrimSpace(renewReq.ReverseToken) == "" {
240
+ writeJSON(w, map[string]interface{}{
241
+ "success": false,
242
+ "message": "reverse_token is required",
243
+ })
244
+ return
245
+ }
246
+
247
+ // Get existing lease
248
+ entry, ok := r.server.GetLeaseManager().GetLeaseByID(renewReq.LeaseID)
249
+ if !ok {
250
+ writeJSON(w, map[string]interface{}{
251
+ "success": false,
252
+ "message": "lease not found",
253
+ })
254
+ return
255
+ }
256
+ if subtle.ConstantTimeCompare([]byte(strings.TrimSpace(entry.Lease.ReverseToken)), []byte(strings.TrimSpace(renewReq.ReverseToken))) != 1 {
257
+ writeJSON(w, map[string]interface{}{
258
+ "success": false,
259
+ "message": "unauthorized lease renewal",
260
+ })
261
+ return
262
+ }
263
+
264
+ // Update expiration
265
+ entry.Lease.Expires = time.Now().Add(30 * time.Second)
266
+ if !r.server.GetLeaseManager().UpdateLease(entry.Lease) {
267
+ writeJSON(w, map[string]interface{}{
268
+ "success": false,
269
+ "message": "failed to renew lease",
270
+ })
271
+ return
272
+ }
273
+
274
+ // Re-register route if needed (e.g., router restarted while lease remained active).
275
+ if err := r.registerSNIRoute(entry.Lease.ID, entry.Lease.Name, entry.Lease.Address); err != nil {
276
+ log.Warn().
277
+ Err(err).
278
+ Str("lease_id", entry.Lease.ID).
279
+ Str("name", entry.Lease.Name).
280
+ Msg("[Registry] Failed to refresh SNI route on renew")
281
+ }
282
+
283
+ writeJSON(w, map[string]interface{}{
284
+ "success": true,
285
+ })
286
+}
287
+
288
+func (r *SDKRegistry) registerSNIRoute(leaseID, name, address string) error {
289
+ if r.sniRouter == nil {
290
+ return nil
291
+ }
292
+ if r.baseHost == "" {
293
+ return fmt.Errorf("invalid app domain configuration")
294
+ }
295
+ sniName := strings.ToLower(strings.TrimSpace(name)) + "." + r.baseHost
296
+ return r.sniRouter.RegisterRoute(sniName, address, leaseID, name)
297
+}
298
+
299
+func resolveLeaseAddress(req *http.Request, advertisedAddr string) (string, error) {
300
+ advertisedAddr = strings.TrimSpace(advertisedAddr)
301
+ host, port, err := net.SplitHostPort(advertisedAddr)
302
+ if err != nil {
303
+ return "", fmt.Errorf("invalid address: %q", advertisedAddr)
304
+ }
305
+
306
+ if isLoopbackOrLocalHost(host) {
307
+ clientIP := strings.TrimSpace(manager.ExtractClientIP(req))
308
+ if clientIP == "" {
309
+ return "", fmt.Errorf("cannot resolve client IP for address: %q", advertisedAddr)
310
+ }
311
+ host = clientIP
312
+ }
313
+
314
+ return net.JoinHostPort(host, port), nil
315
+}
316
+
317
+func isLoopbackOrLocalHost(host string) bool {
318
+ h := strings.ToLower(strings.Trim(strings.TrimSpace(host), "[]"))
319
+ if h == "" || h == "localhost" {
320
+ return true
321
+ }
322
+
323
+ ip := net.ParseIP(h)
324
+ if ip == nil {
325
+ return false
326
+ }
327
+
328
+ return ip.IsLoopback() || ip.IsUnspecified()
329
+}
330
+
331
+func (r *SDKRegistry) unregisterSNIRoute(leaseID string) {
332
+ if r.sniRouter == nil {
333
+ return
334
+ }
335
+ r.sniRouter.UnregisterRouteByLeaseID(leaseID)
336
+}
337
+
338
+// stripWildcard removes the wildcard prefix from a domain
339
+func stripWildcard(domain string) string {
340
+ if len(domain) > 2 && domain[:2] == "*." {
341
+ return domain[2:]
342
+ }
343
+ return domain
344
+}
cmd/relay-server/serve.go
+174
-94
@@ -1,15 +1,20 @@
1
package main
2
3
import (
4
+ "bufio"
5
"context"
6
"embed"
7
+ "fmt"
8
+ "io"
9
+ "net"
10
"net/http"
11
"strings"
12
13
"github.com/rs/zerolog/log"
14
+ "golang.org/x/net/websocket"
15
11
- "gosuda.org/portal/cmd/relay-server/manager"
16
"gosuda.org/portal/portal"
17
+ "gosuda.org/portal/portal/utils/sni"
18
"gosuda.org/portal/utils"
19
)
20
@@ -17,16 +22,11 @@ import (
22
var distFS embed.FS
23
24
// serveHTTP builds the HTTP mux and returns the server.
20
-func serveHTTP(addr string, serv *portal.RelayServer, admin *Admin, frontend *Frontend, noIndex bool, cancel context.CancelFunc) *http.Server {
25
+func serveHTTP(addr string, serv *portal.RelayServer, sniRouter *sni.Router, admin *Admin, frontend *Frontend, noIndex bool, cancel context.CancelFunc) *http.Server {
26
if addr == "" {
27
addr = ":0"
28
}
29
25
- // Initialize WASM cache used by content handlers
26
- if err := frontend.InitWasmCache(); err != nil {
27
- log.Error().Err(err).Msg("failed to initialize WASM cache")
28
- }
29
-
30
// Create app UI mux
31
appMux := http.NewServeMux()
32
@@ -49,17 +49,6 @@ func serveHTTP(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fr
49
frontend.ServeAppStatic(w, r, p, serv)
50
}))
51
52
- // Portal frontend files (for unified caching)
53
- appMux.HandleFunc("/frontend/", withCORSMiddleware(func(w http.ResponseWriter, r *http.Request) {
54
- p := strings.TrimPrefix(r.URL.Path, "/frontend/")
55
- if p == "manifest.json" {
56
- frontend.ServeDynamicManifest(w, r)
57
- return
58
- }
59
-
60
- frontend.ServePortalStaticFile(w, r, p)
61
- }))
62
-
52
// Tunnel installer script and binaries
53
appMux.HandleFunc("/tunnel", func(w http.ResponseWriter, r *http.Request) {
54
serveTunnelScript(w, r)
@@ -68,38 +57,14 @@ func serveHTTP(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fr
57
serveTunnelBinary(w, r)
58
})
59
71
- appMux.HandleFunc("/relay", func(w http.ResponseWriter, r *http.Request) {
72
- if r.Method != http.MethodGet {
73
- w.Header().Set("Allow", http.MethodGet)
74
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
75
- return
76
- }
77
-
78
- // Check if IP is banned
79
- clientIP := manager.ExtractClientIP(r)
80
- ipManager := admin.GetIPManager()
81
- if ipManager != nil && ipManager.IsIPBanned(clientIP) {
82
- log.Warn().Str("ip", clientIP).Msg("[server] connection rejected: IP banned")
83
- http.Error(w, "forbidden", http.StatusForbidden)
84
- return
85
- }
86
-
87
- stream, wsConn, err := utils.UpgradeToWSStream(w, r, nil)
88
- if err != nil {
89
- log.Error().Err(err).Msg("[server] websocket upgrade failed")
90
- return
91
- }
92
-
93
- // Store pending IP for lease association (will be linked when lease is registered)
94
- if ipManager != nil && clientIP != "" {
95
- ipManager.StorePendingIP(clientIP)
96
- }
97
-
98
- if err := serv.HandleConnection(stream); err != nil {
99
- log.Error().Err(err).Msg("[server] websocket relay connection error")
100
- wsConn.Close()
101
- return
102
- }
60
+ // SDK Registry API for lease registration (used by SDK and tunnel clients)
61
+ registry := NewSDKRegistry(serv, sniRouter, flagPortalAppURL)
62
+ appMux.HandleFunc("/api/register", registry.HandleRegister)
63
+ appMux.HandleFunc("/api/unregister", registry.HandleUnregister)
64
+ appMux.HandleFunc("/api/renew", registry.HandleRenew)
65
+ appMux.Handle("/api/connect", websocket.Server{
66
+ Handshake: func(*websocket.Config, *http.Request) error { return nil },
67
+ Handler: websocket.Handler(serv.GetReverseHub().HandleConnect),
68
})
69
70
// App UI index page - serve React frontend with SSR (delegates to serveAppStatic)
@@ -119,53 +84,28 @@ func serveHTTP(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fr
84
admin.HandleAdminRequest(w, r, serv)
85
})
86
122
- // Create portal frontend mux (routes only)
123
- portalMux := http.NewServeMux()
124
-
125
- // Static file handler for /frontend/ (for unified caching)
126
- portalMux.HandleFunc("/frontend/", withCORSMiddleware(func(w http.ResponseWriter, r *http.Request) {
127
- p := strings.TrimPrefix(r.URL.Path, "/frontend/")
128
- if p == "manifest.json" {
129
- frontend.ServeDynamicManifest(w, r)
130
- return
131
- }
132
- frontend.ServePortalStaticFile(w, r, p)
133
- }))
134
-
135
- // Service worker for portal subdomains (serve from dist/wasm)
136
- portalMux.HandleFunc("/service-worker.js", func(w http.ResponseWriter, r *http.Request) {
137
- frontend.ServeDynamicServiceWorker(w, r)
138
- })
139
-
140
- // Create HTTP reverse proxy for subdomain tunneling (same-origin cookie support)
141
- httpProxy := NewHTTPProxy(serv)
142
-
143
- // Root handler: try server-side reverse proxy first, then fall back to portal HTML
144
- portalMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
145
- // Try server-side reverse proxy to tunnel backend
146
- if httpProxy.TryProxy(w, r) {
147
- return
148
- }
149
-
150
- // Fallback: serve portal frontend (Service Worker based proxy)
151
- withCORSMiddleware(func(w http.ResponseWriter, r *http.Request) {
152
- if r.URL.Path == "/" {
153
- frontend.ServePortalHTMLWithSSR(w, r, serv)
154
- return
155
- }
156
- frontend.ServePortalStatic(w, r)
157
- })(w, r)
158
- })
159
-
160
- // routes based on host and path
87
+ // Create the main handler
88
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
162
- // Route subdomain requests (e.g., *.example.com) to portalMux
163
- // and everything else to the app UI mux.
89
+ // Handle subdomain requests
90
if utils.IsSubdomain(flagPortalAppURL, r.Host) {
165
- portalMux.ServeHTTP(w, r)
166
- } else {
167
- appMux.ServeHTTP(w, r)
91
+ log.Debug().
92
+ Str("host", r.Host).
93
+ Str("url", r.URL.String()).
94
+ Msg("[server] handling subdomain request")
95
+ // Check if the tunnel has TLS enabled by looking up the lease
96
+ if shouldProxyHTTP(r.Host, serv) {
97
+ // TLS is not enabled on the tunnel, proxy via HTTP
98
+ log.Debug().Str("host", r.Host).Msg("[server] proxying to HTTP")
99
+ proxyToHTTP(w, r, serv)
100
+ return
101
+ }
102
+ // TLS is enabled, redirect to HTTPS
103
+ // The SNI router handles TLS passthrough on :443.
104
+ log.Debug().Str("host", r.Host).Msg("[server] redirecting to HTTPS")
105
+ redirectToHTTPS(w, r)
106
+ return
107
}
108
+ appMux.ServeHTTP(w, r)
109
})
110
111
srv := &http.Server{
@@ -184,6 +124,146 @@ func serveHTTP(addr string, serv *portal.RelayServer, admin *Admin, frontend *Fr
124
return srv
125
}
126
127
+// leaseNameFromHost extracts the lease name from a subdomain host.
128
+// It returns the lease name and true if the host is a valid subdomain of appURL.
129
+func leaseNameFromHost(host, appURL string) (string, bool) {
130
+ if !utils.IsSubdomain(appURL, host) {
131
+ return "", false
132
+ }
133
+
134
+ normalizedHost := strings.ToLower(strings.TrimSpace(utils.StripPort(host)))
135
+ baseHost := strings.ToLower(strings.TrimSpace(
136
+ utils.StripPort(utils.StripWildCard(utils.StripScheme(appURL))),
137
+ ))
138
+
139
+ if normalizedHost == "" || baseHost == "" || normalizedHost == baseHost {
140
+ return "", false
141
+ }
142
+
143
+ suffix := "." + baseHost
144
+ if !strings.HasSuffix(normalizedHost, suffix) {
145
+ return "", false
146
+ }
147
+
148
+ leaseName := strings.TrimSuffix(normalizedHost, suffix)
149
+ if leaseName == "" || strings.Contains(leaseName, ".") {
150
+ // Lease names do not include dots; avoid ambiguous nested subdomains.
151
+ return "", false
152
+ }
153
+
154
+ return leaseName, true
155
+}
156
+
157
+// redirectToHTTPS redirects the request to HTTPS on port 443
158
+func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {
159
+ target := "https://" + r.Host + r.URL.Path
160
+ if r.URL.RawQuery != "" {
161
+ target += "?" + r.URL.RawQuery
162
+ }
163
+ log.Debug().
164
+ Str("from", r.URL.String()).
165
+ Str("to", target).
166
+ Msg("[server] redirecting to HTTPS")
167
+ http.Redirect(w, r, target, http.StatusMovedPermanently)
168
+}
169
+
170
+// shouldProxyHTTP checks if the request should be proxied via HTTP
171
+// based on the lease's TLSEnabled setting.
172
+// Returns true if TLS is NOT enabled (can proxy via HTTP).
173
+func shouldProxyHTTP(host string, serv *portal.RelayServer) bool {
174
+ leaseName, ok := leaseNameFromHost(host, flagPortalAppURL)
175
+ if !ok {
176
+ log.Debug().Str("host", host).Msg("[proxy] shouldProxyHTTP: failed to extract lease name")
177
+ return false
178
+ }
179
+
180
+ entry, ok := serv.GetLeaseManager().GetLeaseByName(leaseName)
181
+ if !ok {
182
+ log.Debug().Str("lease_name", leaseName).Msg("[proxy] shouldProxyHTTP: lease not found")
183
+ return true
184
+ }
185
+
186
+ // If TLS is NOT enabled, we can proxy via HTTP
187
+ shouldProxy := !entry.Lease.TLSEnabled
188
+ log.Debug().
189
+ Str("lease_name", leaseName).
190
+ Bool("tls_enabled", entry.Lease.TLSEnabled).
191
+ Bool("should_proxy_http", shouldProxy).
192
+ Msg("[proxy] shouldProxyHTTP check")
193
+ return shouldProxy
194
+}
195
+
196
+func proxyToHTTP(w http.ResponseWriter, r *http.Request, serv *portal.RelayServer) {
197
+ leaseName, ok := leaseNameFromHost(r.Host, flagPortalAppURL)
198
+ if !ok {
199
+ http.Error(w, "invalid subdomain", http.StatusBadRequest)
200
+ return
201
+ }
202
+
203
+ // Find lease by name
204
+ entry, ok := serv.GetLeaseManager().GetLeaseByName(leaseName)
205
+ if !ok {
206
+ http.Error(w, "service not found", http.StatusNotFound)
207
+ return
208
+ }
209
+
210
+ if entry.Lease.TLSEnabled {
211
+ http.Error(w, "TLS enabled requires HTTPS access", http.StatusBadRequest)
212
+ return
213
+ }
214
+
215
+ targetConn, releaseConn, err := openLeaseConnection(entry.Lease.ID, serv)
216
+ if err != nil {
217
+ log.Error().
218
+ Err(err).
219
+ Str("lease", leaseName).
220
+ Str("target", entry.Lease.Address).
221
+ Msg("[proxy] failed to connect to backend")
222
+ http.Error(w, "service unavailable", http.StatusServiceUnavailable)
223
+ return
224
+ }
225
+ defer releaseConn()
226
+
227
+ // Write the HTTP request to the tunnel
228
+ if err := r.Write(targetConn); err != nil {
229
+ log.Error().Err(err).Msg("[proxy] failed to write request to tunnel")
230
+ http.Error(w, "proxy error", http.StatusInternalServerError)
231
+ return
232
+ }
233
+
234
+ // Read the response from the tunnel
235
+ resp, err := http.ReadResponse(bufio.NewReader(targetConn), r)
236
+ if err != nil {
237
+ log.Error().Err(err).Msg("[proxy] failed to read response from tunnel")
238
+ http.Error(w, "proxy error", http.StatusInternalServerError)
239
+ return
240
+ }
241
+ defer resp.Body.Close()
242
+
243
+ // Copy headers
244
+ for k, vv := range resp.Header {
245
+ for _, v := range vv {
246
+ w.Header().Add(k, v)
247
+ }
248
+ }
249
+
250
+ // Write status code
251
+ w.WriteHeader(resp.StatusCode)
252
+
253
+ // Copy body
254
+ if _, err := io.Copy(w, resp.Body); err != nil {
255
+ log.Debug().Err(err).Msg("[proxy] error copying response body")
256
+ }
257
+}
258
+
259
+func openLeaseConnection(leaseID string, serv *portal.RelayServer) (net.Conn, func(), error) {
260
+ reverseConn, err := serv.GetReverseHub().AcquireStarted(leaseID, portal.ReverseHTTPWait)
261
+ if err != nil {
262
+ return nil, nil, fmt.Errorf("no reverse connection available for lease %s: %w", leaseID, err)
263
+ }
264
+ return reverseConn.Conn, reverseConn.Close, nil
265
+}
266
+
267
func withCORSMiddleware(h http.HandlerFunc) http.HandlerFunc {
268
return func(w http.ResponseWriter, r *http.Request) {
269
utils.SetCORSHeaders(w)
cmd/vanity-id/README.md
deleted
-117
@@ -1,117 +0,0 @@
1
-# Vanity ID Generator
2
-
3
-A high-performance parallel vanity ID generator that uses `cryptoops.DeriveID` and `randpool` to quickly generate cryptographic identities with custom prefix patterns.
4
-
5
-## Features
6
-
7
-- **Parallel Processing**: Uses multiple goroutines to maximize CPU utilization
8
-- **Fast Random Generation**: Leverages `randpool.CSPRNG_RAND` for efficient random number generation
9
-- **Real-time Statistics**: Displays attempt rate, progress, and estimated time every 2 seconds
10
-- **Smart ETA Calculation**: Mathematically calculates expected completion time based on prefix length
11
-- **Configurable**: Customizable prefix, worker count, and result limit
12
-
13
-## Usage
14
-
15
-```bash
16
-# Generate one ID with "CHAT" prefix (default)
17
-go run ./cmd/vanity-id
18
-
19
-# Generate IDs with custom prefix
20
-go run ./cmd/vanity-id -prefix PORTAL
21
-
22
-# Generate multiple IDs
23
-go run ./cmd/vanity-id -prefix DNS -max 3
24
-
25
-# Use more workers (default is number of CPUs)
26
-go run ./cmd/vanity-id -prefix KEY -workers 16
27
-
28
-# Generate unlimited IDs (press Ctrl+C to stop)
29
-go run ./cmd/vanity-id -prefix TEST -max 0
30
-```
31
-
32
-## Command-line Options
33
-
34
-- `-prefix`: ID prefix to search for (default: "CHAT")
35
-- `-workers`: Number of parallel workers (default: number of CPUs)
36
-- `-max`: Maximum number of results to find, 0 = unlimited (default: 1)
37
-
38
-## Output Example
39
-
40
-```
41
-Searching for IDs with prefix: TEST (4 characters)
42
-Using 8 parallel workers
43
-Max results: 1
44
-Expected attempts per result: 524288 (average)
45
-
46
-[Stats] Attempts: 546004 | Found: 0 | Rate: 272418/sec | Elapsed: 2.0s | ETA: 2s
47
-[#1] Found at 3.60s (attempt #807217):
48
- ID: TESTIWIBIRNDLZOHD3H2D6AD7Q
49
- PrivateKey: ZIhWbN39MThmbqREW+Ir7PvRxzzcuEVvJlOGwuive1ZL6RMsaBDcOSWj5MzSeyS+uqG8JARUssjODC70oC+sXg==
50
- PublicKey: S+kTLGgQ3Dklo+TM0nskvrqhvCQEVLLIzgwu9KAvrF4=
51
-
52
-
53
-=== Final Stats ===
54
-Total attempts: 810086
55
-Total found: 1
56
-Elapsed time: 3.60s
57
-Rate: 225067 attempts/sec
58
-```
59
-
60
-**Note**: Keys are displayed in base64 encoding for readability:
61
-- PrivateKey: 64 bytes (ed25519 seed + public key)
62
-- PublicKey: 32 bytes
63
-
64
-## How It Works
65
-
66
-1. **Random Key Generation**: Each worker generates random ed25519 private keys using `randpool.CSPRNG_RAND`
67
-2. **ID Derivation**: The corresponding ID is derived using `cryptoops.DeriveID` which uses HMAC-SHA256 and base32 encoding
68
-3. **Prefix Matching**: The ID is checked against the desired prefix
69
-4. **ETA Calculation**: Expected completion time is calculated based on:
70
- - Current attempt rate (attempts/sec)
71
- - Remaining results needed
72
- - Mathematical probability (32^n for n character prefix)
73
-5. **Result Collection**: Matching credentials are collected and displayed with their full private/public key pairs
74
-
75
-## Performance Notes
76
-
77
-- The search difficulty increases exponentially with prefix length
78
-- Each additional character multiplies the expected attempts by ~32 (base32 alphabet size)
79
-- Average attempts needed:
80
- - 1 character: ~16 attempts
81
- - 2 characters: ~512 attempts
82
- - 3 characters: ~16,384 attempts
83
- - 4 characters: ~524,288 attempts
84
- - 5 characters: ~16,777,216 attempts
85
-
86
-On a typical 8-core CPU, you can expect:
87
-- ~250,000-300,000 attempts/second
88
-- 1-2 character prefixes: instant
89
-- 3 character prefixes: < 1 second
90
-- 4 character prefixes: 2-10 seconds
91
-- 5 character prefixes: 1-5 minutes
92
-
93
-## Integration
94
-
95
-The generated credentials can be used with the `cryptoops.Credential` type:
96
-
97
-```go
98
-import (
99
- "crypto/ed25519"
100
- "encoding/base64"
101
- "gosuda.org/portal/portal/core/cryptoops"
102
-)
103
-
104
-// Use the private key from the output (base64 encoded)
105
-privateKeyB64 := "ZIhWbN39MThmbqREW+Ir7PvRxzzcuEVvJlOGwuive1ZL6RMsaBDcOSWj5MzSeyS+uqG8JARUssjODC70oC+sXg=="
106
-privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyB64)
107
-if err != nil {
108
- panic(err)
109
-}
110
-
111
-cred, err := cryptoops.NewCredentialFromPrivateKey(ed25519.PrivateKey(privateKeyBytes))
112
-if err != nil {
113
- panic(err)
114
-}
115
-
116
-// Verify the ID matches
117
-fmt.Println(cred.ID()) // Should print: TESTIWIBIRNDLZOHD3H2D6AD7Q
cmd/vanity-id/main.go
deleted
-182
@@ -1,182 +0,0 @@
1
-package main
2
-
3
-import (
4
- "crypto/ed25519"
5
- "encoding/base64"
6
- "flag"
7
- "fmt"
8
- "math"
9
- "runtime"
10
- "strings"
11
- "sync"
12
- "sync/atomic"
13
- "time"
14
-
15
- "gosuda.org/portal/portal/core/cryptoops"
16
- "gosuda.org/portal/portal/utils/randpool"
17
-)
18
-
19
-func main() {
20
- prefix := flag.String("prefix", "CHAT", "ID prefix to search for")
21
- workers := flag.Int("workers", runtime.NumCPU(), "Number of parallel workers")
22
- maxResults := flag.Int("max", 1, "Maximum number of results to find (0 = unlimited)")
23
- flag.Parse()
24
-
25
- // Convert prefix to uppercase (base32 encoding is uppercase)
26
- *prefix = strings.ToUpper(*prefix)
27
-
28
- // Calculate expected attempts (base32 has 32 characters)
29
- expectedAttempts := math.Pow(32, float64(len(*prefix)))
30
-
31
- fmt.Printf("Searching for IDs with prefix: %s (%d characters)\n", *prefix, len(*prefix))
32
- fmt.Printf("Using %d parallel workers\n", *workers)
33
- fmt.Printf("Max results: %d\n", *maxResults)
34
- fmt.Printf("Expected attempts per result: %.0f (average)\n", expectedAttempts/2)
35
- fmt.Println()
36
-
37
- var (
38
- attempts uint64
39
- found uint64
40
- startTime = time.Now()
41
- results = make(chan *Result, *workers)
42
- wg sync.WaitGroup
43
- ctx = make(chan struct{}) // Context for stopping workers
44
- )
45
-
46
- // Start worker goroutines
47
- for range *workers {
48
- wg.Add(1)
49
- go worker(*prefix, &attempts, &found, results, &wg, *maxResults, ctx)
50
- }
51
-
52
- // Start stats reporter
53
- done := make(chan bool)
54
- go statsReporter(&attempts, &found, startTime, done, len(*prefix), *maxResults)
55
-
56
- // Collect and print results
57
- foundCount := 0
58
- for result := range results {
59
- foundCount++
60
- elapsed := time.Since(startTime)
61
- fmt.Printf("\n[#%d] Found at %.2fs (attempt #%d):\n", foundCount, elapsed.Seconds(), result.Attempt)
62
- fmt.Printf(" ID: %s\n", result.ID)
63
- fmt.Printf(" PrivateKey: %s\n", base64.StdEncoding.EncodeToString(result.PrivateKey))
64
- fmt.Printf(" PublicKey: %s\n", base64.StdEncoding.EncodeToString(result.PublicKey))
65
- fmt.Println()
66
-
67
- // If we've reached max results, signal workers to stop
68
- if *maxResults > 0 && foundCount >= *maxResults {
69
- close(ctx)
70
- // Wait for all workers to finish
71
- go func() {
72
- wg.Wait()
73
- close(results)
74
- }()
75
- }
76
- }
77
-
78
- done <- true
79
- elapsed := time.Since(startTime)
80
- fmt.Printf("\n=== Final Stats ===\n")
81
- fmt.Printf("Total attempts: %d\n", atomic.LoadUint64(&attempts))
82
- fmt.Printf("Total found: %d\n", foundCount)
83
- fmt.Printf("Elapsed time: %.2fs\n", elapsed.Seconds())
84
- fmt.Printf("Rate: %.0f attempts/sec\n", float64(atomic.LoadUint64(&attempts))/elapsed.Seconds())
85
-}
86
-
87
-type Result struct {
88
- ID string
89
- PrivateKey ed25519.PrivateKey
90
- PublicKey ed25519.PublicKey
91
- Attempt uint64
92
-}
93
-
94
-func worker(prefix string, attempts, found *uint64, results chan<- *Result, wg *sync.WaitGroup, maxResults int, ctx <-chan struct{}) {
95
- defer wg.Done()
96
-
97
- var seed [32]byte
98
-
99
- for {
100
- // Check if we should stop
101
- select {
102
- case <-ctx:
103
- return
104
- default:
105
- }
106
-
107
- // Generate random seed using randpool
108
- randpool.Rand(seed[:])
109
-
110
- // Generate private key from seed (this is 64 bytes: 32 byte seed + 32 byte public key)
111
- privateKey := ed25519.NewKeyFromSeed(seed[:])
112
-
113
- // Extract public key (last 32 bytes of private key)
114
- publicKey := ed25519.PublicKey(privateKey[32:])
115
-
116
- // Derive ID
117
- id := cryptoops.DeriveID(publicKey)
118
-
119
- // Increment attempts counter
120
- attemptNum := atomic.AddUint64(attempts, 1)
121
-
122
- // Check if ID starts with the desired prefix
123
- if strings.HasPrefix(id, prefix) {
124
- // Increment found counter
125
- atomic.AddUint64(found, 1)
126
-
127
- // Try to send result, but return if context is closed
128
- select {
129
- case results <- &Result{
130
- ID: id,
131
- PrivateKey: privateKey,
132
- PublicKey: publicKey,
133
- Attempt: attemptNum,
134
- }:
135
- case <-ctx:
136
- return
137
- }
138
- }
139
- }
140
-}
141
-
142
-func statsReporter(attempts, found *uint64, startTime time.Time, done <-chan bool, prefixLen int, maxResults int) {
143
- ticker := time.NewTicker(2 * time.Second)
144
- defer ticker.Stop()
145
-
146
- // Calculate expected attempts per result
147
- expectedAttemptsPerResult := math.Pow(32, float64(prefixLen)) / 2
148
-
149
- for {
150
- select {
151
- case <-ticker.C:
152
- elapsed := time.Since(startTime)
153
- a := atomic.LoadUint64(attempts)
154
- f := atomic.LoadUint64(found)
155
- rate := float64(a) / elapsed.Seconds()
156
-
157
- // Calculate estimated time to completion
158
- var etaStr string
159
- if rate > 0 && maxResults > 0 {
160
- remainingResults := maxResults - int(f)
161
- if remainingResults > 0 {
162
- expectedRemainingAttempts := float64(remainingResults) * expectedAttemptsPerResult
163
- etaSeconds := expectedRemainingAttempts / rate
164
-
165
- if etaSeconds < 60 {
166
- etaStr = fmt.Sprintf(" | ETA: %.0fs", etaSeconds)
167
- } else if etaSeconds < 3600 {
168
- etaStr = fmt.Sprintf(" | ETA: %.1fm", etaSeconds/60)
169
- } else {
170
- etaStr = fmt.Sprintf(" | ETA: %.1fh", etaSeconds/3600)
171
- }
172
- }
173
- }
174
-
175
- fmt.Printf("\r[Stats] Attempts: %d | Found: %d | Rate: %.0f/sec | Elapsed: %.1fs%s",
176
- a, f, rate, elapsed.Seconds(), etaStr)
177
- case <-done:
178
- fmt.Println() // New line after final stats
179
- return
180
- }
181
- }
182
-}
cmd/webclient/httpjs/http_js.go
deleted
-526
@@ -1,526 +0,0 @@
1
-package httpjs
2
-
3
-import (
4
- "bufio"
5
- "bytes"
6
- "errors"
7
- "io"
8
- "net/http"
9
- "net/textproto"
10
- "strings"
11
- "syscall/js"
12
-
13
- "gosuda.org/portal/cmd/webclient/streamjs"
14
-)
15
-
16
-var (
17
- ErrRequestFailed = errors.New("request failed")
18
- ErrAborted = errors.New("request aborted")
19
-)
20
-
21
-var (
22
- _fetch = js.Global().Get("fetch")
23
- _Headers = js.Global().Get("Headers")
24
- _Response = js.Global().Get("Response")
25
- _ArrayBuffer = js.Global().Get("ArrayBuffer")
26
- _Uint8Array = js.Global().Get("Uint8Array")
27
- _Promise = js.Global().Get("Promise")
28
- _Object = js.Global().Get("Object")
29
- _Array = js.Global().Get("Array")
30
- _Error = js.Global().Get("Error")
31
-)
32
-
33
-// Request represents an HTTP request that will be sent via fetch API
34
-type Request struct {
35
- Method string
36
- URL string
37
- Headers map[string]string
38
- Body []byte
39
-}
40
-
41
-// Response represents an HTTP response with streaming body support
42
-type Response struct {
43
- StatusCode int
44
- Headers map[string]string
45
- Body *streamjs.ReadableStream
46
-
47
- jsResponse js.Value
48
- bodyReader io.ReadCloser // Store the underlying reader for ReadAll
49
-}
50
-
51
-// NewRequest creates a new HTTP request
52
-func NewRequest(method, url string) *Request {
53
- return &Request{
54
- Method: method,
55
- URL: url,
56
- Headers: make(map[string]string),
57
- }
58
-}
59
-
60
-// SetHeader sets a request header
61
-func (r *Request) SetHeader(key, value string) {
62
- r.Headers[key] = value
63
-}
64
-
65
-// SetBody sets the request body from a byte slice
66
-func (r *Request) SetBody(body []byte) {
67
- r.Body = body
68
-}
69
-
70
-// Do executes the HTTP request and returns a Response
71
-func (r *Request) Do() (*Response, error) {
72
- // Create fetch options
73
- opts := _Object.New()
74
- opts.Set("method", r.Method)
75
-
76
- // Set headers
77
- if len(r.Headers) > 0 {
78
- jsHeaders := _Headers.New()
79
- for key, value := range r.Headers {
80
- jsHeaders.Call("append", key, value)
81
- }
82
- opts.Set("headers", jsHeaders)
83
- }
84
-
85
- // Set body if present (convert to ArrayBuffer)
86
- if len(r.Body) > 0 {
87
- buffer := _ArrayBuffer.New(len(r.Body))
88
- array := _Uint8Array.New(buffer)
89
- js.CopyBytesToJS(array, r.Body)
90
- opts.Set("body", buffer)
91
- }
92
-
93
- // Create channels for async result
94
- resultCh := make(chan *Response, 1)
95
- errCh := make(chan error, 1)
96
-
97
- // Execute fetch
98
- fetchPromise := _fetch.Invoke(r.URL, opts)
99
-
100
- // Handle response
101
- var thenFunc, catchFunc js.Func
102
-
103
- thenFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
104
- defer thenFunc.Release()
105
-
106
- jsResp := args[0]
107
-
108
- // Parse response
109
- resp := &Response{
110
- StatusCode: jsResp.Get("status").Int(),
111
- Headers: make(map[string]string),
112
- jsResponse: jsResp,
113
- }
114
-
115
- // Extract headers
116
- jsHeaders := jsResp.Get("headers")
117
- entriesIter := jsHeaders.Call("entries")
118
-
119
- for {
120
- next := entriesIter.Call("next")
121
- if next.Get("done").Bool() {
122
- break
123
- }
124
- entry := next.Get("value")
125
- key := entry.Index(0).String()
126
- value := entry.Index(1).String()
127
- resp.Headers[key] = value
128
- }
129
-
130
- // Get body as ReadableStream
131
- jsBody := jsResp.Get("body")
132
- if !jsBody.IsNull() && !jsBody.IsUndefined() {
133
- // Create a Go reader that reads from JS ReadableStream
134
- reader := &jsStreamReader{
135
- jsReader: jsBody.Call("getReader"),
136
- }
137
- resp.bodyReader = reader
138
- resp.Body = streamjs.NewReadableStream(reader)
139
- }
140
-
141
- resultCh <- resp
142
- return nil
143
- })
144
-
145
- catchFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
146
- defer catchFunc.Release()
147
-
148
- if len(args) > 0 {
149
- errMsg := args[0].Get("message").String()
150
- errCh <- errors.New(errMsg)
151
- } else {
152
- errCh <- ErrRequestFailed
153
- }
154
- return nil
155
- })
156
-
157
- fetchPromise.Call("then", thenFunc).Call("catch", catchFunc)
158
-
159
- // Wait for result
160
- select {
161
- case resp := <-resultCh:
162
- return resp, nil
163
- case err := <-errCh:
164
- return nil, err
165
- }
166
-}
167
-
168
-// jsStreamReader implements io.ReadCloser by reading from a JS ReadableStream
169
-type jsStreamReader struct {
170
- jsReader js.Value
171
- closed bool
172
-}
173
-
174
-func (r *jsStreamReader) Read(p []byte) (n int, err error) {
175
- if r.closed {
176
- return 0, io.EOF
177
- }
178
-
179
- // Create channels for async read
180
- resultCh := make(chan readResult, 1)
181
-
182
- // Call read() on the reader
183
- readPromise := r.jsReader.Call("read")
184
-
185
- var thenFunc js.Func
186
- thenFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
187
- defer thenFunc.Release()
188
-
189
- result := args[0]
190
- done := result.Get("done").Bool()
191
-
192
- if done {
193
- resultCh <- readResult{n: 0, err: io.EOF}
194
- return nil
195
- }
196
-
197
- // Get the chunk (Uint8Array)
198
- chunk := result.Get("value")
199
- if chunk.IsNull() || chunk.IsUndefined() {
200
- resultCh <- readResult{n: 0, err: nil}
201
- return nil
202
- }
203
-
204
- // Copy data from JS to Go
205
- length := chunk.Get("byteLength").Int()
206
- if length == 0 {
207
- resultCh <- readResult{n: 0, err: nil}
208
- return nil
209
- }
210
-
211
- // Copy as much as we can fit in p
212
- copyLen := length
213
- if copyLen > len(p) {
214
- copyLen = len(p)
215
- }
216
-
217
- // Create a temporary Uint8Array view if we need to copy partial data
218
- if copyLen < length {
219
- chunk = _Uint8Array.New(chunk.Get("buffer"), chunk.Get("byteOffset"), copyLen)
220
- }
221
-
222
- js.CopyBytesToGo(p[:copyLen], chunk)
223
- resultCh <- readResult{n: copyLen, err: nil}
224
- return nil
225
- })
226
-
227
- readPromise.Call("then", thenFunc)
228
-
229
- // Wait for result
230
- res := <-resultCh
231
- return res.n, res.err
232
-}
233
-
234
-func (r *jsStreamReader) Close() error {
235
- if r.closed {
236
- return nil
237
- }
238
- r.closed = true
239
-
240
- // Cancel the reader
241
- if !r.jsReader.IsNull() && !r.jsReader.IsUndefined() {
242
- r.jsReader.Call("cancel")
243
- }
244
- return nil
245
-}
246
-
247
-type readResult struct {
248
- n int
249
- err error
250
-}
251
-
252
-// ReadAll reads the entire response body into a byte slice
253
-func (resp *Response) ReadAll() ([]byte, error) {
254
- if resp.bodyReader == nil {
255
- return []byte{}, nil
256
- }
257
-
258
- var buf bytes.Buffer
259
- buffer := make([]byte, 32*1024) // 32KB buffer to reduce Go-JS boundary crossings
260
-
261
- for {
262
- n, err := resp.bodyReader.Read(buffer)
263
- if n > 0 {
264
- buf.Write(buffer[:n])
265
- }
266
- if err == io.EOF {
267
- break
268
- }
269
- if err != nil {
270
- return nil, err
271
- }
272
- }
273
-
274
- return buf.Bytes(), nil
275
-}
276
-
277
-// Close closes the response body stream
278
-func (resp *Response) Close() error {
279
- if resp.Body != nil {
280
- resp.Body.Close()
281
- }
282
- return nil
283
-}
284
-
285
-// Get performs a GET request
286
-func Get(url string) (*Response, error) {
287
- req := NewRequest("GET", url)
288
- return req.Do()
289
-}
290
-
291
-// Post performs a POST request with the given body
292
-func Post(url string, contentType string, body []byte) (*Response, error) {
293
- req := NewRequest("POST", url)
294
- if contentType != "" {
295
- req.SetHeader("Content-Type", contentType)
296
- }
297
- req.SetBody(body)
298
- return req.Do()
299
-}
300
-
301
-// Put performs a PUT request with the given body
302
-func Put(url string, contentType string, body []byte) (*Response, error) {
303
- req := NewRequest("PUT", url)
304
- if contentType != "" {
305
- req.SetHeader("Content-Type", contentType)
306
- }
307
- req.SetBody(body)
308
- return req.Do()
309
-}
310
-
311
-// Delete performs a DELETE request
312
-func Delete(url string) (*Response, error) {
313
- req := NewRequest("DELETE", url)
314
- return req.Do()
315
-}
316
-
317
-// JSRequestToHTTPRequest converts a JavaScript Request object to net/http.Request
318
-func JSRequestToHTTPRequest(jsReq js.Value) (*http.Request, error) {
319
- // Get method and URL
320
- method := jsReq.Get("method").String()
321
- url := jsReq.Get("url").String()
322
-
323
- // Read body as ArrayBuffer
324
- var bodyReader io.Reader
325
- jsBody := jsReq.Get("body")
326
-
327
- if !jsBody.IsNull() && !jsBody.IsUndefined() {
328
- // Create promise to read body
329
- bodyPromise := jsReq.Call("arrayBuffer")
330
-
331
- bodyChan := make(chan []byte, 1)
332
- errChan := make(chan error, 1)
333
-
334
- var successFunc, failFunc js.Func
335
- successFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
336
- defer successFunc.Release()
337
-
338
- jsBodyArray := _Uint8Array.New(args[0])
339
- bodyBuffer := make([]byte, jsBodyArray.Get("byteLength").Int())
340
- js.CopyBytesToGo(bodyBuffer, jsBodyArray)
341
- bodyChan <- bodyBuffer
342
- return nil
343
- })
344
-
345
- failFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
346
- defer failFunc.Release()
347
-
348
- if len(args) > 0 {
349
- errChan <- errors.New(args[0].String())
350
- } else {
351
- errChan <- errors.New("failed to read request body")
352
- }
353
- return nil
354
- })
355
-
356
- bodyPromise.Call("then", successFunc).Call("catch", failFunc)
357
-
358
- select {
359
- case body := <-bodyChan:
360
- bodyReader = bytes.NewReader(body)
361
- case err := <-errChan:
362
- return nil, err
363
- }
364
- } else {
365
- bodyReader = bytes.NewReader([]byte{})
366
- }
367
-
368
- // Create HTTP request
369
- httpReq, err := http.NewRequest(method, url, bodyReader)
370
- if err != nil {
371
- return nil, err
372
- }
373
-
374
- // Convert headers
375
- jsHeaders := _Array.Call("from", jsReq.Get("headers").Call("entries"))
376
- headersLen := jsHeaders.Length()
377
-
378
- var headerBuilder strings.Builder
379
- for i := 0; i < headersLen; i++ {
380
- entry := jsHeaders.Index(i)
381
- if entry.Length() < 2 {
382
- continue
383
- }
384
-
385
- key := entry.Index(0).String()
386
- value := entry.Index(1).String()
387
-
388
- headerBuilder.WriteString(key)
389
- headerBuilder.WriteString(": ")
390
- headerBuilder.WriteString(value)
391
- headerBuilder.WriteString("\r\n")
392
- }
393
- headerBuilder.WriteString("\r\n")
394
-
395
- // Parse headers using textproto
396
- tpr := textproto.NewReader(bufio.NewReader(strings.NewReader(headerBuilder.String())))
397
- mimeHeader, err := tpr.ReadMIMEHeader()
398
- if err != nil {
399
- return nil, err
400
- }
401
- httpReq.Header = http.Header(mimeHeader)
402
-
403
- return httpReq, nil
404
-}
405
-
406
-// HTTPResponseToJSResponse converts an http.Response to a JavaScript Response object with streaming support
407
-func HTTPResponseToJSResponse(httpResp *http.Response) js.Value {
408
- // Create JS headers object
409
- jsHeaders := _Object.New()
410
-
411
- for key, values := range httpResp.Header {
412
- for _, value := range values {
413
- jsHeaders.Call("append", key, value)
414
- }
415
- }
416
-
417
- // Create streaming body using ReadableStream
418
- var jsBody js.Value
419
- if httpResp.Body != nil {
420
- stream := streamjs.NewReadableStream(httpResp.Body)
421
- jsBody = stream.Value
422
- } else {
423
- jsBody = js.Null()
424
- }
425
-
426
- // Create response options
427
- jsOptions := _Object.New()
428
- jsOptions.Set("status", httpResp.StatusCode)
429
- jsOptions.Set("statusText", httpResp.Status)
430
- jsOptions.Set("headers", jsHeaders)
431
-
432
- // Create and return JS Response
433
- jsResp := _Response.New(jsBody, jsOptions)
434
- return jsResp
435
-}
436
-
437
-// ServeHTTPAsyncWithStreaming handles an HTTP request asynchronously and returns a streaming JS Response
438
-func ServeHTTPAsyncWithStreaming(handler http.Handler, jsReq js.Value) js.Value {
439
- return _Promise.New(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
440
- resolve := args[0]
441
- reject := args[1]
442
-
443
- go func() {
444
- // Convert JS Request to http.Request
445
- httpReq, err := JSRequestToHTTPRequest(jsReq)
446
- if err != nil {
447
- reject.Invoke(_Error.New(err.Error()))
448
- return
449
- }
450
-
451
- // Create a pipe for streaming response
452
- pr, pw := io.Pipe()
453
-
454
- // Create custom ResponseWriter that writes to pipe
455
- respWriter := &streamingResponseWriter{
456
- pipeWriter: pw,
457
- header: make(http.Header),
458
- statusCode: 200,
459
- wroteHeaderChan: make(chan struct{}, 1),
460
- }
461
-
462
- // Serve HTTP in goroutine
463
- go func() {
464
- defer pw.Close()
465
- defer func() {
466
- if r := recover(); r != nil {
467
- // Handle panic
468
- respWriter.statusCode = http.StatusInternalServerError
469
- pw.CloseWithError(errors.New("internal server error"))
470
- }
471
- }()
472
-
473
- handler.ServeHTTP(respWriter, httpReq)
474
-
475
- if !respWriter.wroteHeader {
476
- respWriter.WriteHeader(http.StatusBadGateway)
477
- http.Error(respWriter, "Bad Gateway\n\nUpstream server error", http.StatusBadGateway)
478
- }
479
- }()
480
-
481
- <-respWriter.wroteHeaderChan
482
-
483
- // Create http.Response with streaming body
484
- httpResp := &http.Response{
485
- StatusCode: respWriter.statusCode,
486
- Status: http.StatusText(respWriter.statusCode),
487
- Header: respWriter.header,
488
- Body: pr,
489
- }
490
-
491
- // Convert to JS Response with streaming
492
- jsResp := HTTPResponseToJSResponse(httpResp)
493
- resolve.Invoke(jsResp)
494
- }()
495
-
496
- return nil
497
- }))
498
-}
499
-
500
-// streamingResponseWriter implements http.ResponseWriter for streaming responses
501
-type streamingResponseWriter struct {
502
- pipeWriter *io.PipeWriter
503
- header http.Header
504
- statusCode int
505
- wroteHeader bool
506
- wroteHeaderChan chan struct{}
507
-}
508
-
509
-func (w *streamingResponseWriter) Header() http.Header {
510
- return w.header
511
-}
512
-
513
-func (w *streamingResponseWriter) Write(b []byte) (int, error) {
514
- if !w.wroteHeader {
515
- w.WriteHeader(http.StatusOK)
516
- }
517
- return w.pipeWriter.Write(b)
518
-}
519
-
520
-func (w *streamingResponseWriter) WriteHeader(statusCode int) {
521
- if !w.wroteHeader {
522
- w.statusCode = statusCode
523
- w.wroteHeader = true
524
- close(w.wroteHeaderChan)
525
- }
526
-}
cmd/webclient/index.html
deleted
-434
@@ -1,434 +0,0 @@
1
-<!DOCTYPE html>
2
-<html>
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Portal Proxy Gateway</title>
7
- <meta property="og:title" content="[%OG_TITLE%]" />
8
- <meta property="og:description" content="[%OG_DESCRIPTION%]" />
9
- <meta property="og:image" content="[%OG_IMAGE_URL%]" />
10
- <meta name="twitter:card" content="summary_large_image" />
11
- <meta name="twitter:title" content="[%OG_TITLE%]" />
12
- <meta name="twitter:description" content="[%OG_DESCRIPTION%]" />
13
- <meta name="twitter:image" content="[%OG_IMAGE_URL%]" />
14
-
15
- <style>
16
- body {
17
- margin: 0;
18
- padding: 0;
19
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
20
- sans-serif;
21
- background: #000;
22
- }
23
-
24
- #loading-screen {
25
- position: fixed;
26
- top: 0;
27
- left: 0;
28
- width: 100%;
29
- height: 100%;
30
- background: #000;
31
- display: flex;
32
- flex-direction: column;
33
- justify-content: center;
34
- align-items: center;
35
- z-index: 9999;
36
- transition: opacity 0.5s ease-out;
37
- }
38
-
39
- #loading-screen.hide {
40
- opacity: 0;
41
- pointer-events: none;
42
- }
43
-
44
- #loading-logo {
45
- position: relative;
46
- max-width: 500px;
47
- width: 90%;
48
- margin-bottom: 40px;
49
- /* 컨테이너 쿼리를 위한 설정 */
50
- container-type: inline-size;
51
- }
52
-
53
- #loading-logo video {
54
- width: 100%;
55
- height: auto;
56
- display: block;
57
- }
58
-
59
- .logo-overlay {
60
- position: absolute;
61
- top: 0;
62
- left: 0;
63
- width: 100%;
64
- height: 100%;
65
- display: flex;
66
- flex-direction: column;
67
- justify-content: space-between;
68
- align-items: center;
69
- padding: 10% 1%;
70
- box-sizing: border-box;
71
- pointer-events: none;
72
- }
73
-
74
- .logo-title {
75
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
76
- sans-serif;
77
- /* 51.5px at 500px container = 51.5/500*100 = 10.3cqw */
78
- font-size: 10.3cqw;
79
- font-weight: 600;
80
- color: #ffffff;
81
- letter-spacing: 0.2em;
82
- text-align: center;
83
- text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
84
- margin: 0;
85
- }
86
-
87
- .logo-subtitle {
88
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
89
- sans-serif;
90
- /* 18px at 500px container = 18/500*100 = 3.6cqw */
91
- font-size: 3.6cqw;
92
- font-weight: 600;
93
- color: #ffffff;
94
- letter-spacing: -0.05em;
95
- text-align: center;
96
- text-shadow: 0 0 15px rgba(255, 255, 255, 0.4);
97
- margin: 0;
98
- }
99
-
100
- .loading-bar-container {
101
- width: 300px;
102
- max-width: 80%;
103
- height: 4px;
104
- background: rgba(255, 255, 255, 0.1);
105
- border-radius: 2px;
106
- overflow: hidden;
107
- position: relative;
108
- }
109
-
110
- .loading-bar {
111
- height: 100%;
112
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
113
- border-radius: 2px;
114
- animation: loading 1.5s ease-in-out infinite;
115
- }
116
-
117
- @keyframes loading {
118
- 0% {
119
- width: 0%;
120
- margin-left: 0%;
121
- }
122
-
123
- 50% {
124
- width: 50%;
125
- margin-left: 25%;
126
- }
127
-
128
- 100% {
129
- width: 0%;
130
- margin-left: 100%;
131
- }
132
- }
133
-
134
- .loading-text {
135
- color: rgba(255, 255, 255, 0.7);
136
- margin-top: 20px;
137
- font-size: 14px;
138
- text-align: center;
139
- transition: color 0.3s ease;
140
- }
141
-
142
- .loading-text.error {
143
- color: #ef4444;
144
- }
145
- </style>
146
- </head>
147
-
148
- <body>
149
- <!-- Loading Screen -->
150
- <div id="loading-screen">
151
- <div id="loading-logo">
152
- <video autoplay loop muted playsinline>
153
- <source src="/portal.mp4" type="video/mp4" />
154
- </video>
155
- <div class="logo-overlay">
156
- <h1 class="logo-title">PORTAL</h1>
157
- <p class="logo-subtitle">LOCAL TO WEB. INSTANT ACCESS.</p>
158
- </div>
159
- </div>
160
- <div class="loading-bar-container" id="loading-bar-container">
161
- <div class="loading-bar"></div>
162
- </div>
163
- <div class="loading-text" id="loading-text">
164
- Initializing Portal Network...
165
- </div>
166
- </div>
167
-
168
- <script>
169
- // In-app browser detection and redirect handler
170
- (function () {
171
- const userAgent = navigator.userAgent.toLowerCase();
172
- const finalUrl = window.location.href;
173
-
174
- // Detect various in-app browsers
175
- const isKakao = /kakaotalk/i.test(userAgent);
176
- const isNaver = /naver/i.test(userAgent);
177
- const isFacebook = /fb|fbav|fban/i.test(userAgent);
178
- const isInstagram = /instagram/i.test(userAgent);
179
- const isLine = /line/i.test(userAgent);
180
- const isInAppBrowser =
181
- isKakao || isNaver || isFacebook || isInstagram || isLine;
182
- const isAndroid = /android/.test(userAgent);
183
- const isIOS = /ipad|iphone|ipod/.test(userAgent);
184
-
185
- if (!isInAppBrowser) {
186
- return; // Not in-app browser, proceed normally
187
- }
188
-
189
- // Handle redirection to external browser
190
- if (isAndroid) {
191
- if (isKakao) {
192
- location.href =
193
- "kakaotalk://web/openExternal?url=" +
194
- encodeURIComponent(finalUrl);
195
- setTimeout(() => {
196
- location.href = "kakaotalk://inappbrowser/close";
197
- }, 10);
198
- } else {
199
- // Use Intent to open in Chrome or default browser
200
- const intentUrl =
201
- "intent://" +
202
- finalUrl.replace(/^https?:\/\//, "") +
203
- "#Intent;scheme=https;action=android.intent.action.VIEW;end";
204
- location.href = intentUrl;
205
- }
206
- return; // Stop execution
207
- } else if (isIOS) {
208
- if (isKakao) {
209
- location.href =
210
- "kakaotalk://web/openExternal?url=" +
211
- encodeURIComponent(finalUrl);
212
- // Set up auto-close listener for iOS KakaoTalk
213
- document.addEventListener("visibilitychange", () => {
214
- if (document.visibilityState == "visible") {
215
- location.href = "kakaoweb://closeBrowser";
216
- }
217
- });
218
- } else {
219
- // For other iOS in-app browsers, show message in loading screen
220
- // updateLoadingText("⚠️ Please open in external browser (Safari)");
221
- // document.getElementById("loading-text").classList.add("error");
222
- }
223
- return; // Stop execution
224
- }
225
- })();
226
- </script>
227
- <script>
228
- // Update loading text
229
- function updateLoadingText(message, isError = false) {
230
- const loadingText = document.getElementById("loading-text");
231
- if (loadingText) {
232
- loadingText.textContent = message;
233
- if (isError) {
234
- loadingText.classList.add("error");
235
- } else {
236
- loadingText.classList.remove("error");
237
- }
238
- }
239
- }
240
-
241
- // Show error in loading text
242
- function showError(error, context = "") {
243
- let errorMessage = "";
244
- if (typeof error === "string") {
245
- errorMessage = error;
246
- } else if (error instanceof Error) {
247
- errorMessage = `${error.message}`;
248
- } else {
249
- errorMessage = "An error occurred";
250
- }
251
-
252
- if (context) {
253
- errorMessage = `⚠️ ${context}: ${errorMessage}`;
254
- } else {
255
- errorMessage = `⚠️ ${errorMessage}`;
256
- }
257
-
258
- updateLoadingText(errorMessage, true);
259
- console.error(`[Portal Error - ${context}]`, error);
260
- }
261
-
262
- // Global error handler
263
- window.addEventListener("error", (event) => {
264
- showError(event.error || event.message, "Error");
265
- event.preventDefault();
266
- });
267
-
268
- // Unhandled promise rejection handler
269
- window.addEventListener("unhandledrejection", (event) => {
270
- showError(event.reason, "Promise Error");
271
- event.preventDefault();
272
- });
273
-
274
- // Listen for errors from Service Worker
275
- if ("serviceWorker" in navigator) {
276
- navigator.serviceWorker.addEventListener("message", (event) => {
277
- if (event.data && event.data.type === "SW_ERROR") {
278
- const error = event.data.error;
279
- showError(error.message, "Service Worker Error");
280
- }
281
- });
282
- }
283
-
284
- async function registerServiceWorker() {
285
- let retryCount = 0;
286
- const maxRetries = 30; // 3초 (30 × 100ms)
287
-
288
- const checkWASMReady = async () => {
289
- try {
290
- const resp = await fetch("/e8c2c70c-ec4a-40b2-b8af-d5638264f831", {
291
- cache: "no-store",
292
- });
293
- const text = await resp.text();
294
-
295
- if (text === "ACK-e8c2c70c-ec4a-40b2-b8af-d5638264f831") {
296
- updateLoadingText("Portal Network Ready!");
297
- setTimeout(() => {
298
- window.location.reload();
299
- }, 500);
300
- } else if (text === "NAK-e8c2c70c-ec4a-40b2-b8af-d5638264f831") {
301
- retryCount++;
302
- if (retryCount > maxRetries) {
303
- throw new Error(
304
- `WASM initialization timeout after ${maxRetries} retries`
305
- );
306
- }
307
- updateLoadingText(
308
- `Initializing WASM... (${retryCount}/${maxRetries})`
309
- );
310
- setTimeout(checkWASMReady, 100);
311
- } else {
312
- if (text.includes("<!DOCTYPE") || text.includes("<html>")) {
313
- throw new Error("Service Worker not active - please refresh");
314
- } else {
315
- throw new Error(
316
- `Unexpected response: ${text.substring(0, 50)}`
317
- );
318
- }
319
- }
320
- } catch (error) {
321
- showError(error, "Connection");
322
- }
323
- };
324
-
325
- const waitForController = () => {
326
- return new Promise((resolve, reject) => {
327
- const checkController = () => {
328
- if (navigator.serviceWorker.controller) {
329
- resolve();
330
- return true;
331
- }
332
- return false;
333
- };
334
-
335
- if (checkController()) {
336
- return;
337
- }
338
-
339
- let timeoutId;
340
- let pollIntervalId;
341
-
342
- const onControllerChange = () => {
343
- if (checkController()) {
344
- clearTimeout(timeoutId);
345
- clearInterval(pollIntervalId);
346
- navigator.serviceWorker.removeEventListener(
347
- "controllerchange",
348
- onControllerChange
349
- );
350
- }
351
- };
352
-
353
- navigator.serviceWorker.addEventListener(
354
- "controllerchange",
355
- onControllerChange
356
- );
357
-
358
- pollIntervalId = setInterval(() => {
359
- if (checkController()) {
360
- clearTimeout(timeoutId);
361
- clearInterval(pollIntervalId);
362
- navigator.serviceWorker.removeEventListener(
363
- "controllerchange",
364
- onControllerChange
365
- );
366
- }
367
- }, 100);
368
-
369
- timeoutId = setTimeout(() => {
370
- clearInterval(pollIntervalId);
371
- navigator.serviceWorker.removeEventListener(
372
- "controllerchange",
373
- onControllerChange
374
- );
375
-
376
- if (navigator.serviceWorker.controller) {
377
- resolve();
378
- } else {
379
- reject(new Error("Service Worker activation timeout"));
380
- setTimeout(() => {
381
- location.reload();
382
- }, 500);
383
- }
384
- }, 3000);
385
- });
386
- };
387
-
388
- try {
389
- if (!("serviceWorker" in navigator)) {
390
- throw new Error("Service Worker not supported in this browser");
391
- }
392
-
393
- updateLoadingText("Registering Service Worker...");
394
-
395
- const registration = await navigator.serviceWorker.register(
396
- "/service-worker.js",
397
- {
398
- scope: "/",
399
- updateViaCache: "none",
400
- }
401
- );
402
-
403
- if (!navigator.serviceWorker.controller && registration.active) {
404
- registration.active.postMessage({ type: "CLAIM_CLIENTS" });
405
- }
406
-
407
- updateLoadingText("Activating Service Worker...");
408
-
409
- await navigator.serviceWorker.ready;
410
-
411
- if (!navigator.serviceWorker.controller) {
412
- updateLoadingText("Waiting for activation...");
413
- try {
414
- await Promise.race([
415
- waitForController(),
416
- new Promise((resolve) => setTimeout(resolve, 500)),
417
- ]);
418
- } catch (error) {
419
- // Ignore timeout, proceed anyway
420
- }
421
- }
422
-
423
- updateLoadingText("Connecting to Portal Network...");
424
-
425
- setTimeout(checkWASMReady, 100);
426
- } catch (error) {
427
- showError(error, "Initialization");
428
- }
429
- }
430
-
431
- registerServiceWorker();
432
- </script>
433
- </body>
434
-</html>
cmd/webclient/inject.go
deleted
-82
@@ -1,82 +0,0 @@
1
-//go:build !prod
2
-
3
-package main
4
-
5
-import (
6
- "bytes"
7
-
8
- "github.com/rs/zerolog/log"
9
- "golang.org/x/net/html"
10
-
11
- _ "embed"
12
-)
13
-
14
-//go:embed polyfill.js
15
-var polyfillJS []byte
16
-
17
-func InjectHTML(body []byte) []byte {
18
- doc, err := html.Parse(bytes.NewReader(body))
19
- if err != nil {
20
- log.Error().Err(err).Msg("Failed to parse HTML")
21
- return body
22
- }
23
-
24
- // Find the head or body element
25
- var head *html.Node
26
- var bodyNode *html.Node
27
- var crawler func(*html.Node)
28
- crawler = func(node *html.Node) {
29
- if node.Type == html.ElementNode {
30
- switch node.Data {
31
- case "head":
32
- head = node
33
- case "body":
34
- bodyNode = node
35
- }
36
- }
37
- for child := node.FirstChild; child != nil; child = child.NextSibling {
38
- crawler(child)
39
- }
40
- }
41
- crawler(doc)
42
-
43
- // Create script element
44
- script := &html.Node{
45
- Type: html.ElementNode,
46
- Data: "script",
47
- Attr: []html.Attribute{},
48
- }
49
-
50
- // Add the script content
51
- scriptContent := &html.Node{
52
- Type: html.TextNode,
53
- Data: string(polyfillJS),
54
- }
55
- script.AppendChild(scriptContent)
56
-
57
- // Inject into head if available, otherwise into body
58
- if head != nil {
59
- // Insert as the first child of head
60
- if head.FirstChild != nil {
61
- head.InsertBefore(script, head.FirstChild)
62
- } else {
63
- head.AppendChild(script)
64
- }
65
- } else if bodyNode != nil {
66
- // Insert as the first child of body if head doesn't exist
67
- if bodyNode.FirstChild != nil {
68
- bodyNode.InsertBefore(script, bodyNode.FirstChild)
69
- } else {
70
- bodyNode.AppendChild(script)
71
- }
72
- }
73
-
74
- // Convert back to bytes
75
- var buf bytes.Buffer
76
- if err := html.Render(&buf, doc); err != nil {
77
- log.Error().Err(err).Msg("Failed to render HTML")
78
- return body
79
- }
80
-
81
- return buf.Bytes()
82
-}
cmd/webclient/main_js.go
deleted
-1002
@@ -1,1002 +0,0 @@
1
-//go:build !prod
2
-
3
-package main
4
-
5
-import (
6
- "context"
7
- "crypto/rand"
8
- "encoding/base64"
9
- "encoding/hex"
10
- "encoding/json"
11
- "fmt"
12
- "io"
13
- "net"
14
- "net/http"
15
- "net/url"
16
- "os"
17
- "runtime"
18
- "strings"
19
- "sync"
20
- "syscall/js"
21
- "time"
22
-
23
- "github.com/gorilla/websocket"
24
- "github.com/rs/zerolog"
25
- "github.com/rs/zerolog/log"
26
- "golang.org/x/net/idna"
27
- "gosuda.org/portal/cmd/webclient/httpjs"
28
- "gosuda.org/portal/portal/core/cryptoops"
29
- "gosuda.org/portal/sdk"
30
- "gosuda.org/portal/utils"
31
-)
32
-
33
-var (
34
- client *sdk.Client
35
-
36
- // SDK connection manager for Service Worker messaging
37
- sdkConnections = make(map[string]io.ReadWriteCloser)
38
- sdkConnectionsMu sync.RWMutex
39
-
40
- // Reusable credential for HTTP connections (enables Keep-Alive)
41
- dialerCredential *cryptoops.Credential
42
-
43
- // DNS cache for lease name -> lease ID mapping
44
- dnsCache sync.Map // map[string]*dnsCacheEntry
45
- dnsCacheTTL = 5 * time.Minute
46
-)
47
-
48
-type dnsCacheEntry struct {
49
- leaseID string
50
- expiresAt time.Time
51
-}
52
-
53
-// getBootstrapServers retrieves bootstrap servers from global JavaScript variable
54
-func getBootstrapServers() []string {
55
- // Try to get bootstrap servers from window.__BOOTSTRAP_SERVERS__
56
- bootstrapsValue := js.Global().Get("__BOOTSTRAP_SERVERS__")
57
-
58
- if bootstrapsValue.IsUndefined() || bootstrapsValue.IsNull() {
59
- log.Warn().Msg("__BOOTSTRAP_SERVERS__ not found in global scope, using default")
60
- return []string{"ws://localhost:4017/relay"}
61
- }
62
-
63
- // Handle string (comma-separated)
64
- if bootstrapsValue.Type() == js.TypeString {
65
- bootstrapsStr := bootstrapsValue.String()
66
- if bootstrapsStr == "" {
67
- return []string{"ws://localhost:4017/relay"}
68
- }
69
- servers := strings.Split(bootstrapsStr, ",")
70
- for i := range servers {
71
- servers[i] = strings.TrimSpace(servers[i])
72
- }
73
- return servers
74
- }
75
-
76
- // Handle array
77
- if bootstrapsValue.Type() == js.TypeObject && bootstrapsValue.Length() > 0 {
78
- servers := make([]string, bootstrapsValue.Length())
79
- for i := 0; i < bootstrapsValue.Length(); i++ {
80
- servers[i] = bootstrapsValue.Index(i).String()
81
- }
82
- return servers
83
- }
84
-
85
- log.Warn().Msg("Invalid __BOOTSTRAP_SERVERS__ format, using default")
86
- return []string{"ws://localhost:4017/relay"}
87
-}
88
-
89
-// lookupDNSCache checks the DNS cache for a cached lease ID
90
-func lookupDNSCache(name string) (string, bool) {
91
- if entry, ok := dnsCache.Load(name); ok {
92
- cached := entry.(*dnsCacheEntry)
93
- if time.Now().Before(cached.expiresAt) {
94
- return cached.leaseID, true
95
- }
96
- // Expired entry, delete it
97
- dnsCache.Delete(name)
98
- }
99
- return "", false
100
-}
101
-
102
-// storeDNSCache stores a lease ID in the DNS cache
103
-func storeDNSCache(name, leaseID string) {
104
- dnsCache.Store(name, &dnsCacheEntry{
105
- leaseID: leaseID,
106
- expiresAt: time.Now().Add(dnsCacheTTL),
107
- })
108
-}
109
-
110
-// isValidUpgradeRequest validates that the upgrade request is a well-formed HTTP WebSocket upgrade
111
-func isValidUpgradeRequest(req []byte) bool {
112
- if len(req) < 20 { // Minimum: "GET / HTTP/1.1\r\n\r\n"
113
- return false
114
- }
115
- s := string(req)
116
- // Must start with GET and end with double CRLF
117
- if !strings.HasPrefix(s, "GET ") {
118
- return false
119
- }
120
- if !strings.HasSuffix(s, "\r\n\r\n") {
121
- return false
122
- }
123
- // Must contain Upgrade header (case-insensitive check)
124
- if !strings.Contains(strings.ToLower(s), "upgrade:") {
125
- return false
126
- }
127
- return true
128
-}
129
-
130
-var rdDialer = func(ctx context.Context, network, address string) (net.Conn, error) {
131
- originalAddr := address
132
- address = strings.TrimSuffix(address, ":80")
133
- address = strings.TrimSuffix(address, ":443")
134
-
135
- decodedAddr, err := url.QueryUnescape(address)
136
- if err != nil {
137
- log.Debug().Err(err).Str("address", address).Msg("[Dialer] Failed to unescape address")
138
- decodedAddr = address
139
- }
140
- address = decodedAddr
141
-
142
- unicodeAddr, err := idna.ToUnicode(address)
143
- if err != nil {
144
- log.Debug().Err(err).Str("address", address).Msg("[Dialer] Failed to convert punycode")
145
- unicodeAddr = address
146
- } else if unicodeAddr != address {
147
- log.Debug().Str("punycode", address).Str("unicode", unicodeAddr).Msg("[Dialer] Converted punycode to unicode")
148
- }
149
- address = unicodeAddr
150
-
151
- // Check DNS cache first
152
- if cachedID, ok := lookupDNSCache(address); ok {
153
- log.Debug().Str("name", address).Str("id", cachedID).Msg("[Dialer] DNS cache hit")
154
- address = cachedID
155
- } else {
156
- // Cache miss - perform lookup
157
- lease, err := client.LookupName(address)
158
- if err == nil && lease != nil {
159
- leaseID := lease.Identity.Id
160
- log.Debug().Str("name", address).Str("id", leaseID).Msg("[Dialer] Found lease, caching")
161
- storeDNSCache(unicodeAddr, leaseID)
162
- address = leaseID
163
- } else {
164
- log.Debug().Err(err).Str("name", address).Msg("[Dialer] Lease lookup failed")
165
- }
166
- }
167
-
168
- if originalAddr != address {
169
- log.Info().Str("name", unicodeAddr).Str("resolved", address).Msg("[Dialer] Address resolved")
170
- }
171
-
172
- // Use reusable credential to enable HTTP Keep-Alive
173
- conn, err := client.Dial(dialerCredential, address, "http/1.1")
174
- if err != nil {
175
- log.Error().Err(err).Str("address", address).Msg("[Dialer] Dial failed")
176
- return nil, err
177
- }
178
- log.Info().Str("address", address).Msg("[Dialer] Connection Established")
179
-
180
- return conn, nil
181
-}
182
-
183
-var httpClient = &http.Client{
184
- Timeout: time.Second * 30,
185
- Transport: &http.Transport{
186
- MaxIdleConns: 1000,
187
- MaxIdleConnsPerHost: 100,
188
- DialContext: rdDialer,
189
- },
190
-}
191
-
192
-type Proxy struct {
193
- wsManager *WebSocketManager
194
-}
195
-
196
-// WebSocket connection manager
197
-type WebSocketManager struct {
198
- connections sync.Map // map[string]*WSConnection
199
-}
200
-
201
-type WSConnection struct {
202
- id string
203
- conn *websocket.Conn
204
- messageChan chan wsMessage
205
- closeChan chan struct{}
206
- closeOnce sync.Once
207
- mu sync.Mutex
208
- messageQueue []StreamMessage
209
- queueMu sync.Mutex
210
- isClosed bool
211
-}
212
-
213
-type wsMessage struct {
214
- data []byte
215
- isText bool
216
-}
217
-
218
-type ConnectRequest struct {
219
- URL string `json:"url"`
220
- Protocols []string `json:"protocols"`
221
-}
222
-
223
-type ConnectResponse struct {
224
- ConnID string `json:"connId"`
225
- Protocol string `json:"protocol"`
226
-}
227
-
228
-type SendRequest struct {
229
- Type string `json:"type"` // "text", "binary", "close"
230
- Data string `json:"data,omitempty"`
231
- Code int `json:"code,omitempty"`
232
- Reason string `json:"reason,omitempty"`
233
-}
234
-
235
-type StreamMessage struct {
236
- Type string `json:"type"` // "message", "close"
237
- Data string `json:"data,omitempty"`
238
- MessageType string `json:"messageType,omitempty"` // "text", "binary"
239
- Code int `json:"code,omitempty"`
240
- Reason string `json:"reason,omitempty"`
241
-}
242
-
243
-func NewWebSocketManager() *WebSocketManager {
244
- return &WebSocketManager{}
245
-}
246
-
247
-func generateConnID() string {
248
- b := make([]byte, 16)
249
- rand.Read(b)
250
- return hex.EncodeToString(b)
251
-}
252
-
253
-func (m *WebSocketManager) CreateConnection(uri string, protocols []string) (*WSConnection, string, error) {
254
- u, err := url.Parse(uri)
255
- if err != nil {
256
- return nil, "", err
257
- }
258
- id := getLeaseID(u.Hostname())
259
-
260
- u.Scheme = "ws"
261
- u.Host = id
262
-
263
- // Parse URL to extract host for rdDialer
264
- dialer := websocket.Dialer{
265
- NetDialContext: rdDialer,
266
- Subprotocols: protocols,
267
- }
268
-
269
- conn, resp, err := dialer.Dial(u.String(), nil)
270
- if err != nil {
271
- return nil, "", err
272
- }
273
-
274
- // Get negotiated protocol
275
- negotiatedProtocol := ""
276
- if resp != nil && resp.Header != nil {
277
- negotiatedProtocol = resp.Header.Get("Sec-WebSocket-Protocol")
278
- }
279
-
280
- wsConn := &WSConnection{
281
- id: generateConnID(),
282
- conn: conn,
283
- messageChan: make(chan wsMessage, 100),
284
- closeChan: make(chan struct{}),
285
- messageQueue: make([]StreamMessage, 0),
286
- }
287
-
288
- m.connections.Store(wsConn.id, wsConn)
289
-
290
- // Start message receiver and queue manager
291
- go wsConn.receiveMessages()
292
- go wsConn.manageQueue()
293
-
294
- return wsConn, negotiatedProtocol, nil
295
-}
296
-
297
-func (m *WebSocketManager) GetConnection(id string) (*WSConnection, bool) {
298
- conn, ok := m.connections.Load(id)
299
- if !ok {
300
- return nil, false
301
- }
302
- return conn.(*WSConnection), true
303
-}
304
-
305
-func (m *WebSocketManager) RemoveConnection(id string) {
306
- m.connections.Delete(id)
307
-}
308
-
309
-func (c *WSConnection) receiveMessages() {
310
- defer c.Close()
311
-
312
- for {
313
- messageType, msg, err := c.conn.ReadMessage()
314
- if err != nil {
315
- log.Error().Err(err).Str("connId", c.id).Msg("Error receiving message")
316
- c.queueMu.Lock()
317
- c.isClosed = true
318
- c.queueMu.Unlock()
319
- return
320
- }
321
-
322
- // Only handle binary and text messages
323
- if messageType != websocket.BinaryMessage && messageType != websocket.TextMessage {
324
- continue
325
- }
326
-
327
- wsMsg := wsMessage{
328
- data: msg,
329
- isText: messageType == websocket.TextMessage,
330
- }
331
-
332
- select {
333
- case c.messageChan <- wsMsg:
334
- case <-c.closeChan:
335
- return
336
- }
337
- }
338
-}
339
-
340
-func (c *WSConnection) manageQueue() {
341
- for {
342
- select {
343
- case msg := <-c.messageChan:
344
- c.queueMu.Lock()
345
-
346
- // Use message type from WebSocket frame
347
- messageType := "binary"
348
- if msg.isText {
349
- messageType = "text"
350
- }
351
-
352
- streamMsg := StreamMessage{
353
- Type: "message",
354
- Data: base64.StdEncoding.EncodeToString(msg.data),
355
- MessageType: messageType,
356
- }
357
- c.messageQueue = append(c.messageQueue, streamMsg)
358
- c.queueMu.Unlock()
359
-
360
- case <-c.closeChan:
361
- c.queueMu.Lock()
362
- c.isClosed = true
363
- c.messageQueue = append(c.messageQueue, StreamMessage{
364
- Type: "close",
365
- Code: 1000,
366
- Reason: "Connection closed",
367
- })
368
- c.queueMu.Unlock()
369
- return
370
- }
371
- }
372
-}
373
-
374
-func (c *WSConnection) GetMessages() []StreamMessage {
375
- c.queueMu.Lock()
376
- defer c.queueMu.Unlock()
377
-
378
- messages := make([]StreamMessage, len(c.messageQueue))
379
- copy(messages, c.messageQueue)
380
- c.messageQueue = c.messageQueue[:0]
381
-
382
- return messages
383
-}
384
-
385
-func (c *WSConnection) IsClosed() bool {
386
- c.queueMu.Lock()
387
- defer c.queueMu.Unlock()
388
- return c.isClosed
389
-}
390
-
391
-func (c *WSConnection) Send(data []byte, isText bool) error {
392
- c.mu.Lock()
393
- defer c.mu.Unlock()
394
-
395
- select {
396
- case <-c.closeChan:
397
- return fmt.Errorf("connection closed")
398
- default:
399
- messageType := websocket.BinaryMessage
400
- if isText {
401
- messageType = websocket.TextMessage
402
- }
403
- return c.conn.WriteMessage(messageType, data)
404
- }
405
-}
406
-
407
-func (c *WSConnection) Close() {
408
- c.closeOnce.Do(func() {
409
- close(c.closeChan)
410
- c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
411
- c.conn.Close()
412
- })
413
-}
414
-
415
-func getLeaseID(hostname string) string {
416
- // First, decode URL-encoded characters (e.g., %ED%8E%98%EC%9D%B8%ED%8A%B8 -> 페인트)
417
- decoded, err := url.QueryUnescape(hostname)
418
- if err != nil {
419
- decoded = hostname
420
- }
421
-
422
- // Normalize punycode to lowercase before conversion (punycode is case-insensitive)
423
- decoded = strings.ToLower(decoded)
424
-
425
- // Then, convert punycode to unicode (e.g., xn--v9jub -> 日本語)
426
- host, err := idna.ToUnicode(decoded)
427
- if err != nil {
428
- host = decoded
429
- }
430
-
431
- id := strings.Split(host, ".")[0]
432
- id = strings.TrimSpace(id)
433
- id = strings.ToUpper(id)
434
- return id
435
-}
436
-
437
-func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
438
- // Handle WebSocket polyfill endpoints
439
- if strings.HasPrefix(r.URL.Path, "/sw-cgi/websocket/") {
440
- p.handleWebSocketPolyfill(w, r)
441
- return
442
- }
443
-
444
- log.Info().Msgf("Proxying request to %s", r.URL.String())
445
-
446
- r = r.Clone(context.Background())
447
-
448
- // Decode hostname properly for IDN domains
449
- decodedHost := getLeaseID(r.URL.Hostname())
450
- r.URL.Host = decodedHost
451
- r.URL.Scheme = "http"
452
-
453
- resp, err := httpClient.Do(r)
454
- if err != nil {
455
- log.Error().Err(err).Msgf("Failed to proxy request to %s", r.URL.String())
456
- http.Error(w, fmt.Sprintf("Failed to proxy request to %s, err: %v", r.URL.String(), err), http.StatusBadGateway)
457
- return
458
- }
459
- defer resp.Body.Close()
460
-
461
- for key, value := range resp.Header {
462
- w.Header()[key] = value
463
- }
464
-
465
- if utils.IsHTMLContentType(resp.Header.Get("Content-Type")) {
466
- w.WriteHeader(resp.StatusCode)
467
- body, err := io.ReadAll(resp.Body)
468
- if err != nil {
469
- log.Error().Err(err).Msg("Failed to read response body")
470
- return
471
- }
472
- body = InjectHTML(body)
473
- w.Write(body)
474
- return
475
- }
476
-
477
- w.WriteHeader(resp.StatusCode)
478
- io.Copy(w, resp.Body)
479
-}
480
-
481
-func (p *Proxy) handleWebSocketPolyfill(w http.ResponseWriter, r *http.Request) {
482
- path := r.URL.Path
483
-
484
- if path == "/sw-cgi/websocket/connect" && r.Method == http.MethodPost {
485
- p.handleConnect(w, r)
486
- return
487
- }
488
-
489
- if strings.HasPrefix(path, "/sw-cgi/websocket/poll/") && r.Method == http.MethodGet {
490
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/poll/")
491
- p.handlePoll(w, r, connID)
492
- return
493
- }
494
-
495
- if strings.HasPrefix(path, "/sw-cgi/websocket/send/") && r.Method == http.MethodPost {
496
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/send/")
497
- p.handleSend(w, r, connID)
498
- return
499
- }
500
-
501
- if strings.HasPrefix(path, "/sw-cgi/websocket/disconnect/") && r.Method == http.MethodPost {
502
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/disconnect/")
503
- p.handleDisconnect(w, r, connID)
504
- return
505
- }
506
-
507
- http.Error(w, "Not found", http.StatusNotFound)
508
-}
509
-
510
-func (p *Proxy) handleConnect(w http.ResponseWriter, r *http.Request) {
511
- var req ConnectRequest
512
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
513
- http.Error(w, "Invalid request", http.StatusBadRequest)
514
- return
515
- }
516
-
517
- log.Info().Str("url", req.URL).Strs("protocols", req.Protocols).Msg("Creating WebSocket connection")
518
-
519
- wsConn, protocol, err := p.wsManager.CreateConnection(req.URL, req.Protocols)
520
- if err != nil {
521
- log.Error().Err(err).Msg("Failed to create WebSocket connection")
522
- http.Error(w, fmt.Sprintf("Failed to connect: %v", err), http.StatusBadGateway)
523
- return
524
- }
525
-
526
- resp := ConnectResponse{
527
- ConnID: wsConn.id,
528
- Protocol: protocol,
529
- }
530
-
531
- w.Header().Set("Content-Type", "application/json")
532
- json.NewEncoder(w).Encode(resp)
533
-}
534
-
535
-func (p *Proxy) handlePoll(w http.ResponseWriter, r *http.Request, connID string) {
536
- wsConn, ok := p.wsManager.GetConnection(connID)
537
- if !ok {
538
- http.Error(w, "Connection not found", http.StatusNotFound)
539
- return
540
- }
541
-
542
- // Long polling: wait up to 5 seconds for messages
543
- timeout := time.NewTimer(5 * time.Second)
544
- defer timeout.Stop()
545
-
546
- ticker := time.NewTicker(50 * time.Millisecond)
547
- defer ticker.Stop()
548
-
549
- var messages []StreamMessage
550
-
551
- for {
552
- select {
553
- case <-timeout.C:
554
- // Timeout - return empty or existing messages
555
- messages = wsConn.GetMessages()
556
- goto respond
557
-
558
- case <-ticker.C:
559
- // Check for messages periodically
560
- messages = wsConn.GetMessages()
561
- if len(messages) > 0 {
562
- goto respond
563
- }
564
-
565
- case <-r.Context().Done():
566
- // Client disconnected
567
- return
568
- }
569
- }
570
-
571
-respond:
572
- // Check if connection is closed and cleanup if needed
573
- if wsConn.IsClosed() && len(messages) > 0 {
574
- // Check if close message is in the queue
575
- for _, msg := range messages {
576
- if msg.Type == "close" {
577
- defer func() {
578
- p.wsManager.RemoveConnection(connID)
579
- wsConn.Close()
580
- }()
581
- break
582
- }
583
- }
584
- }
585
-
586
- w.Header().Set("Content-Type", "application/json")
587
- w.WriteHeader(http.StatusOK)
588
- json.NewEncoder(w).Encode(map[string]interface{}{
589
- "messages": messages,
590
- })
591
-}
592
-
593
-func (p *Proxy) handleSend(w http.ResponseWriter, r *http.Request, connID string) {
594
- wsConn, ok := p.wsManager.GetConnection(connID)
595
- if !ok {
596
- http.Error(w, "Connection not found", http.StatusNotFound)
597
- return
598
- }
599
-
600
- var req SendRequest
601
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
602
- http.Error(w, "Invalid request", http.StatusBadRequest)
603
- return
604
- }
605
-
606
- if req.Type == "close" {
607
- log.Info().Str("connId", connID).Msg("Closing WebSocket connection")
608
- wsConn.Close()
609
- p.wsManager.RemoveConnection(connID)
610
- w.WriteHeader(http.StatusOK)
611
- return
612
- }
613
-
614
- var data []byte
615
- var err error
616
- var isText bool
617
-
618
- switch req.Type {
619
- case "binary":
620
- data, err = base64.StdEncoding.DecodeString(req.Data)
621
- if err != nil {
622
- http.Error(w, "Invalid base64 data", http.StatusBadRequest)
623
- return
624
- }
625
- isText = false
626
- case "text":
627
- data = []byte(req.Data)
628
- isText = true
629
- default:
630
- http.Error(w, "Invalid message type", http.StatusBadRequest)
631
- return
632
- }
633
-
634
- if err := wsConn.Send(data, isText); err != nil {
635
- log.Error().Err(err).Msg("Failed to send message")
636
- http.Error(w, fmt.Sprintf("Failed to send: %v", err), http.StatusInternalServerError)
637
- return
638
- }
639
-
640
- w.WriteHeader(http.StatusOK)
641
-}
642
-
643
-func (p *Proxy) handleDisconnect(w http.ResponseWriter, r *http.Request, connID string) {
644
- log.Info().Str("connId", connID).Msg("Handling disconnect request")
645
-
646
- wsConn, ok := p.wsManager.GetConnection(connID)
647
- if !ok {
648
- // Connection already removed or doesn't exist - this is OK
649
- log.Debug().Str("connId", connID).Msg("Connection not found (already disconnected)")
650
- w.WriteHeader(http.StatusOK)
651
- return
652
- }
653
-
654
- // Close the WebSocket connection
655
- wsConn.Close()
656
-
657
- // Remove from manager
658
- p.wsManager.RemoveConnection(connID)
659
-
660
- log.Info().Str("connId", connID).Msg("WebSocket connection disconnected successfully")
661
- w.WriteHeader(http.StatusOK)
662
-}
663
-
664
-// SDK Connection handlers for Service Worker messaging
665
-
666
-func handleSDKConnect(data js.Value) {
667
- defer func() {
668
- if r := recover(); r != nil {
669
- log.Error().Interface("panic", r).Msg("[SDK Connect] Recovered from panic")
670
- }
671
- }()
672
-
673
- // Safely extract fields with validation
674
- if data.Get("leaseName").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined {
675
- log.Warn().Msg("[SDK Connect] Missing required fields")
676
- return
677
- }
678
-
679
- leaseName := data.Get("leaseName").String()
680
- clientId := data.Get("clientId").String()
681
-
682
- // Extract pipelined upgrade request if present (reduces RTT from 2 to 1)
683
- var upgradeRequest []byte
684
- upgradeReqJS := data.Get("upgradeRequest")
685
- if upgradeReqJS.Type() != js.TypeUndefined && upgradeReqJS.Type() != js.TypeNull {
686
- if upgradeReqJS.InstanceOf(js.Global().Get("Uint8Array")) {
687
- length := upgradeReqJS.Get("length").Int()
688
- upgradeRequest = make([]byte, length)
689
- js.CopyBytesToGo(upgradeRequest, upgradeReqJS)
690
- log.Debug().Int("size", length).Msg("[SDK Connect] Pipelined upgrade request received")
691
- }
692
- }
693
-
694
- log.Info().Str("leaseName", leaseName).Str("clientId", clientId).Bool("pipelined", len(upgradeRequest) > 0).Msg("[SDK Connect] Connecting")
695
-
696
- go func() {
697
- defer func() {
698
- if r := recover(); r != nil {
699
- log.Error().Interface("panic", r).Str("clientId", clientId).Msg("[SDK Connect] Goroutine recovered from panic")
700
- }
701
- }()
702
- // Convert leaseName (may be punycode) to uppercase unicode
703
- normalizedLeaseName := getLeaseID(leaseName)
704
- log.Debug().Str("original", leaseName).Str("normalized", normalizedLeaseName).Msg("[SDK Connect] Normalized lease name")
705
-
706
- // Check DNS cache first, then lookup if needed
707
- var leaseID string
708
- if cachedID, ok := lookupDNSCache(normalizedLeaseName); ok {
709
- log.Debug().Str("name", normalizedLeaseName).Str("id", cachedID).Msg("[SDK Connect] DNS cache hit")
710
- leaseID = cachedID
711
- } else {
712
- // Cache miss - perform lookup
713
- lease, err := client.LookupName(normalizedLeaseName)
714
- if err != nil {
715
- log.Error().Err(err).Str("leaseName", leaseName).Msg("[SDK Connect] Lease lookup failed")
716
- js.Global().Call("__sdk_post_message", map[string]interface{}{
717
- "type": "SDK_CONNECT_ERROR",
718
- "clientId": clientId,
719
- "error": err.Error(),
720
- })
721
- return
722
- }
723
- leaseID = lease.GetIdentity().GetId()
724
- storeDNSCache(normalizedLeaseName, leaseID)
725
- log.Info().Str("leaseName", leaseName).Str("leaseID", leaseID).Msg("[SDK Connect] Lease found, cached")
726
- }
727
-
728
- // Create E2EE connection using SDK with lease ID
729
- cred := sdk.NewCredential()
730
- conn, err := client.Dial(cred, leaseID, "http/1.1")
731
- if err != nil {
732
- log.Error().Err(err).Str("leaseID", leaseID).Msg("[SDK Connect] Failed")
733
-
734
- // Send error to client
735
- js.Global().Call("__sdk_post_message", map[string]interface{}{
736
- "type": "SDK_CONNECT_ERROR",
737
- "clientId": clientId,
738
- "error": err.Error(),
739
- })
740
- return
741
- }
742
-
743
- // If pipelined upgrade request is present, validate and send it immediately (saves 1 RTT)
744
- if len(upgradeRequest) > 0 {
745
- if !isValidUpgradeRequest(upgradeRequest) {
746
- log.Warn().Str("leaseID", leaseID).Msg("[SDK Connect] Invalid pipelined upgrade request, skipping")
747
- } else {
748
- _, err := conn.Write(upgradeRequest)
749
- if err != nil {
750
- log.Error().Err(err).Str("leaseID", leaseID).Msg("[SDK Connect] Failed to send pipelined upgrade request")
751
- conn.Close()
752
- js.Global().Call("__sdk_post_message", map[string]interface{}{
753
- "type": "SDK_CONNECT_ERROR",
754
- "clientId": clientId,
755
- "error": err.Error(),
756
- })
757
- return
758
- }
759
- log.Debug().Str("leaseID", leaseID).Msg("[SDK Connect] Pipelined upgrade request sent")
760
- }
761
- }
762
-
763
- // Generate connection ID
764
- connID := generateConnID()
765
-
766
- // Store connection
767
- sdkConnectionsMu.Lock()
768
- sdkConnections[connID] = conn
769
- sdkConnectionsMu.Unlock()
770
-
771
- log.Info().Str("leaseName", leaseName).Str("connId", connID).Msg("[SDK Connect] Connected")
772
-
773
- // Send success to client
774
- js.Global().Call("__sdk_post_message", map[string]interface{}{
775
- "type": "SDK_CONNECT_SUCCESS",
776
- "clientId": clientId,
777
- "connId": connID,
778
- })
779
-
780
- // Start reading from connection
781
- go func() {
782
- buffer := make([]byte, 32*1024)
783
- for {
784
- n, err := conn.Read(buffer)
785
- if err != nil {
786
- if err != io.EOF {
787
- log.Error().Err(err).Str("connId", connID).Msg("[SDK Connect] Read error")
788
- }
789
-
790
- // Remove connection
791
- sdkConnectionsMu.Lock()
792
- delete(sdkConnections, connID)
793
- sdkConnectionsMu.Unlock()
794
-
795
- // Send close to client
796
- code := 1000
797
- if err != io.EOF {
798
- code = 1006
799
- }
800
- js.Global().Call("__sdk_post_message", map[string]interface{}{
801
- "type": "SDK_DATA_CLOSE",
802
- "clientId": clientId,
803
- "connId": connID,
804
- "code": code,
805
- })
806
- return
807
- }
808
-
809
- // Copy data to JavaScript Uint8Array
810
- data := make([]byte, n)
811
- copy(data, buffer[:n])
812
-
813
- uint8Array := js.Global().Get("Uint8Array").New(n)
814
- js.CopyBytesToJS(uint8Array, data)
815
-
816
- // Send data to client
817
- js.Global().Call("__sdk_post_message", map[string]interface{}{
818
- "type": "SDK_DATA",
819
- "clientId": clientId,
820
- "connId": connID,
821
- "data": uint8Array,
822
- })
823
- }
824
- }()
825
- }()
826
-}
827
-
828
-func handleSDKSend(data js.Value) {
829
- defer func() {
830
- if r := recover(); r != nil {
831
- log.Error().Interface("panic", r).Msg("[SDK Send] Recovered from panic")
832
- }
833
- }()
834
-
835
- // Safely extract fields with validation
836
- if data.Get("connId").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined || data.Get("data").Type() == js.TypeUndefined {
837
- log.Warn().Msg("[SDK Send] Missing required fields")
838
- return
839
- }
840
-
841
- connID := data.Get("connId").String()
842
- clientId := data.Get("clientId").String()
843
- payload := data.Get("data")
844
-
845
- // Get connection
846
- sdkConnectionsMu.RLock()
847
- conn, ok := sdkConnections[connID]
848
- sdkConnectionsMu.RUnlock()
849
-
850
- if !ok {
851
- log.Warn().Str("connId", connID).Msg("[SDK Send] Connection not found")
852
- js.Global().Call("__sdk_post_message", map[string]interface{}{
853
- "type": "SDK_SEND_ERROR",
854
- "clientId": clientId,
855
- "connId": connID,
856
- "error": "connection not found",
857
- })
858
- return
859
- }
860
-
861
- // Convert payload to bytes
862
- var bytes []byte
863
- if payload.InstanceOf(js.Global().Get("Uint8Array")) {
864
- length := payload.Get("length").Int()
865
- bytes = make([]byte, length)
866
- js.CopyBytesToGo(bytes, payload)
867
- } else if payload.InstanceOf(js.Global().Get("ArrayBuffer")) {
868
- uint8Array := js.Global().Get("Uint8Array").New(payload)
869
- length := uint8Array.Get("length").Int()
870
- bytes = make([]byte, length)
871
- js.CopyBytesToGo(bytes, uint8Array)
872
- } else {
873
- log.Warn().Str("connId", connID).Msg("[SDK Send] Unsupported data type")
874
- return
875
- }
876
-
877
- go func() {
878
- _, err := conn.Write(bytes)
879
- if err != nil {
880
- log.Error().Err(err).Str("connId", connID).Msg("[SDK Send] Write failed")
881
- js.Global().Call("__sdk_post_message", map[string]interface{}{
882
- "type": "SDK_SEND_ERROR",
883
- "clientId": clientId,
884
- "connId": connID,
885
- "error": err.Error(),
886
- })
887
- }
888
- }()
889
-}
890
-
891
-func handleSDKClose(data js.Value) {
892
- defer func() {
893
- if r := recover(); r != nil {
894
- log.Error().Interface("panic", r).Msg("[SDK Close] Recovered from panic")
895
- }
896
- }()
897
-
898
- // Safely extract fields with validation
899
- if data.Get("connId").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined {
900
- log.Warn().Msg("[SDK Close] Missing required fields")
901
- return
902
- }
903
-
904
- connID := data.Get("connId").String()
905
- clientId := data.Get("clientId").String()
906
-
907
- // Get and remove connection
908
- sdkConnectionsMu.Lock()
909
- conn, ok := sdkConnections[connID]
910
- if ok {
911
- delete(sdkConnections, connID)
912
- }
913
- sdkConnectionsMu.Unlock()
914
-
915
- if !ok {
916
- log.Warn().Str("connId", connID).Msg("[SDK Close] Connection not found")
917
- return
918
- }
919
-
920
- log.Info().Str("connId", connID).Msg("[SDK Close] Closing connection")
921
- conn.Close()
922
-
923
- // Send close confirmation to client
924
- js.Global().Call("__sdk_post_message", map[string]interface{}{
925
- "type": "SDK_DATA_CLOSE",
926
- "clientId": clientId,
927
- "connId": connID,
928
- "code": 1000,
929
- })
930
-}
931
-
932
-func main() {
933
- log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339})
934
- var err error
935
-
936
- // Get bootstrap servers from global JavaScript variable
937
- bootstrapServerList := getBootstrapServers()
938
-
939
- log.Info().Strs("servers", bootstrapServerList).Msg("Initializing RDClient with bootstrap servers from global variable")
940
-
941
- client, err = sdk.NewClient(
942
- sdk.WithBootstrapServers(bootstrapServerList),
943
- sdk.WithDialer(WebSocketDialerJS()),
944
- )
945
- if err != nil {
946
- panic(err)
947
- }
948
- defer client.Close()
949
-
950
- // Initialize reusable credential for HTTP connections
951
- dialerCredential = sdk.NewCredential()
952
-
953
- // Initialize WebSocket manager
954
- wsManager := NewWebSocketManager()
955
- proxy := &Proxy{
956
- wsManager: wsManager,
957
- }
958
-
959
- // Expose HTTP handler to JavaScript as __go_jshttp
960
- js.Global().Set("__go_jshttp", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
961
- if len(args) < 1 {
962
- return js.Global().Get("Promise").Call("reject",
963
- js.Global().Get("Error").New("required parameter JSRequest missing"))
964
- }
965
-
966
- jsReq := args[0]
967
- return httpjs.ServeHTTPAsyncWithStreaming(proxy, jsReq)
968
- }))
969
- log.Info().Msg("Portal proxy handler registered as __go_jshttp")
970
-
971
- // Expose SDK connection handler for Service Worker messaging
972
- js.Global().Set("__sdk_message_handler", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
973
- if len(args) < 2 {
974
- log.Warn().Msg("[SDK Message] Invalid arguments")
975
- return nil
976
- }
977
-
978
- messageType := args[0].String()
979
- data := args[1]
980
-
981
- switch messageType {
982
- case "SDK_CONNECT":
983
- handleSDKConnect(data)
984
- case "SDK_SEND":
985
- handleSDKSend(data)
986
- case "SDK_CLOSE":
987
- handleSDKClose(data)
988
- default:
989
- log.Warn().Str("type", messageType).Msg("[SDK Message] Unknown message type")
990
- }
991
-
992
- return nil
993
- }))
994
- log.Info().Msg("SDK message handler registered as __sdk_message_handler")
995
-
996
- if runtime.Compiler == "tinygo" {
997
- return
998
- }
999
- // Wait
1000
- ch := make(chan bool)
1001
- <-ch
1002
-}
cmd/webclient/main_js_prod.go
deleted
-867
@@ -1,867 +0,0 @@
1
-//go:build prod
2
-
3
-package main
4
-
5
-import (
6
- "context"
7
- "crypto/rand"
8
- "encoding/base64"
9
- "encoding/hex"
10
- "encoding/json"
11
- "fmt"
12
- "io"
13
- "net"
14
- "net/http"
15
- "net/url"
16
- "runtime"
17
- "strings"
18
- "sync"
19
- "syscall/js"
20
- "time"
21
-
22
- "github.com/gorilla/websocket"
23
- "golang.org/x/net/idna"
24
- "gosuda.org/portal/cmd/webclient/httpjs"
25
- "gosuda.org/portal/portal/core/cryptoops"
26
- "gosuda.org/portal/sdk"
27
- "gosuda.org/portal/utils"
28
-)
29
-
30
-// Production build: no logging overhead
31
-
32
-var (
33
- client *sdk.Client
34
-
35
- // SDK connection manager for Service Worker messaging
36
- sdkConnections = make(map[string]io.ReadWriteCloser)
37
- sdkConnectionsMu sync.RWMutex
38
-
39
- // Reusable credential for HTTP connections (enables Keep-Alive)
40
- dialerCredential *cryptoops.Credential
41
-
42
- // DNS cache for lease name -> lease ID mapping
43
- dnsCache sync.Map // map[string]*dnsCacheEntry
44
- dnsCacheTTL = 5 * time.Minute
45
-)
46
-
47
-type dnsCacheEntry struct {
48
- leaseID string
49
- expiresAt time.Time
50
-}
51
-
52
-// getBootstrapServers retrieves bootstrap servers from global JavaScript variable
53
-func getBootstrapServers() []string {
54
- bootstrapsValue := js.Global().Get("__BOOTSTRAP_SERVERS__")
55
-
56
- if bootstrapsValue.IsUndefined() || bootstrapsValue.IsNull() {
57
- return []string{"ws://localhost:4017/relay"}
58
- }
59
-
60
- if bootstrapsValue.Type() == js.TypeString {
61
- bootstrapsStr := bootstrapsValue.String()
62
- if bootstrapsStr == "" {
63
- return []string{"ws://localhost:4017/relay"}
64
- }
65
- servers := strings.Split(bootstrapsStr, ",")
66
- for i := range servers {
67
- servers[i] = strings.TrimSpace(servers[i])
68
- }
69
- return servers
70
- }
71
-
72
- if bootstrapsValue.Type() == js.TypeObject && bootstrapsValue.Length() > 0 {
73
- servers := make([]string, bootstrapsValue.Length())
74
- for i := 0; i < bootstrapsValue.Length(); i++ {
75
- servers[i] = bootstrapsValue.Index(i).String()
76
- }
77
- return servers
78
- }
79
-
80
- return []string{"ws://localhost:4017/relay"}
81
-}
82
-
83
-func lookupDNSCache(name string) (string, bool) {
84
- if entry, ok := dnsCache.Load(name); ok {
85
- cached := entry.(*dnsCacheEntry)
86
- if time.Now().Before(cached.expiresAt) {
87
- return cached.leaseID, true
88
- }
89
- dnsCache.Delete(name)
90
- }
91
- return "", false
92
-}
93
-
94
-func storeDNSCache(name, leaseID string) {
95
- dnsCache.Store(name, &dnsCacheEntry{
96
- leaseID: leaseID,
97
- expiresAt: time.Now().Add(dnsCacheTTL),
98
- })
99
-}
100
-
101
-func isValidUpgradeRequest(req []byte) bool {
102
- if len(req) < 20 {
103
- return false
104
- }
105
- s := string(req)
106
- if !strings.HasPrefix(s, "GET ") {
107
- return false
108
- }
109
- if !strings.HasSuffix(s, "\r\n\r\n") {
110
- return false
111
- }
112
- if !strings.Contains(strings.ToLower(s), "upgrade:") {
113
- return false
114
- }
115
- return true
116
-}
117
-
118
-var rdDialer = func(ctx context.Context, network, address string) (net.Conn, error) {
119
- address = strings.TrimSuffix(address, ":80")
120
- address = strings.TrimSuffix(address, ":443")
121
-
122
- decodedAddr, err := url.QueryUnescape(address)
123
- if err != nil {
124
- decodedAddr = address
125
- }
126
- address = decodedAddr
127
-
128
- unicodeAddr, err := idna.ToUnicode(address)
129
- if err != nil {
130
- unicodeAddr = address
131
- }
132
- address = unicodeAddr
133
-
134
- if cachedID, ok := lookupDNSCache(address); ok {
135
- address = cachedID
136
- } else {
137
- lease, err := client.LookupName(address)
138
- if err == nil && lease != nil {
139
- leaseID := lease.Identity.Id
140
- storeDNSCache(unicodeAddr, leaseID)
141
- address = leaseID
142
- }
143
- }
144
-
145
- conn, err := client.Dial(dialerCredential, address, "http/1.1")
146
- if err != nil {
147
- return nil, err
148
- }
149
-
150
- return conn, nil
151
-}
152
-
153
-var httpClient = &http.Client{
154
- Timeout: time.Second * 30,
155
- Transport: &http.Transport{
156
- MaxIdleConns: 1000,
157
- MaxIdleConnsPerHost: 100,
158
- DialContext: rdDialer,
159
- },
160
-}
161
-
162
-type Proxy struct {
163
- wsManager *WebSocketManager
164
-}
165
-
166
-type WebSocketManager struct {
167
- connections sync.Map
168
-}
169
-
170
-type WSConnection struct {
171
- id string
172
- conn *websocket.Conn
173
- messageChan chan wsMessage
174
- closeChan chan struct{}
175
- closeOnce sync.Once
176
- mu sync.Mutex
177
- messageQueue []StreamMessage
178
- queueMu sync.Mutex
179
- isClosed bool
180
-}
181
-
182
-type wsMessage struct {
183
- data []byte
184
- isText bool
185
-}
186
-
187
-type ConnectRequest struct {
188
- URL string `json:"url"`
189
- Protocols []string `json:"protocols"`
190
-}
191
-
192
-type ConnectResponse struct {
193
- ConnID string `json:"connId"`
194
- Protocol string `json:"protocol"`
195
-}
196
-
197
-type SendRequest struct {
198
- Type string `json:"type"`
199
- Data string `json:"data,omitempty"`
200
- Code int `json:"code,omitempty"`
201
- Reason string `json:"reason,omitempty"`
202
-}
203
-
204
-type StreamMessage struct {
205
- Type string `json:"type"`
206
- Data string `json:"data,omitempty"`
207
- MessageType string `json:"messageType,omitempty"`
208
- Code int `json:"code,omitempty"`
209
- Reason string `json:"reason,omitempty"`
210
-}
211
-
212
-func NewWebSocketManager() *WebSocketManager {
213
- return &WebSocketManager{}
214
-}
215
-
216
-func generateConnID() string {
217
- b := make([]byte, 16)
218
- rand.Read(b)
219
- return hex.EncodeToString(b)
220
-}
221
-
222
-func (m *WebSocketManager) CreateConnection(uri string, protocols []string) (*WSConnection, string, error) {
223
- u, err := url.Parse(uri)
224
- if err != nil {
225
- return nil, "", err
226
- }
227
- id := getLeaseID(u.Hostname())
228
-
229
- u.Scheme = "ws"
230
- u.Host = id
231
-
232
- dialer := websocket.Dialer{
233
- NetDialContext: rdDialer,
234
- Subprotocols: protocols,
235
- }
236
-
237
- conn, resp, err := dialer.Dial(u.String(), nil)
238
- if err != nil {
239
- return nil, "", err
240
- }
241
-
242
- negotiatedProtocol := ""
243
- if resp != nil && resp.Header != nil {
244
- negotiatedProtocol = resp.Header.Get("Sec-WebSocket-Protocol")
245
- }
246
-
247
- wsConn := &WSConnection{
248
- id: generateConnID(),
249
- conn: conn,
250
- messageChan: make(chan wsMessage, 100),
251
- closeChan: make(chan struct{}),
252
- messageQueue: make([]StreamMessage, 0),
253
- }
254
-
255
- m.connections.Store(wsConn.id, wsConn)
256
-
257
- go wsConn.receiveMessages()
258
- go wsConn.manageQueue()
259
-
260
- return wsConn, negotiatedProtocol, nil
261
-}
262
-
263
-func (m *WebSocketManager) GetConnection(id string) (*WSConnection, bool) {
264
- conn, ok := m.connections.Load(id)
265
- if !ok {
266
- return nil, false
267
- }
268
- return conn.(*WSConnection), true
269
-}
270
-
271
-func (m *WebSocketManager) RemoveConnection(id string) {
272
- m.connections.Delete(id)
273
-}
274
-
275
-func (c *WSConnection) receiveMessages() {
276
- defer c.Close()
277
-
278
- for {
279
- messageType, msg, err := c.conn.ReadMessage()
280
- if err != nil {
281
- c.queueMu.Lock()
282
- c.isClosed = true
283
- c.queueMu.Unlock()
284
- return
285
- }
286
-
287
- if messageType != websocket.BinaryMessage && messageType != websocket.TextMessage {
288
- continue
289
- }
290
-
291
- wsMsg := wsMessage{
292
- data: msg,
293
- isText: messageType == websocket.TextMessage,
294
- }
295
-
296
- select {
297
- case c.messageChan <- wsMsg:
298
- case <-c.closeChan:
299
- return
300
- }
301
- }
302
-}
303
-
304
-func (c *WSConnection) manageQueue() {
305
- for {
306
- select {
307
- case msg := <-c.messageChan:
308
- c.queueMu.Lock()
309
-
310
- messageType := "binary"
311
- if msg.isText {
312
- messageType = "text"
313
- }
314
-
315
- streamMsg := StreamMessage{
316
- Type: "message",
317
- Data: base64.StdEncoding.EncodeToString(msg.data),
318
- MessageType: messageType,
319
- }
320
- c.messageQueue = append(c.messageQueue, streamMsg)
321
- c.queueMu.Unlock()
322
-
323
- case <-c.closeChan:
324
- c.queueMu.Lock()
325
- c.isClosed = true
326
- c.messageQueue = append(c.messageQueue, StreamMessage{
327
- Type: "close",
328
- Code: 1000,
329
- Reason: "Connection closed",
330
- })
331
- c.queueMu.Unlock()
332
- return
333
- }
334
- }
335
-}
336
-
337
-func (c *WSConnection) GetMessages() []StreamMessage {
338
- c.queueMu.Lock()
339
- defer c.queueMu.Unlock()
340
-
341
- messages := make([]StreamMessage, len(c.messageQueue))
342
- copy(messages, c.messageQueue)
343
- c.messageQueue = c.messageQueue[:0]
344
-
345
- return messages
346
-}
347
-
348
-func (c *WSConnection) IsClosed() bool {
349
- c.queueMu.Lock()
350
- defer c.queueMu.Unlock()
351
- return c.isClosed
352
-}
353
-
354
-func (c *WSConnection) Send(data []byte, isText bool) error {
355
- c.mu.Lock()
356
- defer c.mu.Unlock()
357
-
358
- select {
359
- case <-c.closeChan:
360
- return fmt.Errorf("connection closed")
361
- default:
362
- messageType := websocket.BinaryMessage
363
- if isText {
364
- messageType = websocket.TextMessage
365
- }
366
- return c.conn.WriteMessage(messageType, data)
367
- }
368
-}
369
-
370
-func (c *WSConnection) Close() {
371
- c.closeOnce.Do(func() {
372
- close(c.closeChan)
373
- c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
374
- c.conn.Close()
375
- })
376
-}
377
-
378
-func getLeaseID(hostname string) string {
379
- decoded, err := url.QueryUnescape(hostname)
380
- if err != nil {
381
- decoded = hostname
382
- }
383
-
384
- decoded = strings.ToLower(decoded)
385
-
386
- host, err := idna.ToUnicode(decoded)
387
- if err != nil {
388
- host = decoded
389
- }
390
-
391
- id := strings.Split(host, ".")[0]
392
- id = strings.TrimSpace(id)
393
- id = strings.ToUpper(id)
394
- return id
395
-}
396
-
397
-func InjectHTML(body []byte) []byte {
398
- // In production, HTML injection is handled by service worker
399
- return body
400
-}
401
-
402
-func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
403
- if strings.HasPrefix(r.URL.Path, "/sw-cgi/websocket/") {
404
- p.handleWebSocketPolyfill(w, r)
405
- return
406
- }
407
-
408
- r = r.Clone(context.Background())
409
-
410
- decodedHost := getLeaseID(r.URL.Hostname())
411
- r.URL.Host = decodedHost
412
- r.URL.Scheme = "http"
413
-
414
- resp, err := httpClient.Do(r)
415
- if err != nil {
416
- http.Error(w, fmt.Sprintf("Failed to proxy request to %s", r.URL.String()), http.StatusBadGateway)
417
- return
418
- }
419
- defer resp.Body.Close()
420
-
421
- for key, value := range resp.Header {
422
- w.Header()[key] = value
423
- }
424
-
425
- if utils.IsHTMLContentType(resp.Header.Get("Content-Type")) {
426
- w.WriteHeader(resp.StatusCode)
427
- body, err := io.ReadAll(resp.Body)
428
- if err != nil {
429
- return
430
- }
431
- w.Write(body)
432
- return
433
- }
434
-
435
- w.WriteHeader(resp.StatusCode)
436
- io.Copy(w, resp.Body)
437
-}
438
-
439
-func (p *Proxy) handleWebSocketPolyfill(w http.ResponseWriter, r *http.Request) {
440
- path := r.URL.Path
441
-
442
- if path == "/sw-cgi/websocket/connect" && r.Method == http.MethodPost {
443
- p.handleConnect(w, r)
444
- return
445
- }
446
-
447
- if strings.HasPrefix(path, "/sw-cgi/websocket/poll/") && r.Method == http.MethodGet {
448
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/poll/")
449
- p.handlePoll(w, r, connID)
450
- return
451
- }
452
-
453
- if strings.HasPrefix(path, "/sw-cgi/websocket/send/") && r.Method == http.MethodPost {
454
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/send/")
455
- p.handleSend(w, r, connID)
456
- return
457
- }
458
-
459
- if strings.HasPrefix(path, "/sw-cgi/websocket/disconnect/") && r.Method == http.MethodPost {
460
- connID := strings.TrimPrefix(path, "/sw-cgi/websocket/disconnect/")
461
- p.handleDisconnect(w, r, connID)
462
- return
463
- }
464
-
465
- http.Error(w, "Not found", http.StatusNotFound)
466
-}
467
-
468
-func (p *Proxy) handleConnect(w http.ResponseWriter, r *http.Request) {
469
- var req ConnectRequest
470
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
471
- http.Error(w, "Invalid request", http.StatusBadRequest)
472
- return
473
- }
474
-
475
- wsConn, protocol, err := p.wsManager.CreateConnection(req.URL, req.Protocols)
476
- if err != nil {
477
- http.Error(w, fmt.Sprintf("Failed to connect: %v", err), http.StatusBadGateway)
478
- return
479
- }
480
-
481
- resp := ConnectResponse{
482
- ConnID: wsConn.id,
483
- Protocol: protocol,
484
- }
485
-
486
- w.Header().Set("Content-Type", "application/json")
487
- json.NewEncoder(w).Encode(resp)
488
-}
489
-
490
-func (p *Proxy) handlePoll(w http.ResponseWriter, r *http.Request, connID string) {
491
- wsConn, ok := p.wsManager.GetConnection(connID)
492
- if !ok {
493
- http.Error(w, "Connection not found", http.StatusNotFound)
494
- return
495
- }
496
-
497
- timeout := time.NewTimer(5 * time.Second)
498
- defer timeout.Stop()
499
-
500
- ticker := time.NewTicker(50 * time.Millisecond)
501
- defer ticker.Stop()
502
-
503
- var messages []StreamMessage
504
-
505
- for {
506
- select {
507
- case <-timeout.C:
508
- messages = wsConn.GetMessages()
509
- goto respond
510
-
511
- case <-ticker.C:
512
- messages = wsConn.GetMessages()
513
- if len(messages) > 0 {
514
- goto respond
515
- }
516
-
517
- case <-r.Context().Done():
518
- return
519
- }
520
- }
521
-
522
-respond:
523
- if wsConn.IsClosed() && len(messages) > 0 {
524
- for _, msg := range messages {
525
- if msg.Type == "close" {
526
- defer func() {
527
- p.wsManager.RemoveConnection(connID)
528
- wsConn.Close()
529
- }()
530
- break
531
- }
532
- }
533
- }
534
-
535
- w.Header().Set("Content-Type", "application/json")
536
- w.WriteHeader(http.StatusOK)
537
- json.NewEncoder(w).Encode(map[string]interface{}{
538
- "messages": messages,
539
- })
540
-}
541
-
542
-func (p *Proxy) handleSend(w http.ResponseWriter, r *http.Request, connID string) {
543
- wsConn, ok := p.wsManager.GetConnection(connID)
544
- if !ok {
545
- http.Error(w, "Connection not found", http.StatusNotFound)
546
- return
547
- }
548
-
549
- var req SendRequest
550
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
551
- http.Error(w, "Invalid request", http.StatusBadRequest)
552
- return
553
- }
554
-
555
- if req.Type == "close" {
556
- wsConn.Close()
557
- p.wsManager.RemoveConnection(connID)
558
- w.WriteHeader(http.StatusOK)
559
- return
560
- }
561
-
562
- var data []byte
563
- var err error
564
- var isText bool
565
-
566
- switch req.Type {
567
- case "binary":
568
- data, err = base64.StdEncoding.DecodeString(req.Data)
569
- if err != nil {
570
- http.Error(w, "Invalid base64 data", http.StatusBadRequest)
571
- return
572
- }
573
- isText = false
574
- case "text":
575
- data = []byte(req.Data)
576
- isText = true
577
- default:
578
- http.Error(w, "Invalid message type", http.StatusBadRequest)
579
- return
580
- }
581
-
582
- if err := wsConn.Send(data, isText); err != nil {
583
- http.Error(w, fmt.Sprintf("Failed to send: %v", err), http.StatusInternalServerError)
584
- return
585
- }
586
-
587
- w.WriteHeader(http.StatusOK)
588
-}
589
-
590
-func (p *Proxy) handleDisconnect(w http.ResponseWriter, r *http.Request, connID string) {
591
- wsConn, ok := p.wsManager.GetConnection(connID)
592
- if !ok {
593
- w.WriteHeader(http.StatusOK)
594
- return
595
- }
596
-
597
- wsConn.Close()
598
- p.wsManager.RemoveConnection(connID)
599
-
600
- w.WriteHeader(http.StatusOK)
601
-}
602
-
603
-func handleSDKConnect(data js.Value) {
604
- defer func() {
605
- if r := recover(); r != nil {
606
- }
607
- }()
608
-
609
- if data.Get("leaseName").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined {
610
- return
611
- }
612
-
613
- leaseName := data.Get("leaseName").String()
614
- clientId := data.Get("clientId").String()
615
-
616
- var upgradeRequest []byte
617
- upgradeReqJS := data.Get("upgradeRequest")
618
- if upgradeReqJS.Type() != js.TypeUndefined && upgradeReqJS.Type() != js.TypeNull {
619
- if upgradeReqJS.InstanceOf(js.Global().Get("Uint8Array")) {
620
- length := upgradeReqJS.Get("length").Int()
621
- upgradeRequest = make([]byte, length)
622
- js.CopyBytesToGo(upgradeRequest, upgradeReqJS)
623
- }
624
- }
625
-
626
- go func() {
627
- defer func() {
628
- if r := recover(); r != nil {
629
- }
630
- }()
631
-
632
- lease, err := client.LookupName(leaseName)
633
- if err != nil {
634
- js.Global().Call("__sdk_post_message", map[string]interface{}{
635
- "type": "SDK_CONNECT_ERROR",
636
- "clientId": clientId,
637
- "error": "lease not found",
638
- })
639
- return
640
- }
641
-
642
- connID := lease.Identity.Id
643
-
644
- cred := sdk.NewCredential()
645
- conn, err := client.Dial(cred, connID, "rdsec/1.0")
646
- if err != nil {
647
- js.Global().Call("__sdk_post_message", map[string]interface{}{
648
- "type": "SDK_CONNECT_ERROR",
649
- "clientId": clientId,
650
- "error": err.Error(),
651
- })
652
- return
653
- }
654
-
655
- sdkConnectionsMu.Lock()
656
- sdkConnections[connID] = conn
657
- sdkConnectionsMu.Unlock()
658
-
659
- js.Global().Call("__sdk_post_message", map[string]interface{}{
660
- "type": "SDK_CONNECT_SUCCESS",
661
- "clientId": clientId,
662
- "connId": connID,
663
- })
664
-
665
- if len(upgradeRequest) > 0 {
666
- if !isValidUpgradeRequest(upgradeRequest) {
667
- js.Global().Call("__sdk_post_message", map[string]interface{}{
668
- "type": "SDK_CONNECT_ERROR",
669
- "clientId": clientId,
670
- "error": "invalid upgrade request",
671
- })
672
- return
673
- }
674
-
675
- _, err = conn.Write(upgradeRequest)
676
- if err != nil {
677
- js.Global().Call("__sdk_post_message", map[string]interface{}{
678
- "type": "SDK_SEND_ERROR",
679
- "clientId": clientId,
680
- "connId": connID,
681
- "error": err.Error(),
682
- })
683
- return
684
- }
685
- }
686
-
687
- buffer := make([]byte, 32*1024)
688
- for {
689
- n, err := conn.Read(buffer)
690
- if err != nil {
691
- sdkConnectionsMu.Lock()
692
- delete(sdkConnections, connID)
693
- sdkConnectionsMu.Unlock()
694
-
695
- js.Global().Call("__sdk_post_message", map[string]interface{}{
696
- "type": "SDK_DATA_CLOSE",
697
- "clientId": clientId,
698
- "connId": connID,
699
- "code": 1000,
700
- })
701
- return
702
- }
703
-
704
- if n > 0 {
705
- data := make([]byte, n)
706
- copy(data, buffer[:n])
707
-
708
- uint8Array := js.Global().Get("Uint8Array").New(n)
709
- js.CopyBytesToJS(uint8Array, data)
710
-
711
- js.Global().Call("__sdk_post_message", map[string]interface{}{
712
- "type": "SDK_DATA",
713
- "clientId": clientId,
714
- "connId": connID,
715
- "data": uint8Array,
716
- })
717
- }
718
- }
719
- }()
720
-}
721
-
722
-func handleSDKSend(data js.Value) {
723
- defer func() {
724
- if r := recover(); r != nil {
725
- }
726
- }()
727
-
728
- if data.Get("connId").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined || data.Get("data").Type() == js.TypeUndefined {
729
- return
730
- }
731
-
732
- connID := data.Get("connId").String()
733
- clientId := data.Get("clientId").String()
734
- payload := data.Get("data")
735
-
736
- sdkConnectionsMu.RLock()
737
- conn, ok := sdkConnections[connID]
738
- sdkConnectionsMu.RUnlock()
739
-
740
- if !ok {
741
- js.Global().Call("__sdk_post_message", map[string]interface{}{
742
- "type": "SDK_SEND_ERROR",
743
- "clientId": clientId,
744
- "connId": connID,
745
- "error": "connection not found",
746
- })
747
- return
748
- }
749
-
750
- var bytes []byte
751
- if payload.InstanceOf(js.Global().Get("Uint8Array")) {
752
- length := payload.Get("length").Int()
753
- bytes = make([]byte, length)
754
- js.CopyBytesToGo(bytes, payload)
755
- } else if payload.InstanceOf(js.Global().Get("ArrayBuffer")) {
756
- uint8Array := js.Global().Get("Uint8Array").New(payload)
757
- length := uint8Array.Get("length").Int()
758
- bytes = make([]byte, length)
759
- js.CopyBytesToGo(bytes, uint8Array)
760
- } else {
761
- return
762
- }
763
-
764
- go func() {
765
- _, err := conn.Write(bytes)
766
- if err != nil {
767
- js.Global().Call("__sdk_post_message", map[string]interface{}{
768
- "type": "SDK_SEND_ERROR",
769
- "clientId": clientId,
770
- "connId": connID,
771
- "error": err.Error(),
772
- })
773
- }
774
- }()
775
-}
776
-
777
-func handleSDKClose(data js.Value) {
778
- defer func() {
779
- if r := recover(); r != nil {
780
- }
781
- }()
782
-
783
- if data.Get("connId").Type() == js.TypeUndefined || data.Get("clientId").Type() == js.TypeUndefined {
784
- return
785
- }
786
-
787
- connID := data.Get("connId").String()
788
- clientId := data.Get("clientId").String()
789
-
790
- sdkConnectionsMu.Lock()
791
- conn, ok := sdkConnections[connID]
792
- if ok {
793
- delete(sdkConnections, connID)
794
- }
795
- sdkConnectionsMu.Unlock()
796
-
797
- if !ok {
798
- return
799
- }
800
-
801
- conn.Close()
802
-
803
- js.Global().Call("__sdk_post_message", map[string]interface{}{
804
- "type": "SDK_DATA_CLOSE",
805
- "clientId": clientId,
806
- "connId": connID,
807
- "code": 1000,
808
- })
809
-}
810
-
811
-func main() {
812
- bootstrapServerList := getBootstrapServers()
813
-
814
- var err error
815
- client, err = sdk.NewClient(
816
- sdk.WithBootstrapServers(bootstrapServerList),
817
- sdk.WithDialer(WebSocketDialerJS()),
818
- )
819
- if err != nil {
820
- panic(err)
821
- }
822
- defer client.Close()
823
-
824
- dialerCredential = sdk.NewCredential()
825
-
826
- wsManager := NewWebSocketManager()
827
- proxy := &Proxy{
828
- wsManager: wsManager,
829
- }
830
-
831
- js.Global().Set("__go_jshttp", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
832
- if len(args) < 1 {
833
- return js.Global().Get("Promise").Call("reject",
834
- js.Global().Get("Error").New("required parameter JSRequest missing"))
835
- }
836
-
837
- jsReq := args[0]
838
- return httpjs.ServeHTTPAsyncWithStreaming(proxy, jsReq)
839
- }))
840
-
841
- js.Global().Set("__sdk_message_handler", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
842
- if len(args) < 2 {
843
- return nil
844
- }
845
-
846
- messageType := args[0].String()
847
- data := args[1]
848
-
849
- switch messageType {
850
- case "SDK_CONNECT":
851
- handleSDKConnect(data)
852
- case "SDK_SEND":
853
- handleSDKSend(data)
854
- case "SDK_CLOSE":
855
- handleSDKClose(data)
856
- }
857
-
858
- return nil
859
- }))
860
-
861
- if runtime.Compiler == "tinygo" {
862
- return
863
- }
864
-
865
- ch := make(chan bool)
866
- <-ch
867
-}
cmd/webclient/polyfill.js
deleted
-696
@@ -1,696 +0,0 @@
1
-(function () {
2
- "use strict";
3
-
4
- // Capture reference to current script for later removal
5
- const currentScript = document.currentScript;
6
-
7
- // Save original WebSocket
8
- const NativeWebSocket = window.WebSocket;
9
-
10
- // Conditional logging helper - only log when localhost is in URL
11
- const isLocalhost = window.location.hostname === 'localhost' ||
12
- window.location.hostname === '127.0.0.1' ||
13
- window.location.hostname.endsWith('.localhost');
14
-
15
- function debugLog(...args) {
16
- if (isLocalhost) {
17
- console.log(...args);
18
- }
19
- }
20
-
21
- // Generate unique client ID
22
- function generateClientId() {
23
- return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
24
- }
25
-
26
- // Generate WebSocket key for handshake
27
- function generateWebSocketKey() {
28
- const bytes = new Uint8Array(16);
29
- crypto.getRandomValues(bytes);
30
- return btoa(String.fromCharCode(...bytes));
31
- }
32
-
33
- // Helper to validate WebSocket constructor arguments (mimics native behavior)
34
- function validateWebSocketArgs(url, protocols) {
35
- if (!url) {
36
- throw new DOMException("Failed to construct 'WebSocket': 1 argument required, but only 0 present.");
37
- }
38
-
39
- let parsedUrl;
40
- try {
41
- parsedUrl = new URL(url, window.location.href);
42
- } catch (e) {
43
- throw new DOMException(`Failed to construct 'WebSocket': The URL '${url}' is invalid.`);
44
- }
45
-
46
- if (parsedUrl.protocol !== 'ws:' && parsedUrl.protocol !== 'wss:') {
47
- throw new DOMException(`Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'. '${parsedUrl.protocol.slice(0, -1)}' is not allowed.`);
48
- }
49
-
50
- let normalizedProtocols = protocols;
51
- if (protocols !== undefined && protocols !== null) {
52
- if (typeof protocols === 'string') {
53
- normalizedProtocols = [protocols];
54
- } else if (Array.isArray(protocols)) {
55
- normalizedProtocols = protocols;
56
- } else {
57
- throw new DOMException("Failed to construct 'WebSocket': The subprotocol '" + protocols + "' is invalid.");
58
- }
59
-
60
- const seen = new Set();
61
- for (const protocol of normalizedProtocols) {
62
- if (typeof protocol !== 'string') {
63
- throw new DOMException("Failed to construct 'WebSocket': The subprotocol '" + protocol + "' is invalid.");
64
- }
65
- if (protocol === '') {
66
- throw new DOMException("Failed to construct 'WebSocket': The subprotocol '' is invalid.");
67
- }
68
- if (seen.has(protocol)) {
69
- throw new DOMException(`Failed to construct 'WebSocket': The subprotocol '${protocol}' is duplicated.`);
70
- }
71
- seen.add(protocol);
72
- }
73
- }
74
-
75
- return { parsedUrl, normalizedProtocols };
76
- }
77
-
78
- // WebSocket polyfill using Service Worker E2EE - extends EventTarget for native event handling
79
- class WebSocketPolyfill extends EventTarget {
80
- // WebSocket ready state constants (matching native WebSocket)
81
- static CONNECTING = 0;
82
- static OPEN = 1;
83
- static CLOSING = 2;
84
- static CLOSED = 3;
85
-
86
- constructor(url, protocols) {
87
- super(); // Initialize EventTarget
88
-
89
- // Validate arguments using native-like validation
90
- const { parsedUrl, normalizedProtocols } = validateWebSocketArgs(url, protocols);
91
-
92
- // Store original URL and protocols
93
- this._url = parsedUrl.href;
94
- this._protocols = normalizedProtocols;
95
- this._parsedUrl = parsedUrl;
96
-
97
- // Define read-only properties to match native WebSocket
98
- Object.defineProperty(this, 'url', {
99
- get: () => this._url,
100
- enumerable: true,
101
- configurable: true
102
- });
103
-
104
- Object.defineProperty(this, 'readyState', {
105
- get: () => this._readyState,
106
- enumerable: true,
107
- configurable: true
108
- });
109
-
110
- Object.defineProperty(this, 'bufferedAmount', {
111
- get: () => this._bufferedAmount,
112
- enumerable: true,
113
- configurable: true
114
- });
115
-
116
- Object.defineProperty(this, 'extensions', {
117
- get: () => this._extensions,
118
- enumerable: true,
119
- configurable: true
120
- });
121
-
122
- Object.defineProperty(this, 'protocol', {
123
- get: () => this._protocol,
124
- enumerable: true,
125
- configurable: true
126
- });
127
-
128
- // Internal state
129
- this._readyState = WebSocketPolyfill.CONNECTING;
130
- this._bufferedAmount = 0;
131
- this._extensions = "";
132
- this._protocol = "";
133
- this.binaryType = "blob";
134
-
135
- // Event handlers (use native-like pattern)
136
- this.onopen = null;
137
- this.onmessage = null;
138
- this.onerror = null;
139
- this.onclose = null;
140
-
141
- // Internal connection state
142
- this._clientId = generateClientId();
143
- this._connId = null;
144
- this._isClosed = false;
145
- this._wsKey = generateWebSocketKey();
146
- this._frameBuffer = new Uint8Array(0);
147
-
148
- // Setup and connect
149
- this._setupMessageListener();
150
- this._connect();
151
- }
152
-
153
- // Helper to send messages to Service Worker
154
- _postToServiceWorker(message) {
155
- navigator.serviceWorker.controller.postMessage({
156
- clientId: this._clientId,
157
- connId: this._connId,
158
- ...message
159
- });
160
- }
161
-
162
- _setupMessageListener() {
163
- // Register handler in global registry (single SW listener handles routing)
164
- swMessageHandlers.set(this._clientId, (data) => {
165
- switch (data.type) {
166
- case "SDK_CONNECT_SUCCESS":
167
- this._handleConnectSuccess(data);
168
- break;
169
- case "SDK_CONNECT_ERROR":
170
- this._handleConnectError(data);
171
- break;
172
- case "SDK_DATA":
173
- this._handleData(data);
174
- break;
175
- case "SDK_DATA_CLOSE":
176
- this._handleDataClose(data);
177
- break;
178
- case "SDK_SEND_ERROR":
179
- this._handleSendError(data);
180
- break;
181
- }
182
- });
183
- }
184
-
185
- _cleanupMessageListener() {
186
- swMessageHandlers.delete(this._clientId);
187
- }
188
-
189
- // Build HTTP upgrade request bytes (used for pipelining)
190
- _buildUpgradeRequest() {
191
- // Parse URL to get path with query parameters
192
- const path = (this._parsedUrl.pathname || "/") + (this._parsedUrl.search || "");
193
- const host = this._parsedUrl.host;
194
-
195
- // Build HTTP Upgrade request
196
- let upgradeRequest = `GET ${path} HTTP/1.1\r\n`;
197
- upgradeRequest += `Host: ${host}\r\n`;
198
- upgradeRequest += `Upgrade: websocket\r\n`;
199
- upgradeRequest += `Connection: Upgrade\r\n`;
200
- upgradeRequest += `Sec-WebSocket-Key: ${this._wsKey}\r\n`;
201
- upgradeRequest += `Sec-WebSocket-Version: 13\r\n`;
202
-
203
- if (this._protocols) {
204
- const protocolStr = Array.isArray(this._protocols)
205
- ? this._protocols.join(', ')
206
- : this._protocols;
207
- upgradeRequest += `Sec-WebSocket-Protocol: ${protocolStr}\r\n`;
208
- }
209
-
210
- upgradeRequest += `\r\n`;
211
- return upgradeRequest;
212
- }
213
-
214
- async _connect() {
215
- debugLog("[WebSocket Polyfill] Connecting via Service Worker SDK to:", this._url);
216
- try {
217
- // Extract and normalize hostname (already parsed in constructor)
218
- let hostname = this._parsedUrl.hostname;
219
-
220
- // Normalize punycode to lowercase (punycode is case-insensitive per RFC 3492)
221
- // This ensures XN--CW4B85OB9G becomes xn--cw4b85ob9g before processing
222
- hostname = hostname.toLowerCase();
223
-
224
- // Extract first label (keep lowercase for punycode conversion in Go backend)
225
- // Go backend will convert punycode->unicode and then uppercase
226
- const leaseName = hostname.split('.')[0];
227
-
228
- debugLog("[WebSocket Polyfill] Lease name:", leaseName);
229
-
230
- // Build upgrade request for pipelining (reduces RTT from 2 to 1)
231
- const upgradeRequest = this._buildUpgradeRequest();
232
- debugLog("[WebSocket Polyfill] Pipelining upgrade request with connect");
233
-
234
- // Set up for upgrade response before sending
235
- this._waitingForUpgrade = true;
236
- this._upgradeBuffer = new Uint8Array(0);
237
-
238
- // Wait for Service Worker to be ready
239
- await navigator.serviceWorker.ready;
240
-
241
- // Convert upgrade request to bytes for pipelining
242
- const encoder = new TextEncoder();
243
- const upgradeBytes = encoder.encode(upgradeRequest);
244
-
245
- // Send connect message with bundled upgrade request (pipelining)
246
- navigator.serviceWorker.controller.postMessage({
247
- type: "SDK_CONNECT",
248
- clientId: this._clientId,
249
- leaseName: leaseName,
250
- upgradeRequest: upgradeBytes, // Bundled for pipelining
251
- });
252
-
253
- } catch (error) {
254
- console.error("[WebSocket Polyfill] Failed to connect:", error);
255
- this._handleError(new Error(error));
256
- }
257
- }
258
-
259
- _handleConnectSuccess(data) {
260
- this._connId = data.connId;
261
-
262
- // Upgrade request was already pipelined with SDK_CONNECT
263
- // Just wait for upgrade response in _handleData
264
- debugLog("[WebSocket Polyfill] E2EE tunnel established, upgrade request already sent (pipelined)");
265
- }
266
-
267
- _handleConnectError(data) {
268
- console.error("[WebSocket Polyfill] Connection error:", data.error);
269
- this._handleError(new Error(data.error));
270
- }
271
-
272
- _handleData(data) {
273
- const uint8Array = data.data;
274
-
275
- // If waiting for upgrade response, buffer and parse HTTP response
276
- if (this._waitingForUpgrade) {
277
- // Append to buffer
278
- const newBuffer = new Uint8Array(this._upgradeBuffer.length + uint8Array.length);
279
- newBuffer.set(this._upgradeBuffer);
280
- newBuffer.set(uint8Array, this._upgradeBuffer.length);
281
- this._upgradeBuffer = newBuffer;
282
-
283
- // Try to parse HTTP response
284
- const decoder = new TextDecoder();
285
- const text = decoder.decode(this._upgradeBuffer);
286
-
287
- // Look for end of HTTP headers (\r\n\r\n)
288
- const headerEndIndex = text.indexOf('\r\n\r\n');
289
- if (headerEndIndex === -1) {
290
- // Not complete yet, keep buffering
291
- return;
292
- }
293
-
294
- // Parse HTTP response
295
- const headers = text.substring(0, headerEndIndex);
296
- debugLog("[WebSocket Polyfill] Received upgrade response:", headers);
297
-
298
- // Check if upgrade was successful
299
- if (!headers.includes('HTTP/1.1 101') && !headers.includes('HTTP/1.0 101')) {
300
- this._handleError(new Error("WebSocket upgrade failed: " + headers.split('\r\n')[0]));
301
- return;
302
- }
303
-
304
- // Extract protocol if present
305
- const protocolMatch = headers.match(/Sec-WebSocket-Protocol:\s*(\S+)/i);
306
- if (protocolMatch) {
307
- this._protocol = protocolMatch[1];
308
- }
309
-
310
- // Upgrade successful!
311
- this._waitingForUpgrade = false;
312
- this._readyState = WebSocketPolyfill.OPEN;
313
-
314
- debugLog("[WebSocket Polyfill] WebSocket connection established");
315
-
316
- // Fire onopen event (dispatchEvent will handle both onopen and listeners)
317
- this.dispatchEvent(new Event("open"));
318
-
319
- // If there's any data after the headers, process it as WebSocket frames
320
- const remainingBytes = this._upgradeBuffer.slice(headerEndIndex + 4);
321
- if (remainingBytes.length > 0) {
322
- this._processWebSocketFrames(remainingBytes);
323
- }
324
- this._upgradeBuffer = null;
325
-
326
- return;
327
- }
328
-
329
- // Normal WebSocket data - process frames
330
- this._processWebSocketFrames(uint8Array);
331
- }
332
-
333
- _processWebSocketFrames(data) {
334
- // For now, assume data is the payload (we'll implement frame parsing if needed)
335
- // WebSocket frames from server are not masked
336
-
337
- if (this._readyState !== WebSocketPolyfill.OPEN) return;
338
-
339
- // Append incoming data to frame buffer
340
- const newBuffer = new Uint8Array(this._frameBuffer.length + data.length);
341
- newBuffer.set(this._frameBuffer);
342
- newBuffer.set(data, this._frameBuffer.length);
343
- this._frameBuffer = newBuffer;
344
-
345
- // Process all complete frames in buffer
346
- while (this._frameBuffer.length >= 2) {
347
- const byte1 = this._frameBuffer[0];
348
- const byte2 = this._frameBuffer[1];
349
-
350
- const fin = (byte1 & 0x80) !== 0;
351
- const opcode = byte1 & 0x0F;
352
- const masked = (byte2 & 0x80) !== 0;
353
- let payloadLen = byte2 & 0x7F;
354
-
355
- let offset = 2;
356
-
357
- // Handle extended payload length
358
- if (payloadLen === 126) {
359
- if (this._frameBuffer.length < 4) return; // Need more data
360
- payloadLen = (this._frameBuffer[2] << 8) | this._frameBuffer[3];
361
- offset = 4;
362
- } else if (payloadLen === 127) {
363
- if (this._frameBuffer.length < 10) return; // Need more data
364
- // For simplicity, assuming payload < 2^32
365
- payloadLen = (this._frameBuffer[6] << 24) | (this._frameBuffer[7] << 16) | (this._frameBuffer[8] << 8) | this._frameBuffer[9];
366
- offset = 10;
367
- }
368
-
369
- // Server messages should not be masked
370
- if (masked) {
371
- offset += 4; // Skip mask key
372
- }
373
-
374
- if (this._frameBuffer.length < offset + payloadLen) {
375
- // Incomplete frame, wait for more data
376
- return;
377
- }
378
-
379
- const payload = this._frameBuffer.slice(offset, offset + payloadLen);
380
-
381
- // Remove processed frame from buffer
382
- this._frameBuffer = this._frameBuffer.slice(offset + payloadLen);
383
-
384
- // Handle different opcodes
385
- if (opcode === 0x01) {
386
- // Text frame
387
- const text = new TextDecoder().decode(payload);
388
- const event = new MessageEvent("message", {
389
- data: text,
390
- origin: this._parsedUrl.origin,
391
- });
392
- this.dispatchEvent(event);
393
- } else if (opcode === 0x02) {
394
- // Binary frame
395
- let eventData;
396
- if (this.binaryType === "blob") {
397
- eventData = new Blob([payload]);
398
- } else {
399
- eventData = payload.buffer;
400
- }
401
- const event = new MessageEvent("message", {
402
- data: eventData,
403
- origin: this._parsedUrl.origin,
404
- });
405
- this.dispatchEvent(event);
406
- } else if (opcode === 0x08) {
407
- // Close frame
408
- let code = 1000;
409
- let reason = "";
410
- if (payload.length >= 2) {
411
- code = (payload[0] << 8) | payload[1];
412
- if (payload.length > 2) {
413
- reason = new TextDecoder().decode(payload.slice(2));
414
- }
415
- }
416
- this._handleDataClose({ code, reason });
417
- } else if (opcode === 0x09) {
418
- // Ping - send pong
419
- this._sendPong(payload);
420
- } else if (opcode === 0x0A) {
421
- // Pong - ignore
422
- }
423
- }
424
- }
425
-
426
- _sendPong(payload) {
427
- // Send pong frame
428
- const frame = this._createWebSocketFrame(0x0A, payload);
429
- this._postToServiceWorker({
430
- type: "SDK_SEND",
431
- data: frame,
432
- });
433
- }
434
-
435
- _createWebSocketFrame(opcode, payload) {
436
- // Create WebSocket frame (client to server, must be masked)
437
- const payloadLen = payload.length;
438
- let frameHeader;
439
- let offset;
440
-
441
- if (payloadLen < 126) {
442
- frameHeader = new Uint8Array(2 + 4 + payloadLen);
443
- frameHeader[0] = 0x80 | opcode; // FIN + opcode
444
- frameHeader[1] = 0x80 | payloadLen; // MASK + length
445
- offset = 2;
446
- } else if (payloadLen < 65536) {
447
- frameHeader = new Uint8Array(4 + 4 + payloadLen);
448
- frameHeader[0] = 0x80 | opcode;
449
- frameHeader[1] = 0x80 | 126;
450
- frameHeader[2] = (payloadLen >> 8) & 0xFF;
451
- frameHeader[3] = payloadLen & 0xFF;
452
- offset = 4;
453
- } else {
454
- frameHeader = new Uint8Array(10 + 4 + payloadLen);
455
- frameHeader[0] = 0x80 | opcode;
456
- frameHeader[1] = 0x80 | 127;
457
- // Simplified: assuming payload < 2^32
458
- frameHeader[2] = 0;
459
- frameHeader[3] = 0;
460
- frameHeader[4] = 0;
461
- frameHeader[5] = 0;
462
- frameHeader[6] = (payloadLen >> 24) & 0xFF;
463
- frameHeader[7] = (payloadLen >> 16) & 0xFF;
464
- frameHeader[8] = (payloadLen >> 8) & 0xFF;
465
- frameHeader[9] = payloadLen & 0xFF;
466
- offset = 10;
467
- }
468
-
469
- // Generate masking key
470
- const maskKey = new Uint8Array(4);
471
- crypto.getRandomValues(maskKey);
472
- frameHeader.set(maskKey, offset);
473
-
474
- // Mask payload
475
- const maskedPayload = new Uint8Array(payloadLen);
476
- for (let i = 0; i < payloadLen; i++) {
477
- maskedPayload[i] = payload[i] ^ maskKey[i % 4];
478
- }
479
-
480
- frameHeader.set(maskedPayload, offset + 4);
481
- return frameHeader;
482
- }
483
-
484
- _handleDataClose(data) {
485
- if (this._isClosed) return;
486
-
487
- const code = data.code || 1000;
488
- const reason = data.reason || "";
489
-
490
- debugLog(
491
- "[WebSocket Polyfill] Connection closed, code:",
492
- code,
493
- "reason:",
494
- reason
495
- );
496
-
497
- this._isClosed = true;
498
- this._readyState = WebSocketPolyfill.CLOSED;
499
-
500
- const event = new CloseEvent("close", {
501
- code: code,
502
- reason: reason,
503
- wasClean: code === 1000,
504
- });
505
-
506
- // dispatchEvent will handle both onclose and event listeners
507
- this.dispatchEvent(event);
508
- }
509
-
510
- _handleSendError(data) {
511
- console.error("[WebSocket Polyfill] Send error:", data.error);
512
- this._handleError(new Error(data.error));
513
- }
514
-
515
- _handleError(error) {
516
- console.error("[WebSocket Polyfill] Error occurred:", error);
517
-
518
- const event = new Event("error");
519
- event.error = error;
520
-
521
- // dispatchEvent will handle both onerror and event listeners
522
- this.dispatchEvent(event);
523
-
524
- // Close connection after error
525
- if (!this._isClosed) {
526
- this._handleDataClose({ code: 1006, reason: error.message });
527
- }
528
- }
529
-
530
- send(data) {
531
- if (this._readyState !== WebSocketPolyfill.OPEN) {
532
- throw new DOMException("Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.");
533
- }
534
-
535
- if (!this._connId) {
536
- throw new Error("Connection not established");
537
- }
538
-
539
- try {
540
- // Convert data to Uint8Array
541
- let bytes;
542
- let opcode;
543
-
544
- if (typeof data === "string") {
545
- const encoder = new TextEncoder();
546
- bytes = encoder.encode(data);
547
- opcode = 0x01; // Text frame
548
- } else if (data instanceof ArrayBuffer) {
549
- bytes = new Uint8Array(data);
550
- opcode = 0x02; // Binary frame
551
- } else if (data instanceof Uint8Array) {
552
- bytes = data;
553
- opcode = 0x02; // Binary frame
554
- } else if (data instanceof Blob) {
555
- // Handle Blob asynchronously
556
- data.arrayBuffer().then(arrayBuffer => {
557
- const bytes = new Uint8Array(arrayBuffer);
558
- const frame = this._createWebSocketFrame(0x02, bytes);
559
- this._postToServiceWorker({
560
- type: "SDK_SEND",
561
- data: frame,
562
- });
563
- });
564
- return;
565
- } else {
566
- throw new Error("Unsupported data type");
567
- }
568
-
569
- // Create WebSocket frame
570
- const frame = this._createWebSocketFrame(opcode, bytes);
571
-
572
- // Send to Service Worker
573
- this._postToServiceWorker({
574
- type: "SDK_SEND",
575
- data: frame,
576
- });
577
- } catch (error) {
578
- console.error("[WebSocket Polyfill] Failed to send message:", error);
579
- this._handleError(error);
580
- }
581
- }
582
-
583
- close(code = 1000, reason = "") {
584
- if (this._isClosed || this._readyState === WebSocketPolyfill.CLOSING) {
585
- return;
586
- }
587
-
588
- debugLog(
589
- "[WebSocket Polyfill] Client initiated close, code:",
590
- code,
591
- "reason:",
592
- reason
593
- );
594
-
595
- this._readyState = WebSocketPolyfill.CLOSING;
596
-
597
- if (this._connId && this._readyState === WebSocketPolyfill.OPEN) {
598
- // Send WebSocket close frame
599
- const reasonBytes = new TextEncoder().encode(reason);
600
- const payload = new Uint8Array(2 + reasonBytes.length);
601
- payload[0] = (code >> 8) & 0xFF;
602
- payload[1] = code & 0xFF;
603
- payload.set(reasonBytes, 2);
604
-
605
- const frame = this._createWebSocketFrame(0x08, payload);
606
-
607
- this._postToServiceWorker({
608
- type: "SDK_SEND",
609
- data: frame,
610
- });
611
- }
612
-
613
- // Close SDK connection
614
- if (this._connId) {
615
- this._postToServiceWorker({
616
- type: "SDK_CLOSE",
617
- });
618
- }
619
-
620
- // Cleanup message listener
621
- this._cleanupMessageListener();
622
-
623
- // Handle close locally
624
- this._handleDataClose({ code, reason });
625
- }
626
-
627
- // Override dispatchEvent to handle onXXX handlers (EventTarget provides addEventListener/removeEventListener)
628
- dispatchEvent(event) {
629
- // Call onXXX handler first (matches native WebSocket behavior)
630
- const handlerName = 'on' + event.type;
631
- if (typeof this[handlerName] === 'function') {
632
- try {
633
- this[handlerName].call(this, event);
634
- } catch (e) {
635
- console.error('Error in event handler:', e);
636
- }
637
- }
638
-
639
- // Use native EventTarget.dispatchEvent for event listeners
640
- return super.dispatchEvent(event);
641
- }
642
- }
643
-
644
- // Check if URL is same-origin
645
- function isSameOrigin(url) {
646
- try {
647
- const wsUrl = new URL(url, window.location.href);
648
- const currentOrigin = window.location.origin;
649
-
650
- // Convert ws:// to http:// and wss:// to https:// for comparison
651
- let wsOrigin = wsUrl.origin;
652
- if (wsUrl.protocol === "ws:") {
653
- wsOrigin = wsOrigin.replace("ws:", "http:");
654
- } else if (wsUrl.protocol === "wss:") {
655
- wsOrigin = wsOrigin.replace("wss:", "https:");
656
- }
657
-
658
- return wsOrigin === currentOrigin;
659
- } catch (e) {
660
- return false;
661
- }
662
- }
663
-
664
- // Replace WebSocket with a simple factory function (zero overhead after construction)
665
- window.WebSocket = function WebSocket(url, protocols) {
666
- // Use polyfill for same-origin, native for cross-origin
667
- if (isSameOrigin(url)) {
668
- debugLog(
669
- "[WebSocket Polyfill] Using E2EE polyfill for same-origin connection:",
670
- url
671
- );
672
- return new WebSocketPolyfill(url, protocols);
673
- } else {
674
- debugLog(
675
- "[WebSocket Polyfill] Using native WebSocket for cross-origin connection:",
676
- url
677
- );
678
- return new NativeWebSocket(url, protocols);
679
- }
680
- };
681
-
682
- // Copy static constants from WebSocketPolyfill
683
- // Essential for: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
684
- window.WebSocket.CONNECTING = WebSocketPolyfill.CONNECTING; // 0
685
- window.WebSocket.OPEN = WebSocketPolyfill.OPEN; // 1
686
- window.WebSocket.CLOSING = WebSocketPolyfill.CLOSING; // 2
687
- window.WebSocket.CLOSED = WebSocketPolyfill.CLOSED; // 3
688
-
689
- debugLog("[WebSocket Polyfill] Initialized with E2EE and WebSocket protocol support");
690
-
691
- // Remove the polyfill script tag after initialization
692
- if (currentScript && currentScript.parentNode) {
693
- currentScript.parentNode.removeChild(currentScript);
694
- debugLog("[WebSocket Polyfill] Script tag removed");
695
- }
696
-})();
cmd/webclient/portal.jpg
Binary files a/cmd/webclient/portal.jpg and /dev/null differ
cmd/webclient/portal.mp4
Binary files a/cmd/webclient/portal.mp4 and /dev/null differ
cmd/webclient/sdk_js.go
deleted
-22
@@ -1,22 +0,0 @@
1
-package main
2
-
3
-import (
4
- "context"
5
- "io"
6
-
7
- "gosuda.org/portal/cmd/webclient/wsjs"
8
-)
9
-
10
-// WebSocketDialerJS creates a WebSocket dialer function for JavaScript/WebAssembly environment
11
-func WebSocketDialerJS() func(context.Context, string) (io.ReadWriteCloser, error) {
12
- return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
13
- // Use the wsjs package to create a WebSocket connection
14
- conn, err := wsjs.Dial(url)
15
- if err != nil {
16
- return nil, err
17
- }
18
-
19
- // Wrap the WebSocket connection with WsStream for io.ReadWriteCloser interface
20
- return wsjs.NewWsStream(conn), nil
21
- }
22
-}
cmd/webclient/service-worker.js
deleted
-808
@@ -1,808 +0,0 @@
1
-//const wasm_exec_URL = "https://cdn.jsdelivr.net/gh/golang/go@go1.25.3/lib/wasm/wasm_exec.js";
2
-const BASE_PATH = self.location.origin || "";
3
-let wasmManifest = null;
4
-let wasmManifestPromise = null;
5
-
6
-// Debug mode detection (disable verbose logging in production)
7
-const DEBUG_MODE = self.location.hostname === 'localhost' ||
8
- self.location.hostname === '127.0.0.1' ||
9
- self.location.hostname.endsWith('.localhost');
10
-
11
-function debugLog(...args) {
12
- if (DEBUG_MODE) {
13
- console.log(...args);
14
- }
15
-}
16
-
17
-// Load manifest from backend (decouples SW from Go template)
18
-async function loadManifest() {
19
- if (wasmManifest) {
20
- return wasmManifest;
21
- }
22
-
23
- if (wasmManifestPromise) {
24
- return wasmManifestPromise;
25
- }
26
-
27
- wasmManifestPromise = (async () => {
28
- try {
29
- debugLog("[SW] Fetching WASM manifest...");
30
- const response = await fetch("/frontend/manifest.json", { cache: "no-cache" });
31
-
32
- if (!response.ok) {
33
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
34
- }
35
-
36
- const manifest = await response.json();
37
- wasmManifest = manifest;
38
-
39
- // Expose bootstrap servers to WASM runtime (service worker global)
40
- if (manifest.bootstraps) {
41
- self.__BOOTSTRAP_SERVERS__ = manifest.bootstraps;
42
- debugLog("[SW] Bootstraps loaded from manifest:", manifest.bootstraps);
43
- }
44
-
45
- debugLog("[SW] Manifest loaded successfully:", manifest);
46
- return manifest;
47
- } catch (error) {
48
- console.error("[SW] Failed to load WASM manifest:", error);
49
-
50
- // Fallback manifest
51
- wasmManifest = {
52
- wasmFile: "main.wasm",
53
- wasmUrl: null
54
- };
55
- console.warn("[SW] Using fallback manifest:", wasmManifest);
56
- return wasmManifest;
57
- } finally {
58
- wasmManifestPromise = null;
59
- }
60
- })();
61
-
62
- return wasmManifestPromise;
63
-}
64
-
65
-let wasm_exec_URL = BASE_PATH + "/frontend/wasm_exec.js";
66
-try {
67
- if (new URL(BASE_PATH).protocol === "http:") {
68
- wasm_exec_URL = "/frontend/wasm_exec.js";
69
- }
70
- debugLog("[SW] Loading wasm_exec.js from:", wasm_exec_URL);
71
- importScripts(wasm_exec_URL);
72
- debugLog("[SW] wasm_exec.js loaded successfully");
73
-} catch (error) {
74
- console.error("[SW] Failed to load wasm_exec.js:", error);
75
- throw new Error(`Failed to load wasm_exec.js from ${wasm_exec_URL}: ${error.message}`);
76
-}
77
-
78
-let loading = false;
79
-let initError = null;
80
-let _lastReload = Date.now();
81
-let initPromise = null; // Prevent concurrent initialization
82
-
83
-// Service Worker version for debugging
84
-const SW_VERSION = "1.0.0";
85
-
86
-debugLog(`[SW] Service Worker v${SW_VERSION} loaded`);
87
-
88
-// Service Worker readiness stages
89
-const ReadinessStage = {
90
- UNINITIALIZED: 0, // No handlers available
91
- WASM_LOADING: 1, // Loading in progress
92
- WASM_LOADED: 2, // __go_jshttp available
93
- READY: 3, // Both __go_jshttp and __sdk_message_handler available (fully operational)
94
-};
95
-
96
-let declaredStage = ReadinessStage.UNINITIALIZED; // What we think the stage is
97
-
98
-// Check handler availability
99
-function areHandlersAvailable() {
100
- return {
101
- http: typeof __go_jshttp !== "undefined",
102
- sdk: typeof __sdk_message_handler !== "undefined"
103
- };
104
-}
105
-
106
-// Compute actual stage based on runtime state
107
-function getCurrentStage() {
108
- if (loading) {
109
- return ReadinessStage.WASM_LOADING;
110
- }
111
-
112
- const { http, sdk } = areHandlersAvailable();
113
-
114
- if (http && sdk) {
115
- return ReadinessStage.READY;
116
- } else if (http && !sdk) {
117
- return ReadinessStage.WASM_LOADED;
118
- } else {
119
- return ReadinessStage.UNINITIALIZED;
120
- }
121
-}
122
-
123
-// Check if error is recoverable (handler missing) or fatal (other errors)
124
-function isRecoverableError(error, currentStage, targetStage) {
125
- // Handlers missing = recoverable (can retry infinitely)
126
- if (targetStage === ReadinessStage.READY) {
127
- const { http, sdk } = areHandlersAvailable();
128
- if (!http || !sdk) {
129
- return true;
130
- }
131
- }
132
-
133
- // Check for fatal errors that we should not retry infinitely
134
- const errorMsg = error.message.toLowerCase();
135
-
136
- // Fatal errors - should throw immediately
137
- if (errorMsg.includes('out of memory') ||
138
- errorMsg.includes('rangeerror') ||
139
- errorMsg.includes('404') ||
140
- errorMsg.includes('403') ||
141
- errorMsg.includes('invalid wasm') ||
142
- errorMsg.includes('bad magic number')) {
143
- return false;
144
- }
145
-
146
- // Temporary/recoverable errors - can retry
147
- if (errorMsg.includes('timeout') ||
148
- errorMsg.includes('network') ||
149
- errorMsg.includes('fetch') ||
150
- errorMsg.includes('offline')) {
151
- return true;
152
- }
153
-
154
- // Default: if handlers are missing, it's recoverable
155
- return currentStage < targetStage;
156
-}
157
-
158
-// Simple recovery system: check state → recover if needed → execute
159
-async function ensureReady(targetStage = ReadinessStage.READY) {
160
- let attempt = 0;
161
-
162
- while (true) {
163
- // Step 1: Check current state
164
- const currentStage = getCurrentStage();
165
- if (currentStage >= targetStage) {
166
- debugLog(`[SW] Already at stage ${currentStage}, ready`);
167
- return;
168
- }
169
-
170
- // Step 2: Recover to desired state
171
- try {
172
- if (attempt > 0) {
173
- const delay = Math.min(100 * Math.pow(2, attempt - 1), 5000);
174
- debugLog(`[SW] Retry ${attempt + 1} after ${delay}ms...`);
175
- await new Promise(resolve => setTimeout(resolve, delay));
176
- }
177
-
178
- // Reset state before recovery
179
- declaredStage = ReadinessStage.UNINITIALIZED;
180
- loading = false;
181
- initError = null;
182
-
183
- // Load WASM
184
- await ensureStage(targetStage);
185
-
186
- // Verify success
187
- const finalStage = getCurrentStage();
188
- if (finalStage >= targetStage) {
189
- console.log(`[SW] Recovery successful, reached stage ${finalStage}`);
190
- return;
191
- }
192
-
193
- throw new Error(`Recovery incomplete: expected ${targetStage}, got ${finalStage}`);
194
- } catch (error) {
195
- attempt++;
196
- console.warn(`[SW] Recovery attempt ${attempt} failed:`, error.message);
197
-
198
- // Check if this is a fatal error
199
- const currentStageNow = getCurrentStage();
200
- if (!isRecoverableError(error, currentStageNow, targetStage)) {
201
- console.error(`[SW] Fatal error, cannot recover:`, error);
202
- throw error;
203
- }
204
-
205
- // Continue loop for recoverable errors
206
- }
207
- }
208
-}
209
-
210
-// Sync declared stage with actual stage
211
-function syncStage() {
212
- const actualStage = getCurrentStage();
213
- if (declaredStage !== actualStage) {
214
- debugLog(`[SW] Stage sync: ${declaredStage} -> ${actualStage}`);
215
- declaredStage = actualStage;
216
- }
217
- return actualStage;
218
-}
219
-
220
-// Mobile detection and optimization
221
-const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
222
-const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
223
-const isAndroid = /Android/.test(navigator.userAgent);
224
-const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
225
-
226
-debugLog(`[SW] Platform: ${isMobile ? 'Mobile' : 'Desktop'}, iOS: ${isIOS}, Android: ${isAndroid}, Safari: ${isSafari}`);
227
-
228
-// Network utilities with retry logic
229
-async function fetchWithRetry(url, options = {}, maxRetries = 3) {
230
- let lastError;
231
-
232
- for (let i = 0; i < maxRetries; i++) {
233
- try {
234
- console.log(`[SW] Fetching ${url} (attempt ${i + 1}/${maxRetries})`);
235
- const response = await fetch(url, options);
236
-
237
- if (!response.ok) {
238
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
239
- }
240
-
241
- return response;
242
- } catch (error) {
243
- lastError = error;
244
- console.warn(`[SW] Fetch attempt ${i + 1} failed:`, error.message);
245
-
246
- // Don't retry on certain errors
247
- if (error.message.includes('404') || error.message.includes('403')) {
248
- throw error;
249
- }
250
-
251
- // Wait before retry (exponential backoff)
252
- if (i < maxRetries - 1) {
253
- const delay = Math.min(1000 * Math.pow(2, i), 5000);
254
- console.log(`[SW] Retrying in ${delay}ms...`);
255
- await new Promise(resolve => setTimeout(resolve, delay));
256
- }
257
- }
258
- }
259
-
260
- throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
261
-}
262
-
263
-// Send error to all clients
264
-async function notifyClientsOfError(error) {
265
- const clients = await self.clients.matchAll();
266
- const errorMessage = {
267
- type: "SW_ERROR",
268
- error: {
269
- name: error.name,
270
- message: error.message,
271
- stack: error.stack,
272
- },
273
- };
274
-
275
- for (const client of clients) {
276
- client.postMessage(errorMessage);
277
- }
278
-}
279
-
280
-// Stage-based initialization with automatic dependency resolution
281
-async function ensureStage(targetStage) {
282
- // Sync stage before checking
283
- const current = syncStage();
284
- debugLog(`[SW] Ensuring stage: ${targetStage}, current: ${current}`);
285
-
286
- // Already at or past the target stage
287
- if (current >= targetStage) {
288
- debugLog(`[SW] Already at stage ${current}, no action needed`);
289
- return true;
290
- }
291
-
292
- // Recursive dependency resolution
293
- switch (targetStage) {
294
- case ReadinessStage.WASM_LOADED:
295
- await ensureWASMLoaded();
296
- break;
297
-
298
- case ReadinessStage.READY:
299
- // First ensure WASM is loaded
300
- await ensureStage(ReadinessStage.WASM_LOADED);
301
- await ensureHandlersRegistered();
302
- break;
303
- }
304
-
305
- // Verify we reached the target stage
306
- const finalStage = syncStage();
307
- if (finalStage < targetStage) {
308
- throw new Error(`Failed to reach stage ${targetStage}, stuck at ${finalStage}`);
309
- }
310
-
311
- return true;
312
-}
313
-
314
-// Ensure WASM is loaded
315
-async function ensureWASMLoaded() {
316
- // Verify current stage
317
- const current = syncStage();
318
-
319
- // If already loaded, return immediately
320
- if (current >= ReadinessStage.WASM_LOADED) {
321
- debugLog("[SW] WASM already loaded (verified by handler check)");
322
- return true;
323
- }
324
-
325
- // Prevent concurrent initialization attempts
326
- if (initPromise) {
327
- debugLog("[SW] Init already in progress, reusing existing promise");
328
- await initPromise;
329
- return syncStage() >= ReadinessStage.WASM_LOADED;
330
- }
331
-
332
- if (loading) {
333
- debugLog("[SW] Init already loading, waiting...");
334
- // Wait for loading to complete
335
- while (loading && syncStage() < ReadinessStage.WASM_LOADED) {
336
- await new Promise(resolve => setTimeout(resolve, 100));
337
- }
338
- return syncStage() >= ReadinessStage.WASM_LOADED;
339
- }
340
-
341
- loading = true;
342
- declaredStage = ReadinessStage.WASM_LOADING;
343
-
344
- initPromise = (async () => {
345
- try {
346
- debugLog("[SW] Starting WASM initialization...");
347
-
348
- await runWASM();
349
-
350
- // Stage will be auto-synced based on handler availability
351
- initError = null;
352
-
353
- // Verify we actually reached the expected stage
354
- const finalStage = syncStage();
355
- debugLog(`[SW] WASM initialization complete, stage: ${finalStage}`);
356
- } catch (error) {
357
- console.error("[SW] Error initializing WASM:", error);
358
- initError = error;
359
- declaredStage = ReadinessStage.UNINITIALIZED;
360
- loading = false;
361
- await notifyClientsOfError(error);
362
- throw error;
363
- } finally {
364
- initPromise = null;
365
- // Don't reset loading here - leave it true until WASM exits
366
- }
367
- })();
368
-
369
- await initPromise;
370
- return syncStage() >= ReadinessStage.WASM_LOADED;
371
-}
372
-
373
-// Ensure handlers are registered
374
-async function ensureHandlersRegistered() {
375
- // Verify current stage
376
- const current = syncStage();
377
-
378
- // Check if handlers exist
379
- if (current >= ReadinessStage.READY) {
380
- debugLog("[SW] Handlers already registered (verified by handler check)");
381
- return true;
382
- }
383
-
384
- debugLog("[SW] Handlers not registered, waiting...");
385
-
386
- // Wait for handlers to be registered (max 10 seconds)
387
- let waitCount = 0;
388
- const maxWait = 100;
389
-
390
- while (waitCount < maxWait) {
391
- const stage = syncStage();
392
- if (stage >= ReadinessStage.READY) {
393
- debugLog("[SW] Handlers registered successfully");
394
- return true;
395
- }
396
-
397
- await new Promise(resolve => setTimeout(resolve, 100));
398
- waitCount++;
399
- }
400
-
401
- // If handlers still not available, WASM might have failed
402
- console.warn("[SW] Handlers not available after waiting, WASM may need reloading");
403
- declaredStage = ReadinessStage.UNINITIALIZED;
404
- loading = false;
405
-
406
- // Retry WASM load
407
- return await ensureWASMLoaded();
408
-}
409
-
410
-// Legacy init function for backward compatibility - with infinite retry
411
-async function init() {
412
- return await ensureReady(ReadinessStage.READY);
413
-}
414
-
415
-async function runWASM() {
416
- // Check actual runtime state, not just if handler exists
417
- const currentStage = getCurrentStage();
418
- if (currentStage >= ReadinessStage.WASM_LOADED) {
419
- debugLog("[SW] WASM already loaded and verified");
420
- return;
421
- }
422
-
423
- try {
424
- // Ensure manifest is loaded
425
- const manifest = await loadManifest();
426
-
427
- // Determine WASM URL from manifest
428
- let wasm_URL;
429
- if (manifest.wasmUrl && new URL(manifest.wasmUrl).protocol !== "http:") {
430
- wasm_URL = manifest.wasmUrl;
431
- } else {
432
- wasm_URL = `/frontend/${manifest.wasmFile}`;
433
- }
434
- debugLog("[SW] WASM URL:", wasm_URL);
435
-
436
- // Create Go runtime
437
- const go = new Go();
438
-
439
- // Fetch WASM file with retry logic
440
- debugLog("[SW] Fetching WASM file...");
441
- let instance;
442
-
443
- // Set timeout for WASM instantiation (especially important on mobile)
444
- const instantiateTimeout = isMobile ? 30000 : 15000; // 30s mobile, 15s desktop
445
-
446
- try {
447
- // Use compileStreaming if available (most efficient)
448
- if (WebAssembly.compileStreaming) {
449
- const response = await fetchWithRetry(wasm_URL, {}, isMobile ? 5 : 3);
450
-
451
- // Check Content-Type before streaming
452
- const contentType = response.headers.get('content-type') || '';
453
- debugLog("[SW] WASM response Content-Type:", contentType);
454
-
455
- if (contentType.includes('text/html')) {
456
- throw new Error(
457
- `Received HTML instead of WASM file. This usually means Service Worker is not properly intercepting requests. ` +
458
- `Content-Type: ${contentType}, URL: ${wasm_URL}`
459
- );
460
- }
461
-
462
- debugLog("[SW] WASM file fetched, size:", response.headers.get('content-length'), "bytes");
463
-
464
- // Use instantiateStreaming for optimal performance
465
- const instantiatePromise = WebAssembly.instantiateStreaming(
466
- Promise.resolve(response),
467
- go.importObject
468
- );
469
-
470
- const timeoutPromise = new Promise((_, reject) =>
471
- setTimeout(() => reject(new Error(`WebAssembly instantiation timeout after ${instantiateTimeout}ms`)), instantiateTimeout)
472
- );
473
-
474
- instance = await Promise.race([instantiatePromise, timeoutPromise]);
475
- debugLog("[SW] WebAssembly instantiated successfully via streaming");
476
- }
477
- } catch (streamError) {
478
- // Fallback to traditional instantiate
479
- console.warn("[SW] compileStreaming failed, falling back to traditional method:", streamError.message);
480
-
481
- const response = await fetchWithRetry(wasm_URL, {}, isMobile ? 5 : 3);
482
-
483
- // Check Content-Type to detect if we got HTML instead of WASM
484
- const contentType = response.headers.get('content-type') || '';
485
- debugLog("[SW] WASM response Content-Type:", contentType);
486
-
487
- if (contentType.includes('text/html')) {
488
- throw new Error(
489
- `Received HTML instead of WASM file. Content-Type: ${contentType}, URL: ${wasm_URL}`
490
- );
491
- }
492
-
493
- debugLog("[SW] WASM file fetched, size:", response.headers.get('content-length'), "bytes");
494
-
495
- const wasm_file = await response.arrayBuffer();
496
- debugLog("[SW] WASM ArrayBuffer size:", wasm_file.byteLength, "bytes");
497
-
498
- // Additional validation: Check WASM magic number (0x00 0x61 0x73 0x6d)
499
- const magicNumber = new Uint8Array(wasm_file, 0, 4);
500
- if (magicNumber[0] !== 0x00 || magicNumber[1] !== 0x61 ||
501
- magicNumber[2] !== 0x73 || magicNumber[3] !== 0x6d) {
502
- // Try to detect if it's HTML
503
- const decoder = new TextDecoder();
504
- const firstBytes = decoder.decode(new Uint8Array(wasm_file, 0, Math.min(100, wasm_file.byteLength)));
505
-
506
- if (firstBytes.includes('<!DOCTYPE') || firstBytes.includes('<html>')) {
507
- throw new Error(
508
- `Received HTML document instead of WASM file. ` +
509
- `This indicates Service Worker is not active or not intercepting requests properly. ` +
510
- `First bytes: ${firstBytes.substring(0, 50)}...`
511
- );
512
- } else {
513
- throw new Error(
514
- `Invalid WASM file (bad magic number). ` +
515
- `Expected: [0x00, 0x61, 0x73, 0x6d], Got: [${Array.from(magicNumber).map(b => '0x' + b.toString(16).padStart(2, '0')).join(', ')}]`
516
- );
517
- }
518
- }
519
- debugLog("[SW] WASM magic number validated");
520
-
521
- // Instantiate WebAssembly with timeout
522
- debugLog("[SW] Instantiating WebAssembly...");
523
-
524
- const instantiatePromise = WebAssembly.instantiate(wasm_file, go.importObject);
525
- const timeoutPromise = new Promise((_, reject) =>
526
- setTimeout(() => reject(new Error(`WebAssembly instantiation timeout after ${instantiateTimeout}ms`)), instantiateTimeout)
527
- );
528
-
529
- instance = await Promise.race([instantiatePromise, timeoutPromise]);
530
- debugLog("[SW] WebAssembly instantiated successfully");
531
- }
532
-
533
- const onExit = () => {
534
- console.warn("[SW] Go Program Exited - handlers will be undefined");
535
- __go_jshttp = undefined;
536
- __sdk_message_handler = undefined;
537
- loading = false;
538
- initError = null;
539
- syncStage(); // Auto-sync to UNINITIALIZED
540
- };
541
-
542
- // Run Go program
543
- debugLog("[SW] Running Go program...");
544
- go.run(instance.instance)
545
- .then(onExit)
546
- .catch((error) => {
547
- console.error("[SW] Go Program Runtime Error:", error);
548
- onExit();
549
- });
550
-
551
- debugLog("[SW] WASM initialization completed successfully");
552
- } catch (error) {
553
- console.error("[SW] WASM initialization failed at:", error.stack || error);
554
- console.error("[SW] Error details:", {
555
- name: error.name,
556
- message: error.message,
557
- stack: error.stack
558
- });
559
-
560
- // Check for specific error types
561
- let errorType = "unknown";
562
- let userMessage = error.message;
563
-
564
- if (error.message.includes("memory") || error.message.includes("RangeError")) {
565
- errorType = "out_of_memory";
566
- userMessage = "Not enough memory to load application. Please close other tabs and try again.";
567
- console.error("[SW] Out of memory error detected");
568
- } else if (error.message.includes("timeout")) {
569
- errorType = "timeout";
570
- userMessage = "Loading timed out. Please check your connection and try again.";
571
- console.error("[SW] Timeout error detected");
572
- } else if (error.message.includes("offline") || error.message.includes("Failed to fetch")) {
573
- errorType = "network";
574
- userMessage = "Network error. Please check your connection.";
575
- console.error("[SW] Network error detected");
576
- } else if (error.message.includes("HTML")) {
577
- errorType = "service_worker_not_active";
578
- userMessage = "Service Worker not active. Please refresh the page.";
579
- console.error("[SW] Service Worker activation issue detected");
580
- }
581
-
582
- throw new Error(`WASM Initialization (${errorType}): ${userMessage}`);
583
- }
584
-}
585
-
586
-self.addEventListener("install", (e) => {
587
- debugLog("[SW] Install event triggered");
588
-
589
- e.waitUntil(
590
- (async () => {
591
- try {
592
- await init();
593
- // Only skipWaiting if initialization succeeded
594
- // WARNING: skipWaiting() can cause version mismatch issues
595
- // Consider removing this in production if updates can wait for page reload
596
- await self.skipWaiting();
597
- debugLog("[SW] Skipped waiting phase");
598
- } catch (error) {
599
- console.error("[SW] Installation failed:", error);
600
- // Don't skipWaiting on error - let the old SW keep running
601
- throw error;
602
- }
603
- })()
604
- );
605
-});
606
-
607
-self.addEventListener("activate", (e) => {
608
- debugLog("[SW] Activation event triggered");
609
-
610
- e.waitUntil(
611
- (async () => {
612
- try {
613
- // Delete old caches to free up space (especially important on mobile)
614
- const cacheKeys = await caches.keys();
615
- const oldCaches = cacheKeys.filter(key => key.startsWith('portal-') && key !== `portal-v${SW_VERSION}`);
616
- if (oldCaches.length > 0) {
617
- console.log(`[SW] Deleting ${oldCaches.length} old caches:`, oldCaches);
618
- await Promise.all(oldCaches.map(key => caches.delete(key)));
619
- }
620
-
621
- // Claim clients first to take control immediately
622
- await self.clients.claim();
623
- debugLog("[SW] Clients claimed");
624
-
625
- // Safari/iOS specific: Wait a bit before initializing WASM
626
- if (isSafari || isIOS) {
627
- debugLog("[SW] Safari/iOS detected, waiting 100ms before WASM init");
628
- await new Promise(resolve => setTimeout(resolve, 100));
629
- }
630
-
631
- // Then initialize WASM in background (don't block activation)
632
- await init();
633
- } catch (error) {
634
- console.error("[SW] Activation failed:", error);
635
- await notifyClientsOfError(error);
636
- }
637
- })()
638
- );
639
-});
640
-
641
-// Helper function to broadcast message to all clients
642
-async function broadcastToClients(message) {
643
- const clients = await self.clients.matchAll();
644
- clients.forEach((client) => {
645
- client.postMessage(message);
646
- });
647
-}
648
-
649
-// Expose to WASM
650
-self.__sdk_post_message = broadcastToClients;
651
-
652
-// Periodic health check (only in debug mode or when errors occur)
653
-// Adjust interval based on mode: debug = 30s, production = 5min
654
-const healthCheckInterval = DEBUG_MODE ? 30000 : 5 * 60 * 1000;
655
-
656
-setInterval(() => {
657
- const stage = syncStage();
658
- const handlers = areHandlersAvailable();
659
- const health = {
660
- stage: stage,
661
- stageName: Object.keys(ReadinessStage).find(key => ReadinessStage[key] === stage),
662
- wasmActive: handlers.http,
663
- sdkActive: handlers.sdk,
664
- loading: loading,
665
- initError: initError ? initError.message : null,
666
- uptime: Date.now() - _lastReload
667
- };
668
-
669
- // Only log in debug mode or if there's an issue
670
- if (DEBUG_MODE || !handlers.http || initError) {
671
- debugLog("[SW] Health Check:", health);
672
- }
673
-
674
- // Auto-recovery if stage is too low
675
- const recoveryStage = syncStage(); // Get actual stage for recovery check
676
- if (recoveryStage < ReadinessStage.READY && !loading) {
677
- // Allow recovery even if initError exists (clear it and try again)
678
- if (initError) {
679
- console.warn("[SW] Previous init error detected, clearing and retrying...", initError.message);
680
- initError = null;
681
- }
682
- console.warn("[SW] Stage too low, attempting recovery...", health);
683
- ensureStage(ReadinessStage.READY).catch(err => {
684
- console.error("[SW] Recovery failed:", err);
685
- initError = err; // Store new error
686
- });
687
- }
688
-}, healthCheckInterval);
689
-
690
-self.addEventListener("message", (event) => {
691
- if (event.data && event.data.type === "CLAIM_CLIENTS") {
692
- self.clients
693
- .claim()
694
- .then(() => {
695
- self.clients.matchAll().then((clients) => {
696
- clients.forEach((client) => {
697
- client.postMessage({ type: "CLAIMED" });
698
- });
699
- });
700
- })
701
- .catch((error) => {
702
- console.error("[SW] Manual clients.claim() failed:", error);
703
- });
704
- return;
705
- }
706
-
707
- // Handle SDK messages (SDK_CONNECT, SDK_SEND, SDK_CLOSE)
708
- if (event.data && event.data.type && event.data.type.startsWith("SDK_")) {
709
- (async () => {
710
- try {
711
- // Centralized recovery: Wait until handlers are ready
712
- debugLog("[SW] SDK message received, ensuring handlers are ready...");
713
- await ensureReady(ReadinessStage.READY);
714
-
715
- // Handlers should now be available
716
- if (typeof __sdk_message_handler === "undefined") {
717
- throw new Error("SDK message handler still not available after centralized recovery");
718
- }
719
-
720
- // Call WASM message handler
721
- __sdk_message_handler(event.data.type, event.data);
722
- } catch (error) {
723
- console.error("[SW] SDK message handling failed:", error);
724
- // Send error back to client
725
- if (event.data.clientId) {
726
- await broadcastToClients({
727
- type: event.data.type.replace("SDK_", "SDK_") + "_ERROR",
728
- clientId: event.data.clientId,
729
- error: "Handler unavailable: " + error.message,
730
- });
731
- }
732
- }
733
- })();
734
- }
735
-});
736
-
737
-self.addEventListener("fetch", (e) => {
738
- const url = new URL(e.request.url);
739
-
740
- // Skip non-origin requests
741
- if (url.origin !== self.location.origin) {
742
- e.respondWith(fetch(e.request));
743
- return;
744
- }
745
-
746
- // Skip Service Worker infrastructure files (prevent infinite loop during initialization)
747
- if (url.pathname.startsWith("/frontend/") ||
748
- url.pathname === "/service-worker.js" ||
749
- url.pathname === "/portal.mp4") {
750
- e.respondWith(fetch(e.request));
751
- return;
752
- }
753
-
754
- // Health check endpoint - check WASM status
755
- if (url.pathname === "/e8c2c70c-ec4a-40b2-b8af-d5638264f831") {
756
- e.respondWith(
757
- (async () => {
758
- try {
759
- // Centralized recovery: Wait until handlers are ready
760
- await ensureReady(ReadinessStage.READY);
761
-
762
- if (typeof __go_jshttp !== "undefined") {
763
- return new Response("ACK-e8c2c70c-ec4a-40b2-b8af-d5638264f831", {
764
- status: 200,
765
- });
766
- }
767
- } catch (error) {
768
- console.error("[SW] Health check failed:", error);
769
- }
770
-
771
- return new Response("NAK-e8c2c70c-ec4a-40b2-b8af-d5638264f831", {
772
- status: 503,
773
- });
774
- })()
775
- );
776
- return;
777
- }
778
-
779
- e.respondWith(
780
- (async () => {
781
- try {
782
- // Centralized recovery: Wait until handlers are ready
783
- debugLog("[SW] Fetch request received, ensuring handlers are ready...");
784
- await ensureReady(ReadinessStage.READY);
785
-
786
- // Handler should now be available
787
- if (typeof __go_jshttp === "undefined") {
788
- throw new Error("__go_jshttp still not available after centralized recovery");
789
- }
790
-
791
- // Process request
792
- const resp = await __go_jshttp(e.request);
793
-
794
- return resp;
795
- } catch (error) {
796
- console.error("[SW] Request handling failed:", error);
797
-
798
- return new Response(
799
- "Service temporarily unavailable. Please refresh the page.",
800
- {
801
- status: 503,
802
- statusText: "Service Unavailable",
803
- }
804
- );
805
- }
806
- })()
807
- );
808
-});
cmd/webclient/streamjs/stream_js.go
deleted
-135
@@ -1,135 +0,0 @@
1
-package streamjs
2
-
3
-import (
4
- "io"
5
- "sync"
6
- "syscall/js"
7
-)
8
-
9
-var (
10
- _ReadableStream = js.Global().Get("ReadableStream")
11
- _Object = js.Global().Get("Object")
12
- _Promise = js.Global().Get("Promise")
13
- _Error = js.Global().Get("Error")
14
- _Uint8Array = js.Global().Get("Uint8Array")
15
-)
16
-
17
-type ReadableStream struct {
18
- js.Value
19
- r io.ReadCloser
20
- closeOnce sync.Once
21
-
22
- // 데이터를 읽기 위한 버퍼
23
- buffer []byte
24
-
25
- funcsToBeReleased []js.Func
26
-}
27
-
28
-// NewReadableStream는 Go의 io.ReadCloser를 JS ReadableStream으로 래핑합니다.
29
-func NewReadableStream(r io.ReadCloser) *ReadableStream {
30
- // 1. Go 래퍼 구조체를 먼저 생성합니다.
31
- rs := &ReadableStream{
32
- r: r,
33
- buffer: make([]byte, 32*1024), // 32KB buffer to reduce Go-JS boundary crossings
34
- }
35
-
36
- // 2. JS 콜백 함수들을 정의합니다. 이 함수들은 'rs' 포인터를 클로저로 캡처합니다.
37
- var onStart, onPull, onCancel js.Func
38
-
39
- // start: 스트림이 시작될 때 호출됨 (보통 비워둠)
40
- onStart = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
41
- // controller := args[0]
42
- return nil
43
- })
44
-
45
- // pull: JS 런타임이 데이터를 요청할 때 호출됨 (가장 중요)
46
- onPull = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
47
- controller := args[0]
48
-
49
- // 3. Promise를 생성하여 반환합니다. (비동기 작업)
50
- // JS 스레드를 차단하지 않기 위해 Go 루틴에서 실제 I/O를 수행합니다.
51
- var promiseFn js.Func
52
- promiseFn = js.FuncOf(func(this js.Value, pArgs []js.Value) interface{} {
53
- resolve := pArgs[0]
54
- reject := pArgs[1]
55
-
56
- // 4. 고루틴에서 (잠재적으로 블로킹되는) Read 수행
57
- go func() {
58
- defer promiseFn.Release()
59
-
60
- n, err := rs.r.Read(rs.buffer)
61
-
62
- // 5. 에러 처리
63
- if err != nil {
64
- if err == io.EOF {
65
- // 5a. 파일 끝 (EOF) -> 스트림 정상 종료
66
- controller.Call("close")
67
- } else {
68
- // 5b. 실제 읽기 오류 -> 스트림 에러 종료
69
- jsErr := _Error.New(err.Error())
70
- controller.Call("error", jsErr)
71
- reject.Invoke(jsErr) // Promise 거부
72
- }
73
- resolve.Invoke() // Promise 이행 (pull 작업 완료)
74
- return
75
- }
76
-
77
- // 6. 성공적으로 데이터를 읽은 경우
78
- if n > 0 {
79
- // 6a. 읽은 만큼(n 바이트) JS Uint8Array 생성
80
- jsChunk := _Uint8Array.New(n)
81
-
82
- // 6b. Go 버퍼(rs.buffer[:n])에서 JS Uint8Array로 바이트 복사
83
- js.CopyBytesToJS(jsChunk, rs.buffer[:n])
84
-
85
- // 6c. JS 스트림 컨트롤러에 데이터 추가 (enqueue)
86
- controller.Call("enqueue", jsChunk)
87
- }
88
-
89
- // 7. pull 작업이 성공적으로 완료되었음을 알림 (Promise 이행)
90
- resolve.Invoke()
91
- }()
92
-
93
- return nil
94
- })
95
-
96
- return _Promise.New(promiseFn)
97
- })
98
-
99
- // cancel: 스트림이 JS 쪽에서 취소될 때 호출됨
100
- onCancel = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
101
- // Go 리더기(ReadCloser)를 닫아 리소스를 정리합니다.
102
- rs.closeOnce.Do(func() {
103
- rs.r.Close()
104
- })
105
- return nil
106
- })
107
-
108
- // 8. JS 'underlyingSource' 객체 생성
109
- underlyingSource := _Object.New()
110
- underlyingSource.Set("start", onStart)
111
- underlyingSource.Set("pull", onPull)
112
- underlyingSource.Set("cancel", onCancel)
113
- // underlyingSource.Set("type", "bytes") // Safari does not support ReadableByteStreamController
114
-
115
- // 9. JS ReadableStream 인스턴스 생성
116
- stream := _ReadableStream.New(underlyingSource)
117
-
118
- // 10. Go 래퍼 구조체 필드 완성
119
- rs.Value = stream
120
- rs.funcsToBeReleased = []js.Func{onStart, onPull, onCancel}
121
-
122
- return rs
123
-}
124
-
125
-// Close는 스트림을 닫고 할당된 JS 함수들을 해제(release)합니다.
126
-func (rs *ReadableStream) Close() {
127
- for _, f := range rs.funcsToBeReleased {
128
- f.Release()
129
- }
130
-
131
- // Go 리더기도 닫아줍니다.
132
- rs.closeOnce.Do(func() {
133
- rs.r.Close()
134
- })
135
-}
cmd/webclient/wasm_exec.js
deleted
-604
@@ -1,604 +0,0 @@
1
-// Copyright 2018 The Go Authors. All rights reserved.
2
-// Use of this source code is governed by a BSD-style
3
-//
4
-// Copyright 2009 The Go Authors.
5
-//
6
-// Redistribution and use in source and binary forms, with or without
7
-// modification, are permitted provided that the following conditions are
8
-// met:
9
-//
10
-// * Redistributions of source code must retain the above copyright
11
-// notice, this list of conditions and the following disclaimer.
12
-// * Redistributions in binary form must reproduce the above
13
-// copyright notice, this list of conditions and the following disclaimer
14
-// in the documentation and/or other materials provided with the
15
-// distribution.
16
-// * Neither the name of Google LLC nor the names of its
17
-// contributors may be used to endorse or promote products derived from
18
-// this software without specific prior written permission.
19
-//
20
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
-//
32
-
33
-
34
-"use strict";
35
-
36
-(() => {
37
- const enosys = () => {
38
- const err = new Error("not implemented");
39
- err.code = "ENOSYS";
40
- return err;
41
- };
42
-
43
- if (!globalThis.fs) {
44
- let outputBuf = "";
45
- globalThis.fs = {
46
- constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
47
- writeSync(fd, buf) {
48
- outputBuf += decoder.decode(buf);
49
- const nl = outputBuf.lastIndexOf("\n");
50
- if (nl != -1) {
51
- console.log(outputBuf.substring(0, nl));
52
- outputBuf = outputBuf.substring(nl + 1);
53
- }
54
- return buf.length;
55
- },
56
- write(fd, buf, offset, length, position, callback) {
57
- if (offset !== 0 || length !== buf.length || position !== null) {
58
- callback(enosys());
59
- return;
60
- }
61
- const n = this.writeSync(fd, buf);
62
- callback(null, n);
63
- },
64
- chmod(path, mode, callback) { callback(enosys()); },
65
- chown(path, uid, gid, callback) { callback(enosys()); },
66
- close(fd, callback) { callback(enosys()); },
67
- fchmod(fd, mode, callback) { callback(enosys()); },
68
- fchown(fd, uid, gid, callback) { callback(enosys()); },
69
- fstat(fd, callback) { callback(enosys()); },
70
- fsync(fd, callback) { callback(null); },
71
- ftruncate(fd, length, callback) { callback(enosys()); },
72
- lchown(path, uid, gid, callback) { callback(enosys()); },
73
- link(path, link, callback) { callback(enosys()); },
74
- lstat(path, callback) { callback(enosys()); },
75
- mkdir(path, perm, callback) { callback(enosys()); },
76
- open(path, flags, mode, callback) { callback(enosys()); },
77
- read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
78
- readdir(path, callback) { callback(enosys()); },
79
- readlink(path, callback) { callback(enosys()); },
80
- rename(from, to, callback) { callback(enosys()); },
81
- rmdir(path, callback) { callback(enosys()); },
82
- stat(path, callback) { callback(enosys()); },
83
- symlink(path, link, callback) { callback(enosys()); },
84
- truncate(path, length, callback) { callback(enosys()); },
85
- unlink(path, callback) { callback(enosys()); },
86
- utimes(path, atime, mtime, callback) { callback(enosys()); },
87
- };
88
- }
89
-
90
- if (!globalThis.process) {
91
- globalThis.process = {
92
- getuid() { return -1; },
93
- getgid() { return -1; },
94
- geteuid() { return -1; },
95
- getegid() { return -1; },
96
- getgroups() { throw enosys(); },
97
- pid: -1,
98
- ppid: -1,
99
- umask() { throw enosys(); },
100
- cwd() { throw enosys(); },
101
- chdir() { throw enosys(); },
102
- }
103
- }
104
-
105
- if (!globalThis.path) {
106
- globalThis.path = {
107
- resolve(...pathSegments) {
108
- return pathSegments.join("/");
109
- }
110
- }
111
- }
112
-
113
- if (!globalThis.crypto) {
114
- throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
115
- }
116
-
117
- if (!globalThis.performance) {
118
- throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
119
- }
120
-
121
- if (!globalThis.TextEncoder) {
122
- throw new Error("globalThis.TextEncoder is not available, polyfill required");
123
- }
124
-
125
- if (!globalThis.TextDecoder) {
126
- throw new Error("globalThis.TextDecoder is not available, polyfill required");
127
- }
128
-
129
- const encoder = new TextEncoder("utf-8");
130
- const decoder = new TextDecoder("utf-8");
131
-
132
- globalThis.Go = class {
133
- constructor() {
134
- this.argv = ["js"];
135
- this.env = {};
136
- this.exit = (code) => {
137
- if (code !== 0) {
138
- console.warn("exit code:", code);
139
- }
140
- };
141
- this._exitPromise = new Promise((resolve) => {
142
- this._resolveExitPromise = resolve;
143
- });
144
- this._pendingEvent = null;
145
- this._scheduledTimeouts = new Map();
146
- this._nextCallbackTimeoutID = 1;
147
-
148
- const setInt64 = (addr, v) => {
149
- this.mem.setUint32(addr + 0, v, true);
150
- this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
151
- }
152
-
153
- const setInt32 = (addr, v) => {
154
- this.mem.setUint32(addr + 0, v, true);
155
- }
156
-
157
- const getInt64 = (addr) => {
158
- const low = this.mem.getUint32(addr + 0, true);
159
- const high = this.mem.getInt32(addr + 4, true);
160
- return low + high * 4294967296;
161
- }
162
-
163
- const loadValue = (addr) => {
164
- const f = this.mem.getFloat64(addr, true);
165
- if (f === 0) {
166
- return undefined;
167
- }
168
- if (!isNaN(f)) {
169
- return f;
170
- }
171
-
172
- const id = this.mem.getUint32(addr, true);
173
- return this._values[id];
174
- }
175
-
176
- const storeValue = (addr, v) => {
177
- const nanHead = 0x7FF80000;
178
-
179
- if (typeof v === "number" && v !== 0) {
180
- if (isNaN(v)) {
181
- this.mem.setUint32(addr + 4, nanHead, true);
182
- this.mem.setUint32(addr, 0, true);
183
- return;
184
- }
185
- this.mem.setFloat64(addr, v, true);
186
- return;
187
- }
188
-
189
- if (v === undefined) {
190
- this.mem.setFloat64(addr, 0, true);
191
- return;
192
- }
193
-
194
- let id = this._ids.get(v);
195
- if (id === undefined) {
196
- id = this._idPool.pop();
197
- if (id === undefined) {
198
- id = this._values.length;
199
- }
200
- this._values[id] = v;
201
- this._goRefCounts[id] = 0;
202
- this._ids.set(v, id);
203
- }
204
- this._goRefCounts[id]++;
205
- let typeFlag = 0;
206
- switch (typeof v) {
207
- case "object":
208
- if (v !== null) {
209
- typeFlag = 1;
210
- }
211
- break;
212
- case "string":
213
- typeFlag = 2;
214
- break;
215
- case "symbol":
216
- typeFlag = 3;
217
- break;
218
- case "function":
219
- typeFlag = 4;
220
- break;
221
- }
222
- this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
223
- this.mem.setUint32(addr, id, true);
224
- }
225
-
226
- const loadSlice = (addr) => {
227
- const array = getInt64(addr + 0);
228
- const len = getInt64(addr + 8);
229
- return new Uint8Array(this._inst.exports.mem.buffer, array, len);
230
- }
231
-
232
- const loadSliceOfValues = (addr) => {
233
- const array = getInt64(addr + 0);
234
- const len = getInt64(addr + 8);
235
- const a = new Array(len);
236
- for (let i = 0; i < len; i++) {
237
- a[i] = loadValue(array + i * 8);
238
- }
239
- return a;
240
- }
241
-
242
- const loadString = (addr) => {
243
- const saddr = getInt64(addr + 0);
244
- const len = getInt64(addr + 8);
245
- return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
246
- }
247
-
248
- const testCallExport = (a, b) => {
249
- this._inst.exports.testExport0();
250
- return this._inst.exports.testExport(a, b);
251
- }
252
-
253
- const timeOrigin = Date.now() - performance.now();
254
- this.importObject = {
255
- _gotest: {
256
- add: (a, b) => a + b,
257
- callExport: testCallExport,
258
- },
259
- gojs: {
260
- // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
261
- // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
262
- // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
263
- // This changes the SP, thus we have to update the SP used by the imported function.
264
-
265
- // func wasmExit(code int32)
266
- "runtime.wasmExit": (sp) => {
267
- sp >>>= 0;
268
- const code = this.mem.getInt32(sp + 8, true);
269
- this.exited = true;
270
- delete this._inst;
271
- delete this._values;
272
- delete this._goRefCounts;
273
- delete this._ids;
274
- delete this._idPool;
275
- this.exit(code);
276
- },
277
-
278
- // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
279
- "runtime.wasmWrite": (sp) => {
280
- sp >>>= 0;
281
- const fd = getInt64(sp + 8);
282
- const p = getInt64(sp + 16);
283
- const n = this.mem.getInt32(sp + 24, true);
284
- fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
285
- },
286
-
287
- // func resetMemoryDataView()
288
- "runtime.resetMemoryDataView": (sp) => {
289
- sp >>>= 0;
290
- this.mem = new DataView(this._inst.exports.mem.buffer);
291
- },
292
-
293
- // func nanotime1() int64
294
- "runtime.nanotime1": (sp) => {
295
- sp >>>= 0;
296
- setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
297
- },
298
-
299
- // func walltime() (sec int64, nsec int32)
300
- "runtime.walltime": (sp) => {
301
- sp >>>= 0;
302
- const msec = (new Date).getTime();
303
- setInt64(sp + 8, msec / 1000);
304
- this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
305
- },
306
-
307
- // func scheduleTimeoutEvent(delay int64) int32
308
- "runtime.scheduleTimeoutEvent": (sp) => {
309
- sp >>>= 0;
310
- const id = this._nextCallbackTimeoutID;
311
- this._nextCallbackTimeoutID++;
312
- this._scheduledTimeouts.set(id, setTimeout(
313
- () => {
314
- this._resume();
315
- while (this._scheduledTimeouts.has(id)) {
316
- // for some reason Go failed to register the timeout event, log and try again
317
- // (temporary workaround for https://github.com/golang/go/issues/28975)
318
- console.warn("scheduleTimeoutEvent: missed timeout event");
319
- this._resume();
320
- }
321
- },
322
- getInt64(sp + 8),
323
- ));
324
- this.mem.setInt32(sp + 16, id, true);
325
- },
326
-
327
- // func clearTimeoutEvent(id int32)
328
- "runtime.clearTimeoutEvent": (sp) => {
329
- sp >>>= 0;
330
- const id = this.mem.getInt32(sp + 8, true);
331
- clearTimeout(this._scheduledTimeouts.get(id));
332
- this._scheduledTimeouts.delete(id);
333
- },
334
-
335
- // func getRandomData(r []byte)
336
- "runtime.getRandomData": (sp) => {
337
- sp >>>= 0;
338
- crypto.getRandomValues(loadSlice(sp + 8));
339
- },
340
-
341
- // func finalizeRef(v ref)
342
- "syscall/js.finalizeRef": (sp) => {
343
- sp >>>= 0;
344
- const id = this.mem.getUint32(sp + 8, true);
345
- this._goRefCounts[id]--;
346
- if (this._goRefCounts[id] === 0) {
347
- const v = this._values[id];
348
- this._values[id] = null;
349
- this._ids.delete(v);
350
- this._idPool.push(id);
351
- }
352
- },
353
-
354
- // func stringVal(value string) ref
355
- "syscall/js.stringVal": (sp) => {
356
- sp >>>= 0;
357
- storeValue(sp + 24, loadString(sp + 8));
358
- },
359
-
360
- // func valueGet(v ref, p string) ref
361
- "syscall/js.valueGet": (sp) => {
362
- sp >>>= 0;
363
- const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
364
- sp = this._inst.exports.getsp() >>> 0; // see comment above
365
- storeValue(sp + 32, result);
366
- },
367
-
368
- // func valueSet(v ref, p string, x ref)
369
- "syscall/js.valueSet": (sp) => {
370
- sp >>>= 0;
371
- Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
372
- },
373
-
374
- // func valueDelete(v ref, p string)
375
- "syscall/js.valueDelete": (sp) => {
376
- sp >>>= 0;
377
- Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
378
- },
379
-
380
- // func valueIndex(v ref, i int) ref
381
- "syscall/js.valueIndex": (sp) => {
382
- sp >>>= 0;
383
- storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
384
- },
385
-
386
- // valueSetIndex(v ref, i int, x ref)
387
- "syscall/js.valueSetIndex": (sp) => {
388
- sp >>>= 0;
389
- Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
390
- },
391
-
392
- // func valueCall(v ref, m string, args []ref) (ref, bool)
393
- "syscall/js.valueCall": (sp) => {
394
- sp >>>= 0;
395
- try {
396
- const v = loadValue(sp + 8);
397
- const m = Reflect.get(v, loadString(sp + 16));
398
- const args = loadSliceOfValues(sp + 32);
399
- const result = Reflect.apply(m, v, args);
400
- sp = this._inst.exports.getsp() >>> 0; // see comment above
401
- storeValue(sp + 56, result);
402
- this.mem.setUint8(sp + 64, 1);
403
- } catch (err) {
404
- sp = this._inst.exports.getsp() >>> 0; // see comment above
405
- storeValue(sp + 56, err);
406
- this.mem.setUint8(sp + 64, 0);
407
- }
408
- },
409
-
410
- // func valueInvoke(v ref, args []ref) (ref, bool)
411
- "syscall/js.valueInvoke": (sp) => {
412
- sp >>>= 0;
413
- try {
414
- const v = loadValue(sp + 8);
415
- const args = loadSliceOfValues(sp + 16);
416
- const result = Reflect.apply(v, undefined, args);
417
- sp = this._inst.exports.getsp() >>> 0; // see comment above
418
- storeValue(sp + 40, result);
419
- this.mem.setUint8(sp + 48, 1);
420
- } catch (err) {
421
- sp = this._inst.exports.getsp() >>> 0; // see comment above
422
- storeValue(sp + 40, err);
423
- this.mem.setUint8(sp + 48, 0);
424
- }
425
- },
426
-
427
- // func valueNew(v ref, args []ref) (ref, bool)
428
- "syscall/js.valueNew": (sp) => {
429
- sp >>>= 0;
430
- try {
431
- const v = loadValue(sp + 8);
432
- const args = loadSliceOfValues(sp + 16);
433
- const result = Reflect.construct(v, args);
434
- sp = this._inst.exports.getsp() >>> 0; // see comment above
435
- storeValue(sp + 40, result);
436
- this.mem.setUint8(sp + 48, 1);
437
- } catch (err) {
438
- sp = this._inst.exports.getsp() >>> 0; // see comment above
439
- storeValue(sp + 40, err);
440
- this.mem.setUint8(sp + 48, 0);
441
- }
442
- },
443
-
444
- // func valueLength(v ref) int
445
- "syscall/js.valueLength": (sp) => {
446
- sp >>>= 0;
447
- setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
448
- },
449
-
450
- // valuePrepareString(v ref) (ref, int)
451
- "syscall/js.valuePrepareString": (sp) => {
452
- sp >>>= 0;
453
- const str = encoder.encode(String(loadValue(sp + 8)));
454
- storeValue(sp + 16, str);
455
- setInt64(sp + 24, str.length);
456
- },
457
-
458
- // valueLoadString(v ref, b []byte)
459
- "syscall/js.valueLoadString": (sp) => {
460
- sp >>>= 0;
461
- const str = loadValue(sp + 8);
462
- loadSlice(sp + 16).set(str);
463
- },
464
-
465
- // func valueInstanceOf(v ref, t ref) bool
466
- "syscall/js.valueInstanceOf": (sp) => {
467
- sp >>>= 0;
468
- this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
469
- },
470
-
471
- // func copyBytesToGo(dst []byte, src ref) (int, bool)
472
- "syscall/js.copyBytesToGo": (sp) => {
473
- sp >>>= 0;
474
- const dst = loadSlice(sp + 8);
475
- const src = loadValue(sp + 32);
476
- if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
477
- this.mem.setUint8(sp + 48, 0);
478
- return;
479
- }
480
- const toCopy = src.subarray(0, dst.length);
481
- dst.set(toCopy);
482
- setInt64(sp + 40, toCopy.length);
483
- this.mem.setUint8(sp + 48, 1);
484
- },
485
-
486
- // func copyBytesToJS(dst ref, src []byte) (int, bool)
487
- "syscall/js.copyBytesToJS": (sp) => {
488
- sp >>>= 0;
489
- const dst = loadValue(sp + 8);
490
- const src = loadSlice(sp + 16);
491
- if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
492
- this.mem.setUint8(sp + 48, 0);
493
- return;
494
- }
495
- const toCopy = src.subarray(0, dst.length);
496
- dst.set(toCopy);
497
- setInt64(sp + 40, toCopy.length);
498
- this.mem.setUint8(sp + 48, 1);
499
- },
500
-
501
- "debug": (value) => {
502
- console.log(value);
503
- },
504
- }
505
- };
506
- }
507
-
508
- async run(instance) {
509
- if (!(instance instanceof WebAssembly.Instance)) {
510
- throw new Error("Go.run: WebAssembly.Instance expected");
511
- }
512
- this._inst = instance;
513
- this.mem = new DataView(this._inst.exports.mem.buffer);
514
- this._values = [ // JS values that Go currently has references to, indexed by reference id
515
- NaN,
516
- 0,
517
- null,
518
- true,
519
- false,
520
- globalThis,
521
- this,
522
- ];
523
- this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
524
- this._ids = new Map([ // mapping from JS values to reference ids
525
- [0, 1],
526
- [null, 2],
527
- [true, 3],
528
- [false, 4],
529
- [globalThis, 5],
530
- [this, 6],
531
- ]);
532
- this._idPool = []; // unused ids that have been garbage collected
533
- this.exited = false; // whether the Go program has exited
534
-
535
- // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
536
- let offset = 4096;
537
-
538
- const strPtr = (str) => {
539
- const ptr = offset;
540
- const bytes = encoder.encode(str + "\0");
541
- new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
542
- offset += bytes.length;
543
- if (offset % 8 !== 0) {
544
- offset += 8 - (offset % 8);
545
- }
546
- return ptr;
547
- };
548
-
549
- const argc = this.argv.length;
550
-
551
- const argvPtrs = [];
552
- this.argv.forEach((arg) => {
553
- argvPtrs.push(strPtr(arg));
554
- });
555
- argvPtrs.push(0);
556
-
557
- const keys = Object.keys(this.env).sort();
558
- keys.forEach((key) => {
559
- argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
560
- });
561
- argvPtrs.push(0);
562
-
563
- const argv = offset;
564
- argvPtrs.forEach((ptr) => {
565
- this.mem.setUint32(offset, ptr, true);
566
- this.mem.setUint32(offset + 4, 0, true);
567
- offset += 8;
568
- });
569
-
570
- // The linker guarantees global data starts from at least wasmMinDataAddr.
571
- // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
572
- const wasmMinDataAddr = 4096 + 8192;
573
- if (offset >= wasmMinDataAddr) {
574
- throw new Error("total length of command line and environment variables exceeds limit");
575
- }
576
-
577
- this._inst.exports.run(argc, argv);
578
- if (this.exited) {
579
- this._resolveExitPromise();
580
- }
581
- await this._exitPromise;
582
- }
583
-
584
- _resume() {
585
- if (this.exited) {
586
- throw new Error("Go program has already exited");
587
- }
588
- this._inst.exports.resume();
589
- if (this.exited) {
590
- this._resolveExitPromise();
591
- }
592
- }
593
-
594
- _makeFuncWrapper(id) {
595
- const go = this;
596
- return function () {
597
- const event = { id: id, this: this, args: arguments };
598
- go._pendingEvent = event;
599
- go._resume();
600
- return event.result;
601
- };
602
- }
603
- }
604
-})();
cmd/webclient/wsjs/ws_js.go
deleted
-120
@@ -1,120 +0,0 @@
1
-package wsjs
2
-
3
-import (
4
- "errors"
5
- "syscall/js"
6
-)
7
-
8
-var (
9
- ErrFailedToDial = errors.New("failed to dial websocket")
10
- ErrClosed = errors.New("websocket connection closed")
11
-)
12
-
13
-var (
14
- _WebSocket = js.Global().Get("WebSocket")
15
- _ArrayBuffer = js.Global().Get("ArrayBuffer")
16
- _Uint8Array = js.Global().Get("Uint8Array")
17
-)
18
-
19
-type Conn struct {
20
- ws js.Value
21
-
22
- messageChan chan []byte
23
- closeChan chan struct{}
24
-
25
- funcsToBeReleased []js.Func
26
-}
27
-
28
-func (conn *Conn) freeFuncs() {
29
- for _, f := range conn.funcsToBeReleased {
30
- f.Release()
31
- }
32
-}
33
-
34
-func Dial(uri string) (*Conn, error) {
35
- errCh := make(chan error, 1)
36
-
37
- ws := _WebSocket.New(uri)
38
- ws.Set("binaryType", "arraybuffer")
39
-
40
- conn := &Conn{
41
- ws: ws,
42
- messageChan: make(chan []byte, 128),
43
- closeChan: make(chan struct{}, 1),
44
- }
45
-
46
- onOpen := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
47
- errCh <- nil
48
- return nil
49
- })
50
-
51
- onError := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
52
- errCh <- ErrFailedToDial
53
- return nil
54
- })
55
-
56
- onMessage := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
57
- jsData := args[0].Get("data")
58
- if jsData.Type() == js.TypeString {
59
- // text frame
60
- data := []byte(jsData.String())
61
-
62
- conn.messageChan <- data
63
- } else if jsData.InstanceOf(_ArrayBuffer) {
64
- // binary frame
65
- array := _Uint8Array.New(jsData)
66
- byteLength := array.Get("byteLength").Int()
67
- data := make([]byte, byteLength)
68
- js.CopyBytesToGo(data, array)
69
-
70
- conn.messageChan <- data
71
- }
72
-
73
- return nil
74
- })
75
-
76
- onClose := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
77
- close(conn.closeChan)
78
- return nil
79
- })
80
-
81
- conn.funcsToBeReleased = append(conn.funcsToBeReleased, onOpen, onError, onMessage, onClose)
82
-
83
- conn.ws.Call("addEventListener", "open", onOpen)
84
- conn.ws.Call("addEventListener", "error", onError)
85
- conn.ws.Call("addEventListener", "message", onMessage)
86
- conn.ws.Call("addEventListener", "close", onClose)
87
-
88
- err := <-errCh
89
- if err != nil {
90
- conn.freeFuncs()
91
- return nil, err
92
- }
93
-
94
- return conn, nil
95
-}
96
-
97
-func (conn *Conn) Close() error {
98
- conn.ws.Call("close")
99
- <-conn.closeChan
100
- conn.freeFuncs()
101
- return nil
102
-}
103
-
104
-func (conn *Conn) NextMessage() ([]byte, error) {
105
- select {
106
- case msg := <-conn.messageChan:
107
- return msg, nil
108
- case <-conn.closeChan:
109
- return nil, ErrClosed
110
- }
111
-}
112
-
113
-func (conn *Conn) Send(data []byte) error {
114
- buffer := _ArrayBuffer.New(len(data))
115
- array := _Uint8Array.New(buffer)
116
- js.CopyBytesToJS(array, data)
117
-
118
- conn.ws.Call("send", buffer)
119
- return nil
120
-}
cmd/webclient/wsjs/wsstream_js.go
deleted
-67
@@ -1,67 +0,0 @@
1
-package wsjs
2
-
3
-import (
4
- "sync"
5
-)
6
-
7
-// WsStream provides an io.Reader and io.Writer interface for WebSocket connections
8
-type WsStream struct {
9
- conn *Conn
10
- currentBuffer []byte
11
- readMu sync.Mutex
12
- writeMu sync.Mutex
13
-}
14
-
15
-// NewWsStream creates a new WsStream from a WebSocket connection
16
-func NewWsStream(conn *Conn) *WsStream {
17
- return &WsStream{
18
- conn: conn,
19
- }
20
-}
21
-
22
-// Read implements io.Reader interface
23
-func (ws *WsStream) Read(p []byte) (n int, err error) {
24
- ws.readMu.Lock()
25
- defer ws.readMu.Unlock()
26
-
27
- // If we have remaining data from previous message, use it first
28
- if len(ws.currentBuffer) > 0 {
29
- n = copy(p, ws.currentBuffer)
30
- ws.currentBuffer = ws.currentBuffer[n:]
31
- return n, nil
32
- }
33
-
34
- // Get next message from WebSocket
35
- msg, err := ws.conn.NextMessage()
36
- if err != nil {
37
- return 0, err
38
- }
39
-
40
- // Copy message data to buffer
41
- n = copy(p, msg)
42
-
43
- // Store any remaining data for next read
44
- if n < len(msg) {
45
- ws.currentBuffer = msg[n:]
46
- }
47
-
48
- return n, nil
49
-}
50
-
51
-// Write implements io.Writer interface
52
-func (ws *WsStream) Write(p []byte) (n int, err error) {
53
- ws.writeMu.Lock()
54
- defer ws.writeMu.Unlock()
55
-
56
- err = ws.conn.Send(p)
57
- if err != nil {
58
- return 0, err
59
- }
60
-
61
- return len(p), nil
62
-}
63
-
64
-// Close closes the WebSocket connection
65
-func (ws *WsStream) Close() error {
66
- return ws.conn.Close()
67
-}
go.mod
+1
-6
@@ -3,15 +3,10 @@ module gosuda.org/portal
3
go 1.25.3
4
5
require (
6
- github.com/gorilla/websocket v1.5.3
7
- github.com/hashicorp/yamux v0.1.2
8
- github.com/planetscale/vtprotobuf v0.6.0
6
github.com/rs/zerolog v1.34.0
7
github.com/stretchr/testify v1.11.1
11
- github.com/valyala/bytebufferpool v1.0.0
8
golang.org/x/crypto v0.46.0
13
- golang.org/x/net v0.48.0
14
- google.golang.org/protobuf v1.36.11
9
+ golang.org/x/net v0.47.0
10
gopkg.eu.org/broccoli v1.2.4
11
)
12
go.sum
+2
-22
@@ -2,12 +2,6 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
2
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
5
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
6
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
7
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
8
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
9
-github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
10
-github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
5
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
6
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
7
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
@@ -16,8 +10,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
10
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
11
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
12
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
19
-github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
20
-github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
13
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
@@ -25,12 +17,10 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
17
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
18
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
19
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
28
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
29
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
20
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
21
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
32
-golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
33
-golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
22
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
23
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
24
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
25
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -38,18 +28,8 @@ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
28
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
29
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
30
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
41
-google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
42
-google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
31
gopkg.eu.org/broccoli v1.2.4 h1:9RvAPhBI6QCakVCDBw0QwojVmqK3qmcj0ib66CxK+kY=
32
gopkg.eu.org/broccoli v1.2.4/go.mod h1:eM8HnmLyfiQHAwqh2afErWYnAkkOvi+RXgoXBRhKMCQ=
45
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
46
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
47
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
48
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
49
-google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
50
-google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
51
-gopkg.eu.org/broccoli v1.2.3 h1:Lc6+C3n24sRGoFyYxo1qz8iiXQzwB6VcN9bLjnT9vSo=
52
-gopkg.eu.org/broccoli v1.2.3/go.mod h1:eM8HnmLyfiQHAwqh2afErWYnAkkOvi+RXgoXBRhKMCQ=
33
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
34
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
portal/client.go
deleted
-736
@@ -1,736 +0,0 @@
1
-// Package portal provides client-side functionality for establishing and managing
2
-// relay connections. It handles secure communication channels, lease management,
3
-// and connection multiplexing through the relay server.
4
-package portal
5
-
6
-import (
7
- "crypto/rand"
8
- "errors"
9
- "io"
10
- "sync"
11
- "time"
12
-
13
- "github.com/hashicorp/yamux"
14
- "github.com/rs/zerolog/log"
15
- "gosuda.org/portal/portal/core/cryptoops"
16
- "gosuda.org/portal/portal/core/proto/rdsec"
17
- "gosuda.org/portal/portal/core/proto/rdverb"
18
-)
19
-
20
-var (
21
- // ErrInvalidResponse is returned when the relay server sends an unexpected or malformed response
22
- ErrInvalidResponse = errors.New("invalid response")
23
- // ErrConnectionRejected is returned when the relay server rejects a connection request
24
- ErrConnectionRejected = errors.New("connection rejected")
25
- // ErrRemoteIDMismatch is returned when the remote peer's ID doesn't match the expected lease ID
26
- ErrRemoteIDMismatch = errors.New("remote ID mismatch")
27
-)
28
-
29
-// IncomingConn represents an incoming connection from a remote client.
30
-// It wraps a secure connection with the associated lease ID that was used
31
-// for the connection request.
32
-type IncomingConn struct {
33
- *cryptoops.SecureConnection
34
- leaseID string
35
-}
36
-
37
-// LeaseID returns the lease ID associated with this incoming connection
38
-func (i *IncomingConn) LeaseID() string {
39
- return i.leaseID
40
-}
41
-
42
-// LocalID returns the local identity ID from the secure connection
43
-func (i *IncomingConn) LocalID() string {
44
- return i.SecureConnection.LocalID()
45
-}
46
-
47
-// RemoteID returns the remote peer's identity ID from the secure connection
48
-func (i *IncomingConn) RemoteID() string {
49
- return i.SecureConnection.RemoteID()
50
-}
51
-
52
-// RelayClient manages a connection to a relay server and handles:
53
-// - Lease registration and renewal
54
-// - Incoming connection requests
55
-// - Secure connection establishment
56
-//
57
-// The client uses yamux for connection multiplexing, allowing multiple
58
-// concurrent streams over a single underlying connection.
59
-//
60
-// Thread-safety: All public methods are safe for concurrent use.
61
-type RelayClient struct {
62
- conn io.ReadWriteCloser
63
-
64
- // sess is the yamux session for multiplexing streams
65
- sess *yamux.Session
66
-
67
- // leases maps lease IDs to their credentials for handling incoming connections
68
- leases map[string]*leaseWithCred
69
- leasesMu sync.Mutex
70
-
71
- // stopClientCh signals background workers to shut down
72
- stopClientCh chan struct{}
73
- stopOnce sync.Once // Ensure stopClientCh is closed only once
74
- waitGroup sync.WaitGroup
75
-
76
- // incomingConnCh delivers incoming connections to the application
77
- incomingConnCh chan *IncomingConn
78
-}
79
-
80
-// leaseWithCred pairs a lease with its associated credentials.
81
-// This is used internally to verify and sign messages for lease operations.
82
-type leaseWithCred struct {
83
- Lease *rdverb.Lease
84
- Cred *cryptoops.Credential
85
-}
86
-
87
-// NewRelayClient creates a new relay client from an established connection.
88
-// It initializes the yamux session for stream multiplexing and starts background
89
-// workers for lease renewal and incoming connection handling.
90
-//
91
-// The client starts two goroutines:
92
-// - leaseUpdateWorker: Periodically renews leases before they expire
93
-// - leaseListenWorker: Accepts and handles incoming connection requests
94
-//
95
-// Returns nil if yamux session creation fails.
96
-func NewRelayClient(conn io.ReadWriteCloser) *RelayClient {
97
- log.Debug().Msg("[RelayClient] Creating new relay client")
98
-
99
- // Create yamux session as client
100
- config := yamux.DefaultConfig()
101
- config.Logger = nil // Disable logging for cleaner output
102
- config.MaxStreamWindowSize = 16 * 1024 * 1024 // 16MB for high-BDP scenarios
103
- config.StreamOpenTimeout = 75 * time.Second
104
- config.StreamCloseTimeout = 5 * time.Minute
105
- sess, err := yamux.Client(conn, config)
106
- if err != nil {
107
- log.Error().Err(err).Msg("[RelayClient] Failed to create yamux session")
108
- // If session creation fails, close the connection and return nil
109
- err = conn.Close()
110
- if err != nil {
111
- log.Error().Err(err).Msg("[RelayClient] Failed to close connection")
112
- }
113
- return nil
114
- }
115
-
116
- log.Debug().Msg("[RelayClient] Yamux session created successfully")
117
-
118
- g := &RelayClient{
119
- conn: conn,
120
- sess: sess,
121
- leases: make(map[string]*leaseWithCred),
122
- stopClientCh: make(chan struct{}),
123
- incomingConnCh: make(chan *IncomingConn),
124
- }
125
-
126
- g.waitGroup.Add(2) // One for leaseUpdateWorker, one for leaseListenWorker
127
- go g.leaseUpdateWorker()
128
- go g.leaseListenWorker()
129
-
130
- log.Debug().Msg("[RelayClient] RelayClient initialized and workers started")
131
- return g
132
-}
133
-
134
-// Ping sends a ping to the relay server and measures the round-trip latency.
135
-// It uses yamux's built-in ping mechanism.
136
-func (g *RelayClient) Ping() (time.Duration, error) {
137
- return g.sess.Ping()
138
-}
139
-
140
-// Close gracefully shuts down the relay client.
141
-// It signals all background workers to stop, waits for them to finish,
142
-// then closes the yamux session and underlying connection.
143
-// This method is safe to call multiple times.
144
-func (g *RelayClient) Close() error {
145
- log.Debug().Msg("[RelayClient] Closing relay client")
146
-
147
- // Signal workers to stop (only once)
148
- g.stopOnce.Do(func() {
149
- close(g.stopClientCh)
150
- })
151
-
152
- var errs []error
153
-
154
- // Close the session first to unblock AcceptStream() calls
155
- if g.sess != nil {
156
- if err := g.sess.Close(); err != nil {
157
- log.Error().Err(err).Msg("[RelayClient] Error closing yamux session")
158
- errs = append(errs, err)
159
- }
160
- }
161
-
162
- // Wait for workers to finish after unblocking them
163
- g.waitGroup.Wait()
164
-
165
- // Then close the underlying connection
166
- if g.conn != nil {
167
- if err := g.conn.Close(); err != nil {
168
- log.Error().Err(err).Msg("[RelayClient] Error closing connection")
169
- errs = append(errs, err)
170
- }
171
- }
172
-
173
- log.Debug().Msg("[RelayClient] Relay client closed")
174
- if len(errs) > 0 {
175
- return errs[0]
176
- }
177
- return nil
178
-}
179
-
180
-// leaseUpdateWorker is a background goroutine that periodically renews leases
181
-// before they expire. It checks every 5 seconds and renews any lease that
182
-// will expire within the next 30 seconds.
183
-func (g *RelayClient) leaseUpdateWorker() {
184
- defer g.waitGroup.Done()
185
-
186
- ticker := time.NewTicker(5 * time.Second)
187
- var updateRequired = map[*leaseWithCred]struct{}{}
188
-
189
- defer ticker.Stop()
190
- for {
191
- select {
192
- case <-g.stopClientCh:
193
- return
194
- case <-ticker.C:
195
- // Clear the map for the next update cycle
196
- clear(updateRequired)
197
-
198
- g.leasesMu.Lock()
199
- for _, lease := range g.leases {
200
- // Check if lease expires within 30 seconds
201
- if lease.Lease.Expires < int64(time.Now().Add(30*time.Second).Unix()) {
202
- updateRequired[lease] = struct{}{}
203
- }
204
- }
205
- g.leasesMu.Unlock()
206
-
207
- for lease := range updateRequired {
208
- lease.Lease.Expires = time.Now().Add(30 * time.Second).Unix()
209
- // Check if session is available before updating lease
210
- if g.sess != nil {
211
- _, err := g.updateLease(lease.Cred, lease.Lease)
212
- if err != nil {
213
- log.Error().Err(err).Msg("[RelayClient] Failed to update lease")
214
- }
215
- }
216
- }
217
- }
218
- }
219
-}
220
-
221
-// leaseListenWorker is a background goroutine that accepts incoming connection
222
-// requests from the relay server. It blocks on AcceptStream() and spawns a
223
-// new goroutine to handle each connection request.
224
-func (g *RelayClient) leaseListenWorker() {
225
- defer g.waitGroup.Done()
226
- defer close(g.incomingConnCh)
227
- log.Debug().Msg("[RelayClient] Lease listen worker started")
228
-
229
- for {
230
- select {
231
- case <-g.stopClientCh:
232
- log.Debug().Msg("[RelayClient] Lease listen worker stopped")
233
- return
234
- default:
235
- if g.sess == nil {
236
- // Session not initialized, wait a bit and retry
237
- time.Sleep(500 * time.Millisecond)
238
- continue
239
- }
240
-
241
- stream, err := g.sess.AcceptStream()
242
- if err != nil {
243
- // Check if we're supposed to stop
244
- select {
245
- case <-g.stopClientCh:
246
- return
247
- default:
248
- log.Debug().Err(err).Msg("[RelayClient] Error accepting stream, retrying")
249
- time.Sleep(500 * time.Millisecond) // waiting for reconnection
250
- continue
251
- }
252
- }
253
- log.Debug().Uint32("stream_id", stream.StreamID()).Msg("[RelayClient] Accepted incoming stream")
254
- go g.handleConnectionRequestStream(stream)
255
- }
256
- }
257
-}
258
-
259
-// handleConnectionRequestStream processes an incoming connection request from a client.
260
-// It performs the following steps:
261
-// 1. Reads and validates the connection request packet
262
-// 2. Looks up the requested lease ID
263
-// 3. Sends an accept/reject response
264
-// 4. If accepted, performs server-side cryptographic handshake
265
-// 5. Sends the established secure connection to the incoming channel
266
-func (g *RelayClient) handleConnectionRequestStream(stream *yamux.Stream) {
267
- log.Debug().Uint32("stream_id", stream.StreamID()).Msg("[RelayClient] Handling connection request stream")
268
-
269
- pkt, err := readPacket(stream)
270
- if err != nil {
271
- log.Error().Uint32("stream_id", stream.StreamID()).Err(err).Msg("[RelayClient] Failed to read packet from stream")
272
- err = stream.Close()
273
- if err != nil {
274
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
275
- }
276
- return
277
- }
278
-
279
- if pkt.Type != rdverb.PacketType_PACKET_TYPE_CONNECTION_REQUEST {
280
- log.Warn().Str("packet_type", pkt.Type.String()).Msg("[RelayClient] Unexpected packet type")
281
- err = stream.Close()
282
- if err != nil {
283
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
284
- }
285
- return
286
- }
287
-
288
- req := &rdverb.ConnectionRequest{}
289
- err = req.UnmarshalVT(pkt.Payload)
290
- if err != nil {
291
- log.Error().Err(err).Msg("[RelayClient] Failed to unmarshal connection request")
292
- err = stream.Close()
293
- if err != nil {
294
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
295
- }
296
- return
297
- }
298
-
299
- log.Debug().Str("lease_id", req.LeaseId).Msg("[RelayClient] Connection request received")
300
-
301
- g.leasesMu.Lock()
302
- lease, ok := g.leases[req.LeaseId]
303
- g.leasesMu.Unlock()
304
-
305
- resp := &rdverb.ConnectionResponse{}
306
- if !ok {
307
- log.Warn().Str("lease_id", req.LeaseId).Msg("[RelayClient] Lease not found, rejecting connection")
308
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_REJECTED
309
- } else {
310
- log.Debug().Str("lease_id", req.LeaseId).Msg("[RelayClient] Lease found, accepting connection")
311
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED
312
- }
313
-
314
- respPayload, err := resp.MarshalVT()
315
- if err != nil {
316
- log.Error().Err(err).Msg("[RelayClient] Failed to marshal response")
317
- err = stream.Close()
318
- if err != nil {
319
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
320
- }
321
- return
322
- }
323
-
324
- err = writePacket(stream, &rdverb.Packet{
325
- Type: rdverb.PacketType_PACKET_TYPE_CONNECTION_RESPONSE,
326
- Payload: respPayload,
327
- })
328
- if err != nil {
329
- log.Error().Err(err).Msg("[RelayClient] Failed to write response packet")
330
- err = stream.Close()
331
- if err != nil {
332
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
333
- }
334
- return
335
- }
336
-
337
- if !ok {
338
- err = stream.Close()
339
- if err != nil {
340
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
341
- }
342
- return
343
- }
344
-
345
- log.Debug().Str("lease_id", req.LeaseId).Msg("[RelayClient] Starting server handshake")
346
- handshaker := cryptoops.NewHandshaker(lease.Cred)
347
- secConn, err := handshaker.ServerHandshake(stream, lease.Lease.Alpn)
348
- if err != nil {
349
- log.Error().Err(err).Str("lease_id", req.LeaseId).Msg("[RelayClient] Server handshake failed")
350
- err = stream.Close()
351
- if err != nil {
352
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
353
- }
354
- return
355
- }
356
-
357
- log.Debug().
358
- Str("lease_id", req.LeaseId).
359
- Str("local_id", secConn.LocalID()).
360
- Str("remote_id", secConn.RemoteID()).
361
- Msg("[RelayClient] Secure connection established, sending to incoming channel")
362
-
363
- g.incomingConnCh <- &IncomingConn{
364
- SecureConnection: secConn,
365
- leaseID: req.LeaseId,
366
- }
367
-}
368
-
369
-// GetRelayInfo requests relay server information including supported protocols,
370
-// server version, and other metadata.
371
-func (g *RelayClient) GetRelayInfo() (*rdverb.RelayInfo, error) {
372
- stream, err := g.sess.OpenStream()
373
- if err != nil {
374
- return nil, err
375
- }
376
- defer stream.Close()
377
-
378
- req := &rdverb.RelayInfoRequest{}
379
- reqPayload, err := req.MarshalVT()
380
- if err != nil {
381
- return nil, err
382
- }
383
-
384
- err = writePacket(stream, &rdverb.Packet{
385
- Type: rdverb.PacketType_PACKET_TYPE_RELAY_INFO_REQUEST,
386
- Payload: reqPayload,
387
- })
388
- if err != nil {
389
- return nil, err
390
- }
391
-
392
- respPacket, err := readPacket(stream)
393
- if err != nil {
394
- return nil, err
395
- }
396
-
397
- if respPacket.Type != rdverb.PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE {
398
- return nil, ErrInvalidResponse
399
- }
400
-
401
- var resp rdverb.RelayInfoResponse
402
- err = resp.UnmarshalVT(respPacket.Payload)
403
- if err != nil {
404
- return nil, err
405
- }
406
-
407
- return resp.RelayInfo, nil
408
-}
409
-
410
-// updateLease sends a lease update request to the relay server.
411
-// The request is signed with the provided credentials to prove ownership.
412
-// A nonce and timestamp are included to prevent replay attacks.
413
-func (g *RelayClient) updateLease(cred *cryptoops.Credential, lease *rdverb.Lease) (rdverb.ResponseCode, error) {
414
- stream, err := g.sess.OpenStream()
415
- if err != nil {
416
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
417
- }
418
- defer stream.Close()
419
-
420
- timestamp := time.Now().Unix()
421
- nonce := make([]byte, 12) // 12-byte nonce for replay protection
422
- if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
423
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
424
- }
425
-
426
- req := &rdverb.LeaseUpdateRequest{
427
- Lease: lease,
428
- Nonce: nonce,
429
- Timestamp: timestamp,
430
- }
431
-
432
- reqPayload, err := req.MarshalVT()
433
- if err != nil {
434
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
435
- }
436
-
437
- signedPayload := &rdsec.SignedPayload{
438
- Data: reqPayload,
439
- Signature: cred.Sign(reqPayload),
440
- }
441
-
442
- signedData, err := signedPayload.MarshalVT()
443
- if err != nil {
444
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
445
- }
446
-
447
- err = writePacket(stream, &rdverb.Packet{
448
- Type: rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST,
449
- Payload: signedData,
450
- })
451
- if err != nil {
452
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
453
- }
454
-
455
- respPacket, err := readPacket(stream)
456
- if err != nil {
457
- log.Error().Uint32("stream_id", stream.StreamID()).Err(err).Msg("[RelayClient] Failed to read packet from stream")
458
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
459
- }
460
-
461
- if respPacket.Type != rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE {
462
- log.Error().Uint32("stream_id", stream.StreamID()).Msg("[RelayClient] Unexpected response packet type")
463
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, ErrInvalidResponse
464
- }
465
-
466
- var resp rdverb.LeaseUpdateResponse
467
- err = resp.UnmarshalVT(respPacket.Payload)
468
- if err != nil {
469
- log.Error().Uint32("stream_id", stream.StreamID()).Err(err).Msg("[RelayClient] Failed to unmarshal lease update response")
470
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
471
- }
472
-
473
- return resp.Code, nil
474
-}
475
-
476
-// deleteLease sends a lease deletion request to the relay server.
477
-// The request is signed with the provided credentials to prove ownership.
478
-func (g *RelayClient) deleteLease(cred *cryptoops.Credential, identity *rdsec.Identity) (rdverb.ResponseCode, error) {
479
- stream, err := g.sess.OpenStream()
480
- if err != nil {
481
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
482
- }
483
- defer stream.Close()
484
-
485
- timestamp := time.Now().Unix()
486
- nonce := make([]byte, 12)
487
- if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
488
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
489
- }
490
-
491
- req := &rdverb.LeaseDeleteRequest{
492
- Identity: identity,
493
- Nonce: nonce,
494
- Timestamp: timestamp,
495
- }
496
-
497
- reqPayload, err := req.MarshalVT()
498
- if err != nil {
499
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
500
- }
501
-
502
- signedPayload := &rdsec.SignedPayload{
503
- Data: reqPayload,
504
- Signature: cred.Sign(reqPayload),
505
- }
506
-
507
- signedData, err := signedPayload.MarshalVT()
508
- if err != nil {
509
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
510
- }
511
-
512
- // Send deletion request
513
- err = writePacket(stream, &rdverb.Packet{
514
- Type: rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST,
515
- Payload: signedData,
516
- })
517
- if err != nil {
518
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
519
- }
520
-
521
- // Receive deletion response
522
- respPacket, err := readPacket(stream)
523
- if err != nil {
524
- log.Error().Uint32("stream_id", stream.StreamID()).Err(err).Msg("[RelayClient] Failed to read packet from stream")
525
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
526
- }
527
-
528
- if respPacket.Type != rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE {
529
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, ErrInvalidResponse
530
- }
531
-
532
- var resp rdverb.LeaseDeleteResponse
533
- err = resp.UnmarshalVT(respPacket.Payload)
534
- if err != nil {
535
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, err
536
- }
537
-
538
- return resp.Code, nil
539
-}
540
-
541
-// RequestConnection initiates a connection to a remote peer through the relay.
542
-// It performs the following steps:
543
-// 1. Opens a new yamux stream to the relay server
544
-// 2. Sends a connection request for the specified lease ID
545
-// 3. Waits for accept/reject response from the remote peer
546
-// 4. If accepted, performs client-side cryptographic handshake
547
-// 5. Verifies the remote peer's ID matches the lease ID
548
-//
549
-// Parameters:
550
-// - leaseID: The ID of the lease to connect to
551
-// - alpn: Application-Layer Protocol Negotiation string
552
-// - clientCred: Client's cryptographic credentials for the handshake
553
-//
554
-// Returns the response code, established secure connection (if successful), and any error.
555
-func (g *RelayClient) RequestConnection(leaseID string, alpn string, clientCred *cryptoops.Credential) (rdverb.ResponseCode, *cryptoops.SecureConnection, error) {
556
- log.Debug().Str("lease_id", leaseID).Str("alpn", alpn).Msg("[RelayClient] Requesting connection")
557
-
558
- stream, err := g.sess.OpenStream()
559
- if err != nil {
560
- log.Error().Err(err).Msg("[RelayClient] Failed to open stream for connection request")
561
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
562
- }
563
-
564
- clientIdentity := &rdsec.Identity{
565
- Id: clientCred.ID(),
566
- PublicKey: clientCred.PublicKey(),
567
- }
568
-
569
- req := &rdverb.ConnectionRequest{
570
- LeaseId: leaseID,
571
- ClientIdentity: clientIdentity,
572
- }
573
-
574
- reqPayload, err := req.MarshalVT()
575
- if err != nil {
576
- log.Error().Err(err).Msg("[RelayClient] Failed to marshal connection request")
577
- err = stream.Close()
578
- if err != nil {
579
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
580
- }
581
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
582
- }
583
-
584
- log.Debug().Str("lease_id", leaseID).Msg("[RelayClient] Sending connection request")
585
- err = writePacket(stream, &rdverb.Packet{
586
- Type: rdverb.PacketType_PACKET_TYPE_CONNECTION_REQUEST,
587
- Payload: reqPayload,
588
- })
589
- if err != nil {
590
- log.Error().Err(err).Msg("[RelayClient] Failed to write connection request packet")
591
- err = stream.Close()
592
- if err != nil {
593
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
594
- }
595
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
596
- }
597
-
598
- log.Debug().Str("lease_id", leaseID).Msg("[RelayClient] Waiting for connection response")
599
- respPacket, err := readPacket(stream)
600
- if err != nil {
601
- log.Error().Str("lease_id", leaseID).Err(err).Msg("[RelayClient] Failed to read connection response")
602
- err = stream.Close()
603
- if err != nil {
604
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
605
- }
606
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
607
- }
608
-
609
- if respPacket.Type != rdverb.PacketType_PACKET_TYPE_CONNECTION_RESPONSE {
610
- log.Warn().Str("packet_type", respPacket.Type.String()).Msg("[RelayClient] Unexpected response packet type")
611
- err = stream.Close()
612
- if err != nil {
613
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
614
- }
615
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, ErrInvalidResponse
616
- }
617
-
618
- var resp rdverb.ConnectionResponse
619
- err = resp.UnmarshalVT(respPacket.Payload)
620
- if err != nil {
621
- log.Error().Str("lease_id", leaseID).Err(err).Msg("[RelayClient] Failed to unmarshal connection response")
622
- err = stream.Close()
623
- if err != nil {
624
- log.Error().Err(err).Msg("[RelayClient] Failed to close stream")
625
- }
626
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
627
- }
628
-
629
- log.Debug().
630
- Str("lease_id", leaseID).
631
- Str("response_code", resp.Code.String()).
632
- Msg("[RelayClient] Connection response received")
633
-
634
- if resp.Code != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
635
- log.Warn().Str("lease_id", leaseID).Str("code", resp.Code.String()).Msg("[RelayClient] Connection rejected")
636
- stream.Close()
637
- return resp.Code, nil, ErrConnectionRejected
638
- }
639
-
640
- log.Debug().Str("lease_id", leaseID).Msg("[RelayClient] Starting client handshake")
641
- handshaker := cryptoops.NewHandshaker(clientCred)
642
- secConn, err := handshaker.ClientHandshake(stream, alpn)
643
- if err != nil {
644
- log.Error().Err(err).Str("lease_id", leaseID).Msg("[RelayClient] Client handshake failed")
645
- stream.Close()
646
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, err
647
- }
648
-
649
- // Verify the remote peer's ID matches the expected lease ID
650
- if secConn.RemoteID() != leaseID {
651
- log.Warn().Str("lease_id", leaseID).Msg("[RelayClient] Remote ID mismatch")
652
- stream.Close()
653
- return rdverb.ResponseCode_RESPONSE_CODE_UNKNOWN, nil, ErrRemoteIDMismatch
654
- }
655
-
656
- log.Debug().
657
- Str("lease_id", leaseID).
658
- Str("local_id", secConn.LocalID()).
659
- Str("remote_id", secConn.RemoteID()).
660
- Msg("[RelayClient] Secure connection established successfully")
661
-
662
- return resp.Code, secConn, nil
663
-}
664
-
665
-// RegisterLease registers a new lease with the relay server.
666
-// The lease allows remote clients to connect to this client via the relay.
667
-//
668
-// The lease is cloned to avoid modifying the caller's original lease object.
669
-// On registration failure, the lease is automatically removed from the local cache.
670
-func (g *RelayClient) RegisterLease(cred *cryptoops.Credential, lease *rdverb.Lease) error {
671
- lease = lease.CloneVT() // Clone to avoid modifying the original lease
672
-
673
- identity := &rdsec.Identity{
674
- Id: cred.ID(),
675
- PublicKey: cred.PublicKey(),
676
- }
677
- lease.Identity = identity
678
- lease.Expires = time.Now().Add(30 * time.Second).Unix()
679
-
680
- log.Debug().
681
- Str("lease_id", identity.Id).
682
- Str("name", lease.Name).
683
- Strs("alpns", lease.Alpn).
684
- Msg("[RelayClient] Registering lease")
685
-
686
- g.leasesMu.Lock()
687
- g.leases[identity.Id] = &leaseWithCred{
688
- Lease: lease,
689
- Cred: cred,
690
- }
691
- g.leasesMu.Unlock()
692
-
693
- resp, err := g.updateLease(cred, lease)
694
- if err != nil || resp != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
695
- log.Error().
696
- Err(err).
697
- Str("lease_id", identity.Id).
698
- Str("response", resp.String()).
699
- Msg("[RelayClient] Failed to register lease")
700
- g.leasesMu.Lock()
701
- delete(g.leases, identity.Id)
702
- g.leasesMu.Unlock()
703
- return err
704
- }
705
-
706
- log.Debug().Str("lease_id", identity.Id).Msg("[RelayClient] Lease registered successfully")
707
- return nil
708
-}
709
-
710
-// DeregisterLease removes a lease from the relay server.
711
-// It removes the lease from the local cache immediately, then notifies the server.
712
-func (g *RelayClient) DeregisterLease(cred *cryptoops.Credential) error {
713
- identity := &rdsec.Identity{
714
- Id: cred.ID(),
715
- PublicKey: cred.PublicKey(),
716
- }
717
-
718
- g.leasesMu.Lock()
719
- delete(g.leases, identity.Id)
720
- g.leasesMu.Unlock()
721
-
722
- resp, err := g.deleteLease(cred, identity)
723
- if err != nil || resp != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
724
- log.Error().Err(err).Str("lease_id", identity.Id).Msg("[RelayClient] Failed to deregister lease")
725
- return err
726
- }
727
-
728
- log.Debug().Str("lease_id", identity.Id).Msg("[RelayClient] Lease unregistered successfully")
729
- return nil
730
-}
731
-
732
-// IncomingConnection returns a receive-only channel for incoming connections.
733
-// The channel is closed when the relay client is shut down.
734
-func (g *RelayClient) IncomingConnection() <-chan *IncomingConn {
735
- return g.incomingConnCh
736
-}
portal/core/cryptoops/README.md
deleted
-587
@@ -1,587 +0,0 @@
1
-# Cryptographic Operations & End-to-End Encryption (E2EE)
2
-
3
-This package implements a secure, authenticated end-to-end encryption protocol for Portal using modern cryptographic primitives and best practices.
4
-
5
-## Table of Contents
6
-
7
-- [Overview](#overview)
8
-- [Cryptographic Primitives](#cryptographic-primitives)
9
-- [Protocol Flow](#protocol-flow)
10
-- [Key Derivation](#key-derivation)
11
-- [Message Format](#message-format)
12
-- [Security Properties](#security-properties)
13
-- [Implementation Details](#implementation-details)
14
-- [Error Handling](#error-handling)
15
-
16
-## Overview
17
-
18
-The E2EE protocol provides:
19
-- **Mutual Authentication**: Both client and server verify each other's identities using Ed25519 signatures
20
-- **Forward Secrecy**: Ephemeral X25519 key exchange ensures past sessions remain secure even if long-term keys are compromised
21
-- **Confidentiality**: All application data is encrypted using ChaCha20-Poly1305 AEAD
22
-- **Integrity**: AEAD authentication tags prevent tampering
23
-- **Replay Protection**: Timestamps and random nonces prevent replay attacks
24
-
25
-## Cryptographic Primitives
26
-
27
-### 1. Ed25519 Digital Signatures
28
-- **Purpose**: Long-term identity authentication
29
-- **Key Size**: 32 bytes (256 bits)
30
-- **Signature Size**: 64 bytes
31
-- **Properties**: Deterministic, collision-resistant, provides non-repudiation
32
-
33
-Each peer has a long-term Ed25519 keypair that identifies them:
34
-```go
35
-type Credential struct {
36
- privateKey ed25519.PrivateKey // 64 bytes
37
- publicKey ed25519.PublicKey // 32 bytes
38
- id string // Base32-encoded HMAC-SHA256 of public key
39
-}
40
-```
41
-
42
-#### Identity Derivation Algorithm
43
-
44
-The ID field is deterministically derived from the Ed25519 public key using a secure hashing process:
45
-
46
-```go
47
-var _id_magic = []byte("RDVERB_PROTOCOL_VER_01_SHA256_ID")
48
-var _base32_encoding = base32.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567").WithPadding(base32.NoPadding)
49
-
50
-func DeriveID(publickey ed25519.PublicKey) string {
51
- h := hmac.New(sha256.New, _id_magic)
52
- h.Write(publickey)
53
- hash := h.Sum(nil)
54
- return _base32_encoding.EncodeToString(hash[:16])
55
-}
56
-```
57
-
58
-**Algorithm Steps:**
59
-1. **HMAC-SHA256**: Compute HMAC-SHA256(publicKey, "RDVERB_PROTOCOL_VER_01_SHA256_ID")
60
- - Input: 32-byte Ed25519 public key
61
- - HMAC Key: Protocol-specific magic string
62
- - Output: 32-byte hash
63
-2. **Truncation**: Take first 128 bits (16 bytes) of hash
64
-3. **Base32 Encoding**: Encode with custom alphabet (no padding)
65
- - Alphabet: `ABCDEFGHIJKLMNOPQRSTUVWXYZ234567`
66
- - Padding: None
67
- - Output: 26-character alphanumeric string
68
-
69
-**Security Properties:**
70
-- **Deterministic**: Same public key → same ID (enables caching and verification)
71
-- **One-way**: Computationally infeasible to derive public key from ID
72
-- **Collision-resistant**: 128-bit security provides ~10³⁸ unique IDs
73
-- **Protocol-bound**: HMAC key prevents cross-protocol ID collision attacks
74
-- **Compact**: 26 characters enable efficient URL encoding and database indexing
75
-
76
-
77
-### 2. X25519 Key Exchange (Curve25519)
78
-- **Purpose**: Ephemeral session key agreement
79
-- **Key Size**: 32 bytes (256 bits)
80
-- **Properties**: ECDH over Curve25519, provides forward secrecy
81
-
82
-For each connection, both parties generate a fresh X25519 keypair:
83
-```go
84
-ephemeralPriv := make([]byte, 32) // Scalar
85
-ephemeralPub, _ := curve25519.X25519(ephemeralPriv, curve25519.Basepoint)
86
-```
87
-
88
-The shared secret is computed as:
89
-```go
90
-sharedSecret := curve25519.X25519(myPriv, theirPub)
91
-```
92
-
93
-### 3. ChaCha20-Poly1305 AEAD
94
-- **Purpose**: Authenticated encryption of application data
95
-- **Key Size**: 32 bytes (256 bits)
96
-- **Nonce Size**: 12 bytes (96 bits)
97
-- **Tag Size**: 16 bytes (128 bits)
98
-- **Properties**: Fast, constant-time, provides confidentiality + authenticity
99
-
100
-Each encrypted message includes:
101
-- 12-byte random nonce (generated using CSPRNG)
102
-- Ciphertext (same length as plaintext)
103
-- 16-byte Poly1305 authentication tag
104
-
105
-### 4. HKDF-SHA256 Key Derivation
106
-- **Purpose**: Derive separate encryption keys from shared secret
107
-- **Hash Function**: SHA-256
108
-- **Properties**: Cryptographically strong key derivation, domain separation
109
-
110
-Parameters:
111
-- **IKM (Input Key Material)**: X25519 shared secret (32 bytes)
112
-- **Salt**: Concatenation of both nonces (24 bytes)
113
-- **Info**: Direction-specific context strings
114
- - Client → Server: `"RDSEC_KEY_CLIENT"`
115
- - Server → Client: `"RDSEC_KEY_SERVER"`
116
-- **Output**: 32-byte symmetric keys
117
-
118
-## Protocol Flow
119
-
120
-### Phase 1: Client Initialization
121
-
122
-1. **Generate Ephemeral Keypair**
123
- ```go
124
- clientEphemeralPriv, clientEphemeralPub := generateX25519KeyPair()
125
- ```
126
-
127
-2. **Create ClientInitPayload**
128
- ```protobuf
129
- message ClientInitPayload {
130
- ProtocolVersion version = 1; // PROTOCOL_VERSION_1
131
- bytes nonce = 2; // 12 random bytes
132
- int64 timestamp = 3; // Unix timestamp (seconds)
133
- Identity identity = 4; // Client's Ed25519 identity
134
- string alpn = 5; // Application-Layer Protocol Negotiation
135
- bytes session_public_key = 6; // clientEphemeralPub (32 bytes)
136
- }
137
- ```
138
-
139
-3. **Sign and Send**
140
- ```go
141
- payloadBytes := proto.Marshal(clientInitPayload)
142
- signature := ed25519.Sign(clientPrivateKey, payloadBytes)
143
-
144
- signedPayload := &SignedPayload{
145
- Data: payloadBytes,
146
- Signature: signature,
147
- }
148
-
149
- // Send length-prefixed message (4 bytes length + data)
150
- writeLengthPrefixed(conn, proto.Marshal(signedPayload))
151
- ```
152
-
153
-### Phase 2: Server Validation and Response
154
-
155
-1. **Receive and Validate Client Init**
156
- - Unmarshal SignedPayload
157
- - Verify protocol version is PROTOCOL_VERSION_1
158
- - Validate timestamp is within ±30 seconds
159
- - Verify ALPN matches expected value(s)
160
- - Validate identity structure (correct key sizes)
161
- - Verify Ed25519 signature using client's public key
162
-
163
- **Security Note**: If validation fails, server closes connection silently (no error response) to prevent information leakage.
164
-
165
-2. **Generate Server Ephemeral Keypair**
166
- ```go
167
- serverEphemeralPriv, serverEphemeralPub := generateX25519KeyPair()
168
- ```
169
-
170
-3. **Create and Send ServerInitPayload**
171
- - Similar structure to ClientInitPayload
172
- - Contains server's identity and ephemeral public key
173
- - Signed with server's Ed25519 private key
174
-
175
-### Phase 3: Key Derivation
176
-
177
-Both client and server independently derive the same shared secret but use it to create **different directional keys**:
178
-
179
-```go
180
-// Compute X25519 shared secret (identical for both parties)
181
-sharedSecret := curve25519.X25519(myEphemeralPriv, theirEphemeralPub)
182
-
183
-// Derive directional keys with HKDF
184
-```
185
-
186
-**Client's Key Derivation:**
187
-```go
188
-// Client encrypts with this key (Server decrypts)
189
-salt := clientNonce || serverNonce
190
-clientEncryptKey := HKDF-SHA256(sharedSecret, salt, "RDSEC_KEY_CLIENT")
191
-
192
-// Client decrypts with this key (Server encrypts)
193
-salt := serverNonce || clientNonce
194
-clientDecryptKey := HKDF-SHA256(sharedSecret, salt, "RDSEC_KEY_SERVER")
195
-```
196
-
197
-**Server's Key Derivation:**
198
-```go
199
-// Server encrypts with this key (Client decrypts)
200
-salt := serverNonce || clientNonce
201
-serverEncryptKey := HKDF-SHA256(sharedSecret, salt, "RDSEC_KEY_SERVER")
202
-
203
-// Server decrypts with this key (Client encrypts)
204
-salt := clientNonce || serverNonce
205
-serverDecryptKey := HKDF-SHA256(sharedSecret, salt, "RDSEC_KEY_CLIENT")
206
-```
207
-
208
-**Key Properties:**
209
-- Different salts ensure different keys for each direction
210
-- Info strings provide domain separation
211
-- Both parties can communicate bidirectionally with different keys
212
-- Nonce ordering in salt is critical for correctness
213
-
214
-### Phase 4: Secure Communication
215
-
216
-After handshake, all application data flows through `SecureConnection`:
217
-
218
-```go
219
-type SecureConnection struct {
220
- conn io.ReadWriteCloser
221
- encryptor cipher.AEAD // ChaCha20-Poly1305 with my encryption key
222
- decryptor cipher.AEAD // ChaCha20-Poly1305 with my decryption key
223
- readBuffer *bytebufferpool.ByteBuffer
224
-}
225
-```
226
-
227
-## Message Format
228
-
229
-### Handshake Messages (Length-Prefixed)
230
-
231
-```
232
-+-------------------+-------------------+
233
-| Length (4 bytes) | Protobuf Payload |
234
-| Big Endian Uint32 | (variable length) |
235
-+-------------------+-------------------+
236
-```
237
-
238
-### Encrypted Application Messages
239
-
240
-```
241
-+-------------------+-------------------+-------------------+-------------------+
242
-| Length (4 bytes) | Nonce (12 bytes) | Ciphertext | Tag (16 bytes) |
243
-| Big Endian Uint32 | Random | (variable length) | Poly1305 MAC |
244
-+-------------------+-------------------+-------------------+-------------------+
245
-```
246
-
247
-**Length Field**: Total size of (nonce + ciphertext + tag)
248
-
249
-**Fragmentation**: Messages larger than 32MB are automatically fragmented:
250
-```go
251
-const fragSize = maxRawPacketSize / 2 // 32MB
252
-```
253
-
254
-This prevents excessive memory allocation while maintaining compatibility with the relay server's 64MB packet limit.
255
-
256
-### Encryption Process
257
-
258
-```go
259
-func (sc *SecureConnection) Write(p []byte) (int, error) {
260
- // 1. Generate random nonce
261
- nonce := randomBytes(12)
262
-
263
- // 2. Encrypt with AEAD
264
- ciphertext := encryptor.Seal(nil, nonce, plaintext, nil)
265
- // ciphertext = encrypted_data || tag
266
-
267
- // 3. Frame: length + nonce + ciphertext
268
- length := len(nonce) + len(ciphertext)
269
- frame := length (4 bytes) || nonce || ciphertext
270
-
271
- // 4. Write to connection
272
- conn.Write(frame)
273
-}
274
-```
275
-
276
-### Decryption Process
277
-
278
-```go
279
-func (sc *SecureConnection) Read(p []byte) (int, error) {
280
- // 1. Read 4-byte length prefix
281
- lengthBytes := readFull(4)
282
- length := binary.BigEndian.Uint32(lengthBytes)
283
-
284
- // 2. Validate size limit
285
- if length > maxRawPacketSize {
286
- return error
287
- }
288
-
289
- // 3. Read encrypted message
290
- msgBytes := readFull(length)
291
- nonce := msgBytes[0:12]
292
- ciphertext := msgBytes[12:]
293
-
294
- // 4. Decrypt and authenticate
295
- plaintext, err := decryptor.Open(nil, nonce, ciphertext, nil)
296
- if err != nil {
297
- return ErrDecryptionFailed // Authentication failed
298
- }
299
-
300
- // 5. Copy to output buffer
301
- copy(p, plaintext)
302
-}
303
-```
304
-
305
-## Security Properties
306
-
307
-### 1. Authentication
308
-- **Mutual**: Both parties authenticate each other's long-term identities
309
-- **Signature-based**: Ed25519 signatures over handshake payloads
310
-- **Identity binding**: Public keys are cryptographically bound to identity IDs via HMAC-SHA256
311
- - HMAC key: `"RDVERB_PROTOCOL_VER_01_SHA256_ID"`
312
- - Output: First 128 bits of HMAC-SHA256(publicKey, magic)
313
- - Encoding: Base32 (custom alphabet, no padding)
314
- - Result: 26-character deterministic ID
315
-
316
-### 2. Forward Secrecy
317
-- **Ephemeral Keys**: Fresh X25519 keypair per connection
318
-- **Perfect Forward Secrecy**: Compromise of long-term keys doesn't compromise past sessions
319
-- **Session Isolation**: Each connection uses unique ephemeral keys
320
-
321
-### 3. Confidentiality
322
-- **Strong Cipher**: ChaCha20 stream cipher (256-bit security)
323
-- **Unique Nonces**: Random nonces for each message prevent deterministic encryption
324
-- **No IV Reuse**: CSPRNG-generated nonces ensure probabilistic encryption
325
-
326
-### 4. Integrity & Authenticity
327
-- **AEAD**: Poly1305 MAC provides 128-bit authentication
328
-- **Tamper Detection**: Any modification causes decryption failure
329
-- **No Decrypt-Then-Parse**: Authentication checked before processing
330
-
331
-### 5. Replay Protection
332
-- **Timestamp Validation**: Handshake messages must be within ±30 seconds
333
- ```go
334
- maxTimestampSkew = 30 * time.Second
335
- ```
336
-- **Random Nonces**: Prevent message replay within session
337
-- **No Sequence Numbers**: Stateless design, relies on AEAD and nonces
338
-
339
-### 6. Resistance to Attacks
340
-
341
-**Man-in-the-Middle (MitM)**:
342
-- Attacker cannot forge Ed25519 signatures
343
-- Cannot derive session keys without ephemeral private keys
344
-- Signature verification prevents impersonation
345
-
346
-**Replay Attacks**:
347
-- Timestamp window limits handshake replay
348
-- Unique ephemeral keys per session prevent session replay
349
-- Random nonces prevent message replay
350
-
351
-**Downgrade Attacks**:
352
-- Protocol version explicitly checked
353
-- Only PROTOCOL_VERSION_1 accepted
354
-- Future versions can be added safely
355
-
356
-**Denial of Service (DoS)**:
357
-- Packet size limits prevent memory exhaustion
358
-- Silent failure on invalid handshakes (no amplification)
359
-- Constant-time operations where possible
360
-
361
-**Side-Channel Attacks**:
362
-- ChaCha20 is designed for constant-time operation
363
-- Curve25519 uses constant-time implementation
364
-- Sensitive keys wiped from memory after use
365
- ```go
366
- func wipeMemory(b []byte) {
367
- for i := range b {
368
- b[i] = 0
369
- }
370
- }
371
- ```
372
-
373
-## Implementation Details
374
-
375
-### Memory Management
376
-
377
-The implementation uses careful memory management to minimize allocations and protect sensitive data:
378
-
379
-```go
380
-// Secure buffer pool for sensitive data
381
-var _secureMemoryPool bytebufferpool.Pool
382
-
383
-func bufferGrow(buffer *bytebufferpool.ByteBuffer, n int) {
384
- currentCap := cap(buffer.B)
385
- if n > currentCap {
386
- wipeMemory(buffer.B)
387
- // Align to 16KB boundaries
388
- newSize := (n + 16383) &^ 16383
389
- buffer.B = make([]byte, 0, newSize)
390
- }
391
- buffer.B = buffer.B[:0]
392
-}
393
-
394
-// Acquire buffer with auto-growing and alignment
395
-func acquireBuffer(n int) *bytebufferpool.ByteBuffer {
396
- buffer := _secureMemoryPool.Get()
397
- if buffer.B == nil {
398
- buffer.B = make([]byte, 0)
399
- }
400
- bufferGrow(buffer, n)
401
- return buffer
402
-}
403
-
404
-// Release and wipe buffer
405
-func releaseBuffer(buffer *bytebufferpool.ByteBuffer) {
406
- wipeMemory(buffer.B) // Zero before returning to pool
407
- _secureMemoryPool.Put(buffer)
408
-}
409
-```
410
-
411
-**Benefits:**
412
-- Reduces GC pressure through pooling
413
-- Prevents sensitive data from lingering in memory
414
-- 16KB-aligned allocations for efficiency
415
-- Constant-time memory wiping
416
-
417
-### Random Number Generation
418
-
419
-Cryptographically secure random numbers are critical:
420
-
421
-```go
422
-import "gosuda.org/portal/portal/internal/randpool"
423
-
424
-// Generate random nonce
425
-nonce := make([]byte, nonceSize)
426
-randpool.CSPRNG_RAND(nonce) // Uses crypto/rand internally
427
-```
428
-
429
-**Never use `math/rand`** for security-sensitive operations. All nonces, ephemeral keys, and IVs must come from a CSPRNG.
430
-
431
-### Error Handling Strategy
432
-
433
-```go
434
-var (
435
- ErrHandshakeFailed = errors.New("handshake failed")
436
- ErrInvalidSignature = errors.New("invalid signature")
437
- ErrInvalidTimestamp = errors.New("invalid timestamp")
438
- ErrInvalidProtocol = errors.New("invalid protocol version")
439
- ErrInvalidIdentity = errors.New("invalid identity")
440
- ErrSessionKeyDerive = errors.New("failed to derive session key")
441
- ErrEncryptionFailed = errors.New("encryption failed")
442
- ErrDecryptionFailed = errors.New("decryption failed")
443
- ErrInvalidNonce = errors.New("invalid nonce")
444
-)
445
-```
446
-
447
-**Server Silent Failure**: When server validation fails during handshake, it closes the connection immediately without sending an error response. This prevents information leakage about why the handshake failed:
448
-
449
-```go
450
-if err := h.validateClientInit(...); err != nil {
451
- conn.Close() // Silent close
452
- return nil, err
453
-}
454
-```
455
-
456
-### ALPN (Application-Layer Protocol Negotiation)
457
-
458
-ALPN allows protocol negotiation during handshake:
459
-
460
-```go
461
-// Client specifies desired protocol
462
-clientInit.Alpn = "relay-v1"
463
-
464
-// Server validates against allowed protocols
465
-expectedAlpns := []string{"relay-v1", "relay-v2"}
466
-if !slices.Contains(expectedAlpns, clientInit.Alpn) {
467
- return ErrHandshakeFailed
468
-}
469
-
470
-// Server echoes back the negotiated protocol
471
-serverInit.Alpn = clientInit.Alpn
472
-```
473
-
474
-This enables protocol versioning and feature negotiation without breaking compatibility.
475
-
476
-### Constants and Limits
477
-
478
-```go
479
-const (
480
- nonceSize = 12 // ChaCha20Poly1305 standard nonce
481
- sessionKeySize = 32 // 256-bit symmetric keys
482
- maxTimestampSkew = 30 * time.Second // Clock skew tolerance
483
- maxRawPacketSize = 1 << 26 // 64MB - matches relay server
484
-
485
- // Key derivation context strings
486
- clientKeyInfo = "RDSEC_KEY_CLIENT"
487
- serverKeyInfo = "RDSEC_KEY_SERVER"
488
-)
489
-```
490
-
491
-### Read Buffer Management
492
-
493
-`SecureConnection` maintains a read buffer to handle partial reads:
494
-
495
-```go
496
-type SecureConnection struct {
497
- readBuffer *bytebufferpool.ByteBuffer // Stores leftover decrypted data
498
-}
499
-
500
-func (sc *SecureConnection) Read(p []byte) (int, error) {
501
- // First check if we have buffered data
502
- if len(sc.readBuffer.B) > 0 {
503
- n := copy(p, sc.readBuffer.B)
504
- // Shift remaining data to front
505
- copy(sc.readBuffer.B[:len(sc.readBuffer.B)-n], sc.readBuffer.B[n:])
506
- sc.readBuffer.B = sc.readBuffer.B[:len(sc.readBuffer.B)-n]
507
- return n, nil
508
- }
509
-
510
- // Otherwise, decrypt new packet...
511
-}
512
-```
513
-
514
-This ensures correct behavior when the output buffer is smaller than the decrypted message.
515
-
516
-## Best Practices
517
-
518
-### DO ✓
519
-
520
-1. **Always validate protocol version** before processing handshake messages
521
-2. **Check timestamp** within reasonable window (±30s)
522
-3. **Verify signatures** before trusting identity claims
523
-4. **Use unique nonces** for each encrypted message
524
-5. **Wipe sensitive data** from memory after use
525
-6. **Limit packet sizes** to prevent resource exhaustion
526
-7. **Use constant-time operations** where possible
527
-8. **Handle errors securely** (no information leakage)
528
-
529
-### DON'T ✗
530
-
531
-1. **Never reuse nonces** with the same key
532
-2. **Never skip signature verification**
533
-3. **Never trust timestamps** without validation
534
-4. **Never send error details** in handshake failures
535
-5. **Never use `math/rand`** for security operations
536
-6. **Never ignore return values** from crypto functions
537
-7. **Never log sensitive data** (keys, plaintexts)
538
-8. **Never implement custom crypto** without expert review
539
-
540
-## Testing Considerations
541
-
542
-When testing this implementation:
543
-
544
-1. **Handshake Tests**
545
- - Valid handshake flows (client and server)
546
- - Invalid signatures
547
- - Timestamp skew scenarios
548
- - Protocol version mismatches
549
- - Invalid ALPN
550
- - Malformed messages
551
-
552
-2. **Encryption Tests**
553
- - Round-trip encryption/decryption
554
- - Large messages (fragmentation)
555
- - Concurrent reads/writes
556
- - Buffer boundary conditions
557
-
558
-3. **Security Tests**
559
- - Replay attack resistance
560
- - Tampering detection
561
- - Key isolation between sessions
562
- - Memory wiping verification
563
-
564
-4. **Integration Tests**
565
- - End-to-end communication
566
- - Error propagation
567
- - Connection lifecycle
568
- - Performance benchmarks
569
-
570
-## References
571
-
572
-- **X25519**: [RFC 7748](https://tools.ietf.org/html/rfc7748)
573
-- **Ed25519**: [RFC 8032](https://tools.ietf.org/html/rfc8032)
574
-- **ChaCha20-Poly1305**: [RFC 8439](https://tools.ietf.org/html/rfc8439)
575
-- **HKDF**: [RFC 5869](https://tools.ietf.org/html/rfc5869)
576
-- **ALPN**: [RFC 7301](https://tools.ietf.org/html/rfc7301)
577
-
578
-## Changelog
579
-
580
-### Version 1.0 (Current)
581
-- Initial implementation
582
-- X25519 + ChaCha20-Poly1305 AEAD
583
-- Ed25519 identity signatures
584
-- HKDF-SHA256 key derivation
585
-- Timestamp-based replay protection
586
-- ALPN support
587
-- Automatic message fragmentation
portal/core/cryptoops/handshaker.go
deleted
-677
@@ -1,677 +0,0 @@
1
-package cryptoops
2
-
3
-import (
4
- "crypto/cipher"
5
- "crypto/ed25519"
6
- "crypto/rand"
7
- "crypto/sha256"
8
- "encoding/binary"
9
- "errors"
10
- "fmt"
11
- "io"
12
- "net"
13
- "slices"
14
- "sync"
15
- "time"
16
-
17
- "golang.org/x/crypto/chacha20poly1305"
18
- "golang.org/x/crypto/curve25519"
19
- "golang.org/x/crypto/hkdf"
20
-
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{
27
- New: func() interface{} {
28
- return new([4]byte)
29
- },
30
-}
31
-
32
-var _secureMemoryPool bytebufferpool.Pool
33
-
34
-func wipeMemory(b []byte) {
35
- b = b[:cap(b)]
36
- for i := range b {
37
- b[i] = 0
38
- }
39
-}
40
-
41
-func bufferGrow(buffer *bytebufferpool.ByteBuffer, n int) {
42
- currentCap := cap(buffer.B)
43
- if n > currentCap {
44
- wipeMemory(buffer.B)
45
- // Align to 16KB boundaries
46
- newSize := (n + 16383) &^ 16383
47
- buffer.B = make([]byte, 0, newSize)
48
- }
49
- buffer.B = buffer.B[:0]
50
-}
51
-
52
-func acquireBuffer(n int) *bytebufferpool.ByteBuffer {
53
- buffer := _secureMemoryPool.Get()
54
- if buffer.B == nil {
55
- buffer.B = make([]byte, 0)
56
- }
57
- bufferGrow(buffer, n)
58
- return buffer
59
-}
60
-
61
-func releaseBuffer(buffer *bytebufferpool.ByteBuffer) {
62
- wipeMemory(buffer.B)
63
- _secureMemoryPool.Put(buffer)
64
-}
65
-
66
-var (
67
- ErrHandshakeFailed = errors.New("handshake failed")
68
- ErrInvalidSignature = errors.New("invalid signature")
69
- ErrInvalidTimestamp = errors.New("invalid timestamp")
70
- ErrInvalidProtocol = errors.New("invalid protocol version")
71
- ErrInvalidIdentity = errors.New("invalid identity")
72
- ErrSessionKeyDerive = errors.New("failed to derive session key")
73
- ErrEncryptionFailed = errors.New("encryption failed")
74
- ErrDecryptionFailed = errors.New("decryption failed")
75
- ErrInvalidNonce = errors.New("invalid nonce")
76
-)
77
-
78
-const (
79
- nonceSize = 12 // ChaCha20Poly1305 nonce size
80
- sessionKeySize = 32 // X25519 shared secret size
81
- maxTimestampSkew = 30 * time.Second
82
- maxRawPacketSize = 1 << 26 // 64MB - same as relay server
83
-
84
- // HKDF info strings for key derivation
85
- clientKeyInfo = "RDSEC_KEY_CLIENT"
86
- serverKeyInfo = "RDSEC_KEY_SERVER"
87
-)
88
-
89
-// Handshaker handles the X25519-ChaCha20Poly1305 based handshake protocol
90
-type Handshaker struct {
91
- credential *Credential
92
-}
93
-
94
-// NewHandshaker creates a new Handshaker with the given credential
95
-func NewHandshaker(credential *Credential) *Handshaker {
96
- return &Handshaker{
97
- credential: credential,
98
- }
99
-}
100
-
101
-// SecureConnection represents a secured connection with encryption capabilities
102
-type SecureConnection struct {
103
- conn io.ReadWriteCloser
104
-
105
- localID string
106
- remoteID string
107
-
108
- encryptor cipher.AEAD
109
- decryptor cipher.AEAD
110
-
111
- readBuffer *bytebufferpool.ByteBuffer
112
-
113
- // Ensure Close is safe and idempotent
114
- mu sync.RWMutex
115
- closed bool
116
- closeOnce sync.Once
117
- closeErr error
118
-}
119
-
120
-func (r *SecureConnection) SetDeadline(t time.Time) error {
121
- if conn, ok := r.conn.(interface{ SetDeadline(time.Time) error }); ok {
122
- return conn.SetDeadline(t)
123
- }
124
- return nil
125
-}
126
-
127
-func (r *SecureConnection) SetReadDeadline(t time.Time) error {
128
- if conn, ok := r.conn.(interface{ SetReadDeadline(time.Time) error }); ok {
129
- return conn.SetReadDeadline(t)
130
- }
131
- return nil
132
-}
133
-
134
-func (r *SecureConnection) SetWriteDeadline(t time.Time) error {
135
- if conn, ok := r.conn.(interface{ SetWriteDeadline(time.Time) error }); ok {
136
- return conn.SetWriteDeadline(t)
137
- }
138
- return nil
139
-}
140
-
141
-func (sc *SecureConnection) LocalID() string {
142
- return sc.localID
143
-}
144
-
145
-func (sc *SecureConnection) RemoteID() string {
146
- return sc.remoteID
147
-}
148
-
149
-// Write encrypts and writes data to the underlying connection
150
-func (sc *SecureConnection) Write(p []byte) (int, error) {
151
- sc.mu.RLock()
152
- if sc.closed {
153
- sc.mu.RUnlock()
154
- return 0, net.ErrClosed
155
- }
156
- sc.mu.RUnlock()
157
-
158
- const fragSize = maxRawPacketSize / 2
159
- if len(p) > fragSize {
160
- numFrags := (len(p) + fragSize - 1) / fragSize // ceiling division
161
- for i := range numFrags {
162
- start := i * fragSize
163
- end := min(start+fragSize, len(p))
164
- _, err := sc.writeFragmentation(p[start:end])
165
- if err != nil {
166
- return 0, err
167
- }
168
- }
169
- return len(p), nil
170
- }
171
- return sc.writeFragmentation(p)
172
-}
173
-
174
-// writeFragmentation
175
-func (sc *SecureConnection) writeFragmentation(p []byte) (int, error) {
176
- cipherSize := sc.encryptor.NonceSize() + len(p) + sc.encryptor.Overhead()
177
- bufferSize := 4 + cipherSize
178
- buffer := acquireBuffer(bufferSize)
179
- buffer.B = buffer.B[:bufferSize]
180
- defer releaseBuffer(buffer)
181
-
182
- binary.BigEndian.PutUint32(buffer.B[:4], uint32(cipherSize))
183
-
184
- randpool.Rand(buffer.B[4 : 4+sc.encryptor.NonceSize()])
185
-
186
- sc.encryptor.Seal(
187
- buffer.B[4+sc.encryptor.NonceSize():][:0], // len(0), cap(len(p)+Overhead)
188
- buffer.B[4:4+sc.encryptor.NonceSize()],
189
- p,
190
- nil,
191
- )
192
-
193
- _, err := sc.conn.Write(buffer.B)
194
- if err != nil {
195
- return 0, err
196
- }
197
-
198
- return len(p), nil
199
-}
200
-
201
-// Read reads and decrypts data from the underlying connection
202
-func (sc *SecureConnection) Read(p []byte) (int, error) {
203
- sc.mu.RLock()
204
- if sc.closed {
205
- sc.mu.RUnlock()
206
- return 0, net.ErrClosed
207
- }
208
-
209
- if sc.readBuffer != nil && len(sc.readBuffer.B) > 0 {
210
- n := copy(p, sc.readBuffer.B)
211
- copy(sc.readBuffer.B[:len(sc.readBuffer.B)-n], sc.readBuffer.B[n:])
212
- sc.readBuffer.B = sc.readBuffer.B[:len(sc.readBuffer.B)-n]
213
- sc.mu.RUnlock()
214
- return n, nil
215
- }
216
- sc.mu.RUnlock()
217
-
218
- // Read length prefix first (4 bytes)
219
- lengthBuf := _lengthBufferPool.Get().(*[4]byte)
220
- _, err := io.ReadFull(sc.conn, lengthBuf[:])
221
- if err != nil {
222
- return 0, err
223
- }
224
- length := binary.BigEndian.Uint32(lengthBuf[:])
225
- _lengthBufferPool.Put(lengthBuf)
226
-
227
- // Check packet size limit
228
- if length > maxRawPacketSize {
229
- return 0, ErrDecryptionFailed
230
- }
231
-
232
- // Read the message
233
- msgBuf := acquireBuffer(int(length))
234
- msgBuf.B = msgBuf.B[:length]
235
- defer releaseBuffer(msgBuf)
236
- _, err = io.ReadFull(sc.conn, msgBuf.B)
237
- if err != nil {
238
- return 0, err
239
- }
240
-
241
- // length check
242
- if len(msgBuf.B) < sc.decryptor.NonceSize()+sc.decryptor.Overhead() {
243
- return 0, ErrDecryptionFailed
244
- }
245
-
246
- // Extract nonce and ciphertext
247
- nonce := msgBuf.B[0:sc.decryptor.NonceSize()]
248
- ciphertext := msgBuf.B[sc.decryptor.NonceSize():]
249
-
250
- // Decrypt the data in-place
251
- decrypted, err := sc.decryptor.Open(ciphertext[:0], nonce, ciphertext, nil)
252
- if err != nil {
253
- return 0, ErrDecryptionFailed
254
- }
255
-
256
- sc.mu.Lock()
257
- defer sc.mu.Unlock()
258
-
259
- if sc.closed {
260
- return 0, net.ErrClosed
261
- }
262
-
263
- // Copy decrypted data to the provided buffer
264
- n := copy(p, decrypted)
265
- if n < len(decrypted) {
266
- if sc.readBuffer == nil {
267
- sc.readBuffer = acquireBuffer(len(decrypted) - n)
268
- }
269
- sc.readBuffer.B = append(sc.readBuffer.B, decrypted[n:]...)
270
- }
271
-
272
- return n, nil
273
-}
274
-
275
-// Close closes the underlying connection and releases resources
276
-func (sc *SecureConnection) Close() error {
277
- sc.closeOnce.Do(func() {
278
- sc.mu.Lock()
279
- sc.closed = true
280
- if sc.readBuffer != nil {
281
- releaseBuffer(sc.readBuffer)
282
- sc.readBuffer = nil
283
- }
284
- sc.mu.Unlock()
285
- sc.closeErr = sc.conn.Close()
286
- })
287
- return sc.closeErr
288
-}
289
-
290
-// ClientHandshake performs the client-side of the handshake
291
-func (h *Handshaker) ClientHandshake(conn io.ReadWriteCloser, alpn string) (*SecureConnection, error) {
292
- // Generate ephemeral key pair for this session
293
- ephemeralPriv, ephemeralPub, err := generateX25519KeyPair()
294
- if err != nil {
295
- return nil, ErrHandshakeFailed
296
- }
297
-
298
- // Create client init message
299
- timestamp := time.Now().Unix()
300
- nonce := make([]byte, nonceSize)
301
- if _, err := rand.Read(nonce); err != nil {
302
- return nil, ErrHandshakeFailed
303
- }
304
-
305
- clientInitPayload := &rdsec.ClientInitPayload{
306
- Version: rdsec.ProtocolVersion_PROTOCOL_VERSION_1,
307
- Nonce: nonce,
308
- Timestamp: timestamp,
309
- Identity: &rdsec.Identity{
310
- Id: h.credential.ID(),
311
- PublicKey: h.credential.PublicKey(),
312
- },
313
- Alpn: alpn,
314
- SessionPublicKey: ephemeralPub,
315
- }
316
-
317
- // Serialize and sign the payload
318
- payloadBytes, err := clientInitPayload.MarshalVT()
319
- if err != nil {
320
- return nil, ErrHandshakeFailed
321
- }
322
-
323
- signature := h.credential.Sign(payloadBytes)
324
-
325
- clientInit := &rdsec.SignedPayload{
326
- Data: payloadBytes,
327
- Signature: signature,
328
- }
329
-
330
- // Send client init message
331
- clientInitBytes, err := clientInit.MarshalVT()
332
- if err != nil {
333
- return nil, ErrHandshakeFailed
334
- }
335
-
336
- // Write length-prefixed message
337
- if err := writeLengthPrefixed(conn, clientInitBytes); err != nil {
338
- return nil, ErrHandshakeFailed
339
- }
340
-
341
- // Read server init response
342
- serverInitBytes, err := readLengthPrefixed(conn)
343
- if err != nil {
344
- return nil, ErrHandshakeFailed
345
- }
346
-
347
- serverInitSigned := &rdsec.SignedPayload{}
348
- if err := serverInitSigned.UnmarshalVT(serverInitBytes); err != nil {
349
- return nil, ErrHandshakeFailed
350
- }
351
-
352
- // Unmarshal the server init payload
353
- serverInitPayload := &rdsec.ServerInitPayload{}
354
- if err := serverInitPayload.UnmarshalVT(serverInitSigned.GetData()); err != nil {
355
- return nil, ErrHandshakeFailed
356
- }
357
-
358
- // Validate server init
359
- if err := h.validateServerInit(serverInitSigned, serverInitPayload); err != nil {
360
- return nil, err
361
- }
362
-
363
- // Derive session keys
364
- clientEncryptKey, clientDecryptKey, err := h.deriveClientSessionKeys(
365
- ephemeralPriv, serverInitPayload.GetSessionPublicKey(),
366
- clientInitPayload.GetNonce(), serverInitPayload.GetNonce(),
367
- )
368
- if err != nil {
369
- return nil, err
370
- }
371
-
372
- wipeMemory(ephemeralPriv)
373
-
374
- // Create secure connection
375
- return h.createSecureConnection(conn, clientEncryptKey, clientDecryptKey, serverInitPayload.GetIdentity().GetId())
376
-}
377
-
378
-// ServerHandshake performs the server-side of the handshake
379
-func (h *Handshaker) ServerHandshake(conn io.ReadWriteCloser, alpns []string) (*SecureConnection, error) {
380
- // Read client init message
381
- clientInitBytes, err := readLengthPrefixed(conn)
382
- if err != nil {
383
- return nil, ErrHandshakeFailed
384
- }
385
-
386
- clientInitSigned := &rdsec.SignedPayload{}
387
- if err := clientInitSigned.UnmarshalVT(clientInitBytes); err != nil {
388
- return nil, ErrHandshakeFailed
389
- }
390
-
391
- // Unmarshal the client init payload
392
- clientInitPayload := &rdsec.ClientInitPayload{}
393
- if err := clientInitPayload.UnmarshalVT(clientInitSigned.GetData()); err != nil {
394
- return nil, ErrHandshakeFailed
395
- }
396
-
397
- // Validate client init
398
- if err := h.validateClientInit(clientInitSigned, clientInitPayload, alpns); err != nil {
399
- // Silent failure: close connection and return error without sending response
400
- conn.Close()
401
- return nil, err
402
- }
403
-
404
- // Generate ephemeral key pair for this session
405
- ephemeralPriv, ephemeralPub, err := generateX25519KeyPair()
406
- if err != nil {
407
- return nil, ErrHandshakeFailed
408
- }
409
-
410
- // Create server init message
411
- timestamp := time.Now().Unix()
412
- nonce := make([]byte, nonceSize)
413
- if _, err := rand.Read(nonce); err != nil {
414
- return nil, ErrHandshakeFailed
415
- }
416
-
417
- serverInitPayload := &rdsec.ServerInitPayload{
418
- Version: rdsec.ProtocolVersion_PROTOCOL_VERSION_1,
419
- Nonce: nonce,
420
- Timestamp: timestamp,
421
- Identity: &rdsec.Identity{
422
- Id: h.credential.ID(),
423
- PublicKey: h.credential.PublicKey(),
424
- },
425
- Alpn: clientInitPayload.Alpn,
426
- SessionPublicKey: ephemeralPub,
427
- }
428
-
429
- // Serialize and sign the payload
430
- payloadBytes, err := serverInitPayload.MarshalVT()
431
- if err != nil {
432
- return nil, ErrHandshakeFailed
433
- }
434
-
435
- signature := h.credential.Sign(payloadBytes)
436
-
437
- serverInit := &rdsec.SignedPayload{
438
- Data: payloadBytes,
439
- Signature: signature,
440
- }
441
-
442
- // Derive session keys
443
- serverEncryptKey, serverDecryptKey, err := h.deriveServerSessionKeys(
444
- ephemeralPriv, clientInitPayload.GetSessionPublicKey(),
445
- clientInitPayload.GetNonce(), nonce,
446
- )
447
- if err != nil {
448
- return nil, err
449
- }
450
-
451
- wipeMemory(ephemeralPriv)
452
-
453
- // Send server init message
454
- serverInitBytes, err := serverInit.MarshalVT()
455
- if err != nil {
456
- return nil, ErrHandshakeFailed
457
- }
458
-
459
- // Write length-prefixed message
460
- if err := writeLengthPrefixed(conn, serverInitBytes); err != nil {
461
- return nil, ErrHandshakeFailed
462
- }
463
-
464
- // Create secure connection
465
- return h.createSecureConnection(conn, serverEncryptKey, serverDecryptKey, clientInitPayload.GetIdentity().GetId())
466
-}
467
-
468
-// validateClientInit validates the client init message
469
-func (h *Handshaker) validateClientInit(clientInitSigned *rdsec.SignedPayload, clientInitPayload *rdsec.ClientInitPayload, expectedAlpns []string) error {
470
- if clientInitSigned == nil || clientInitPayload == nil {
471
- return ErrInvalidProtocol
472
- }
473
-
474
- // Check protocol version
475
- if clientInitPayload.GetVersion() != rdsec.ProtocolVersion_PROTOCOL_VERSION_1 {
476
- return ErrInvalidProtocol
477
- }
478
-
479
- // Check timestamp
480
- if err := validateTimestamp(clientInitPayload.GetTimestamp()); err != nil {
481
- return err
482
- }
483
-
484
- // Check ALPN
485
- if !slices.Contains(expectedAlpns, clientInitPayload.GetAlpn()) {
486
- return ErrHandshakeFailed
487
- }
488
-
489
- // Validate identity
490
- if !ValidateIdentity(clientInitPayload.GetIdentity()) {
491
- return ErrInvalidIdentity
492
- }
493
-
494
- // Verify signature
495
- if !ed25519.Verify(clientInitPayload.GetIdentity().GetPublicKey(), clientInitSigned.GetData(), clientInitSigned.GetSignature()) {
496
- return ErrInvalidSignature
497
- }
498
-
499
- return nil
500
-}
501
-
502
-// validateServerInit validates the server init message
503
-func (h *Handshaker) validateServerInit(serverInitSigned *rdsec.SignedPayload, serverInitPayload *rdsec.ServerInitPayload) error {
504
- if serverInitSigned == nil || serverInitPayload == nil {
505
- return ErrInvalidProtocol
506
- }
507
-
508
- // Check protocol version
509
- if serverInitPayload.GetVersion() != rdsec.ProtocolVersion_PROTOCOL_VERSION_1 {
510
- return ErrInvalidProtocol
511
- }
512
-
513
- // Check timestamp
514
- if err := validateTimestamp(serverInitPayload.GetTimestamp()); err != nil {
515
- return err
516
- }
517
-
518
- // Validate identity
519
- if !ValidateIdentity(serverInitPayload.GetIdentity()) {
520
- return ErrInvalidIdentity
521
- }
522
-
523
- // Verify signature
524
- if !ed25519.Verify(serverInitPayload.GetIdentity().GetPublicKey(), serverInitSigned.GetData(), serverInitSigned.GetSignature()) {
525
- return ErrInvalidSignature
526
- }
527
-
528
- return nil
529
-}
530
-
531
-// deriveClientSessionKeys derives encryption and decryption keys for the client
532
-func (h *Handshaker) deriveClientSessionKeys(clientPriv, serverPub, clientNonce, serverNonce []byte) ([]byte, []byte, error) {
533
- // Compute shared secret
534
- sharedSecret, err := curve25519.X25519(clientPriv, serverPub)
535
- if err != nil {
536
- return nil, nil, ErrSessionKeyDerive
537
- }
538
-
539
- // Derive keys using HKDF-like construction
540
- // Both client and server use the same derivation for the same direction
541
- // Client encrypts, server decrypts
542
- salt := append(clientNonce, serverNonce...)
543
- encryptKey := deriveKey(sharedSecret, salt, []byte(clientKeyInfo))
544
- // Server encrypts, client decrypts
545
- salt = append(serverNonce, clientNonce...)
546
- decryptKey := deriveKey(sharedSecret, salt, []byte(serverKeyInfo))
547
-
548
- return encryptKey, decryptKey, nil
549
-}
550
-
551
-// deriveServerSessionKeys derives encryption and decryption keys for the server
552
-func (h *Handshaker) deriveServerSessionKeys(serverPriv, clientPub, clientNonce, serverNonce []byte) ([]byte, []byte, error) {
553
- // Compute shared secret (should be same as client's)
554
- sharedSecret, err := curve25519.X25519(serverPriv, clientPub)
555
- if err != nil {
556
- return nil, nil, ErrSessionKeyDerive
557
- }
558
-
559
- // Derive keys using HKDF-like construction
560
- // Both client and server use the same derivation for the same direction
561
- // Server encrypts, client decrypts
562
- salt := append(serverNonce, clientNonce...)
563
- encryptKey := deriveKey(sharedSecret, salt, []byte(serverKeyInfo))
564
- // Client encrypts, server decrypts
565
- salt = append(clientNonce, serverNonce...)
566
- decryptKey := deriveKey(sharedSecret, salt, []byte(clientKeyInfo))
567
-
568
- return encryptKey, decryptKey, nil
569
-}
570
-
571
-// createSecureConnection creates a new SecureConnection with the given keys and nonces
572
-func (h *Handshaker) createSecureConnection(conn io.ReadWriteCloser, encryptKey, decryptKey []byte, remoteID string) (*SecureConnection, error) {
573
- // Create AEAD instances
574
- encryptor, err := chacha20poly1305.New(encryptKey)
575
- if err != nil {
576
- return nil, ErrEncryptionFailed
577
- }
578
-
579
- decryptor, err := chacha20poly1305.New(decryptKey)
580
- if err != nil {
581
- return nil, ErrEncryptionFailed
582
- }
583
-
584
- readBuffer := acquireBuffer(1 << 12)
585
- readBuffer.B = readBuffer.B[:0]
586
-
587
- secureConn := &SecureConnection{
588
- conn: conn,
589
- localID: h.credential.id,
590
- remoteID: remoteID,
591
- encryptor: encryptor,
592
- decryptor: decryptor,
593
- readBuffer: readBuffer,
594
- }
595
-
596
- return secureConn, nil
597
-}
598
-
599
-// Helper functions
600
-
601
-// generateX25519KeyPair generates a new X25519 key pair
602
-func generateX25519KeyPair() ([]byte, []byte, error) {
603
- priv := make([]byte, curve25519.ScalarSize)
604
- if _, err := rand.Read(priv); err != nil {
605
- return nil, nil, err
606
- }
607
-
608
- pub, err := curve25519.X25519(priv, curve25519.Basepoint)
609
- if err != nil {
610
- return nil, nil, err
611
- }
612
-
613
- return priv, pub, nil
614
-}
615
-
616
-// deriveKey derives a key from the shared secret using HKDF-SHA256
617
-func deriveKey(sharedSecret, salt, info []byte) []byte {
618
- hkdf := hkdf.New(sha256.New, sharedSecret, salt, info)
619
- key := make([]byte, sessionKeySize)
620
- if _, err := hkdf.Read(key); err != nil {
621
- // HKDF should never fail with valid inputs, treat as critical error
622
- panic(fmt.Sprintf("HKDF key derivation failed: %v", err))
623
- }
624
- return key
625
-}
626
-
627
-// validateTimestamp validates that the timestamp is within acceptable range
628
-func validateTimestamp(timestamp int64) error {
629
- now := time.Now().Unix()
630
- diff := now - timestamp
631
-
632
- if diff < -int64(maxTimestampSkew.Seconds()) || diff > int64(maxTimestampSkew.Seconds()) {
633
- return ErrInvalidTimestamp
634
- }
635
-
636
- return nil
637
-}
638
-
639
-// writeLengthPrefixed writes a length-prefixed message to the connection
640
-func writeLengthPrefixed(conn io.Writer, data []byte) error {
641
- length := len(data)
642
- lengthBytes := []byte{
643
- byte(length >> 24),
644
- byte(length >> 16),
645
- byte(length >> 8),
646
- byte(length),
647
- }
648
-
649
- if _, err := conn.Write(lengthBytes); err != nil {
650
- return err
651
- }
652
-
653
- _, err := conn.Write(data)
654
- return err
655
-}
656
-
657
-// readLengthPrefixed reads a length-prefixed message from the connection
658
-func readLengthPrefixed(conn io.Reader) ([]byte, error) {
659
- lengthBytes := make([]byte, 4)
660
- if _, err := io.ReadFull(conn, lengthBytes); err != nil {
661
- return nil, err
662
- }
663
-
664
- length := int(lengthBytes[0])<<24 | int(lengthBytes[1])<<16 | int(lengthBytes[2])<<8 | int(lengthBytes[3])
665
-
666
- // Check packet size limit
667
- if length > maxRawPacketSize {
668
- return nil, ErrHandshakeFailed
669
- }
670
-
671
- data := make([]byte, length)
672
- if _, err := io.ReadFull(conn, data); err != nil {
673
- return nil, err
674
- }
675
-
676
- return data, nil
677
-}
portal/core/cryptoops/handshaker_test.go
deleted
-877
@@ -1,877 +0,0 @@
1
-package cryptoops
2
-
3
-import (
4
- "bytes"
5
- "crypto/rand"
6
- "io"
7
- "net"
8
- "sync"
9
- "testing"
10
- "time"
11
-
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
17
-func pipeConn() (net.Conn, net.Conn) {
18
- listener, err := net.Listen("tcp", "127.0.0.1:0")
19
- if err != nil {
20
- panic(err)
21
- }
22
-
23
- connCh := make(chan net.Conn, 1)
24
- go func() {
25
- conn, err := listener.Accept()
26
- if err != nil {
27
- panic(err)
28
- }
29
- connCh <- conn
30
- listener.Close()
31
- }()
32
-
33
- clientConn, err := net.Dial("tcp", listener.Addr().String())
34
- if err != nil {
35
- panic(err)
36
- }
37
-
38
- serverConn := <-connCh
39
- return clientConn, serverConn
40
-}
41
-
42
-// TestNewHandshaker tests handshaker creation
43
-func TestNewHandshaker(t *testing.T) {
44
- cred, err := NewCredential()
45
- if err != nil {
46
- t.Fatalf("Failed to create credential: %v", err)
47
- }
48
-
49
- h := NewHandshaker(cred)
50
- if h == nil {
51
- t.Fatal("NewHandshaker returned nil")
52
- }
53
- if h.credential != cred {
54
- t.Error("Handshaker credential mismatch")
55
- }
56
-}
57
-
58
-// TestHandshakeSuccess tests a successful handshake
59
-func TestHandshakeSuccess(t *testing.T) {
60
- clientCred, err := NewCredential()
61
- if err != nil {
62
- t.Fatalf("Failed to create client credential: %v", err)
63
- }
64
-
65
- serverCred, err := NewCredential()
66
- if err != nil {
67
- t.Fatalf("Failed to create server credential: %v", err)
68
- }
69
-
70
- clientConn, serverConn := pipeConn()
71
-
72
- clientHandshaker := NewHandshaker(clientCred)
73
- serverHandshaker := NewHandshaker(serverCred)
74
-
75
- // Run handshakes concurrently
76
- var clientSecure, serverSecure *SecureConnection
77
- var clientErr, serverErr error
78
- var wg sync.WaitGroup
79
- wg.Add(2)
80
-
81
- go func() {
82
- defer wg.Done()
83
- clientSecure, clientErr = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
84
- }()
85
-
86
- go func() {
87
- defer wg.Done()
88
- serverSecure, serverErr = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
89
- }()
90
-
91
- wg.Wait()
92
-
93
- if clientErr != nil {
94
- t.Fatalf("Client handshake failed: %v", clientErr)
95
- }
96
- if serverErr != nil {
97
- t.Fatalf("Server handshake failed: %v", serverErr)
98
- }
99
- if clientSecure == nil || serverSecure == nil {
100
- t.Fatal("Secure connections are nil")
101
- }
102
-
103
- // Test that connections can communicate
104
- testMessage := []byte("Hello, secure world!")
105
-
106
- // Client sends to server
107
- _, err = clientSecure.Write(testMessage)
108
- if err != nil {
109
- t.Fatalf("Client write failed: %v", err)
110
- }
111
-
112
- // Server receives
113
- received := make([]byte, len(testMessage))
114
- n, err := io.ReadFull(serverSecure, received)
115
- if err != nil {
116
- t.Fatalf("Server read failed: %v", err)
117
- }
118
- if n != len(testMessage) {
119
- t.Fatalf("Expected to read %d bytes, got %d", len(testMessage), n)
120
- }
121
- if !bytes.Equal(testMessage, received) {
122
- t.Errorf("Message mismatch: expected %q, got %q", testMessage, received)
123
- }
124
-
125
- // Server sends to client
126
- responseMessage := []byte("Hello back!")
127
- _, err = serverSecure.Write(responseMessage)
128
- if err != nil {
129
- t.Fatalf("Server write failed: %v", err)
130
- }
131
-
132
- // Client receives
133
- received = make([]byte, len(responseMessage))
134
- n, err = io.ReadFull(clientSecure, received)
135
- if err != nil {
136
- t.Fatalf("Client read failed: %v", err)
137
- }
138
- if n != len(responseMessage) {
139
- t.Fatalf("Expected to read %d bytes, got %d", len(responseMessage), n)
140
- }
141
- if !bytes.Equal(responseMessage, received) {
142
- t.Errorf("Message mismatch: expected %q, got %q", responseMessage, received)
143
- }
144
-
145
- clientSecure.Close()
146
- serverSecure.Close()
147
-}
148
-
149
-// TestHandshakeInvalidSignature tests handshake validation
150
-func TestHandshakeInvalidSignature(t *testing.T) {
151
- // This is tested implicitly through validateClientInit/validateServerInit
152
- // Detailed unit tests would require mocking which is complex
153
- t.Skip("Covered by integration tests")
154
-}
155
-
156
-// TestHandshakeInvalidTimestamp tests timestamp validation
157
-func TestHandshakeInvalidTimestamp(t *testing.T) {
158
- // Test the validateTimestamp function directly
159
- now := time.Now().Unix()
160
-
161
- // Valid timestamp
162
- if err := validateTimestamp(now); err != nil {
163
- t.Errorf("Current timestamp should be valid: %v", err)
164
- }
165
-
166
- // Old timestamp (> 30s)
167
- if err := validateTimestamp(now - 100); err == nil {
168
- t.Error("Old timestamp should be invalid")
169
- }
170
-
171
- // Future timestamp (> 30s)
172
- if err := validateTimestamp(now + 100); err == nil {
173
- t.Error("Future timestamp should be invalid")
174
- }
175
-}
176
-
177
-// TestHandshakeInvalidProtocolVersion tests protocol version
178
-func TestHandshakeInvalidProtocolVersion(t *testing.T) {
179
- t.Skip("Covered by integration tests")
180
-}
181
-
182
-// TestHandshakeInvalidALPN tests ALPN validation
183
-func TestHandshakeInvalidALPN(t *testing.T) {
184
- t.Skip("Covered by integration tests")
185
-}
186
-
187
-// TestEncryptionRoundTrip tests encryption and decryption
188
-func TestEncryptionRoundTrip(t *testing.T) {
189
- clientCred, _ := NewCredential()
190
- serverCred, _ := NewCredential()
191
-
192
- clientConn, serverConn := pipeConn()
193
-
194
- clientHandshaker := NewHandshaker(clientCred)
195
- serverHandshaker := NewHandshaker(serverCred)
196
-
197
- var clientSecure, serverSecure *SecureConnection
198
- var wg sync.WaitGroup
199
- wg.Add(2)
200
-
201
- go func() {
202
- defer wg.Done()
203
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
204
- }()
205
-
206
- go func() {
207
- defer wg.Done()
208
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
209
- }()
210
-
211
- wg.Wait()
212
-
213
- testCases := []struct {
214
- name string
215
- message []byte
216
- }{
217
- {"Empty", []byte{}},
218
- {"Small", []byte("Hello")},
219
- {"Medium", bytes.Repeat([]byte("A"), 1024)},
220
- {"Large", bytes.Repeat([]byte("B"), 10000)},
221
- {"Binary", []byte{0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD}},
222
- }
223
-
224
- for _, tc := range testCases {
225
- t.Run(tc.name, func(t *testing.T) {
226
- // Client to Server
227
- _, err := clientSecure.Write(tc.message)
228
- if err != nil {
229
- t.Fatalf("Write failed: %v", err)
230
- }
231
-
232
- received := make([]byte, len(tc.message))
233
- if len(tc.message) > 0 {
234
- _, err = io.ReadFull(serverSecure, received)
235
- if err != nil {
236
- t.Fatalf("Read failed: %v", err)
237
- }
238
- if !bytes.Equal(tc.message, received) {
239
- t.Error("Message mismatch")
240
- }
241
- }
242
-
243
- // Server to Client
244
- _, err = serverSecure.Write(tc.message)
245
- if err != nil {
246
- t.Fatalf("Write failed: %v", err)
247
- }
248
-
249
- received = make([]byte, len(tc.message))
250
- if len(tc.message) > 0 {
251
- _, err = io.ReadFull(clientSecure, received)
252
- if err != nil {
253
- t.Fatalf("Read failed: %v", err)
254
- }
255
- if !bytes.Equal(tc.message, received) {
256
- t.Error("Message mismatch")
257
- }
258
- }
259
- })
260
- }
261
-
262
- clientSecure.Close()
263
- serverSecure.Close()
264
-}
265
-
266
-// TestFragmentation tests large message fragmentation
267
-func TestFragmentation(t *testing.T) {
268
- clientCred, _ := NewCredential()
269
- serverCred, _ := NewCredential()
270
-
271
- clientConn, serverConn := pipeConn()
272
-
273
- clientHandshaker := NewHandshaker(clientCred)
274
- serverHandshaker := NewHandshaker(serverCred)
275
-
276
- var clientSecure, serverSecure *SecureConnection
277
- var wg sync.WaitGroup
278
- wg.Add(2)
279
-
280
- go func() {
281
- defer wg.Done()
282
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
283
- }()
284
-
285
- go func() {
286
- defer wg.Done()
287
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
288
- }()
289
-
290
- wg.Wait()
291
-
292
- // Test message larger than fragment size (32MB)
293
- largeMessage := bytes.Repeat([]byte("X"), 40*1024*1024) // 40MB
294
-
295
- go func() {
296
- _, err := clientSecure.Write(largeMessage)
297
- if err != nil {
298
- t.Errorf("Write large message failed: %v", err)
299
- }
300
- }()
301
-
302
- // Read in chunks
303
- received := make([]byte, len(largeMessage))
304
- totalRead := 0
305
- for totalRead < len(largeMessage) {
306
- n, err := serverSecure.Read(received[totalRead:])
307
- if err != nil {
308
- t.Fatalf("Read failed at %d bytes: %v", totalRead, err)
309
- }
310
- totalRead += n
311
- }
312
-
313
- if !bytes.Equal(largeMessage, received) {
314
- t.Error("Large message mismatch after fragmentation")
315
- }
316
-
317
- clientSecure.Close()
318
- serverSecure.Close()
319
-}
320
-
321
-// TestConcurrentWrites tests concurrent writes
322
-func TestConcurrentWrites(t *testing.T) {
323
- clientCred, _ := NewCredential()
324
- serverCred, _ := NewCredential()
325
-
326
- clientConn, serverConn := pipeConn()
327
-
328
- clientHandshaker := NewHandshaker(clientCred)
329
- serverHandshaker := NewHandshaker(serverCred)
330
-
331
- var clientSecure, serverSecure *SecureConnection
332
- var wg sync.WaitGroup
333
- wg.Add(2)
334
-
335
- go func() {
336
- defer wg.Done()
337
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
338
- }()
339
-
340
- go func() {
341
- defer wg.Done()
342
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
343
- }()
344
-
345
- wg.Wait()
346
-
347
- const numMessages = 100
348
- messages := make([][]byte, numMessages)
349
- for i := range numMessages {
350
- messages[i] = []byte{byte(i), byte(i >> 8)}
351
- }
352
-
353
- // Write concurrently from client
354
- var writeWg sync.WaitGroup
355
- for i := range numMessages {
356
- writeWg.Add(1)
357
- go func(msg []byte) {
358
- defer writeWg.Done()
359
- clientSecure.Write(msg)
360
- }(messages[i])
361
- }
362
- writeWg.Wait()
363
-
364
- // Read all messages
365
- received := make(map[string]bool)
366
- for range numMessages {
367
- buf := make([]byte, 2)
368
- _, err := io.ReadFull(serverSecure, buf)
369
- if err != nil {
370
- t.Fatalf("Read failed: %v", err)
371
- }
372
- key := string(buf)
373
- if received[key] {
374
- t.Errorf("Duplicate message received: %v", buf)
375
- }
376
- received[key] = true
377
- }
378
-
379
- if len(received) != numMessages {
380
- t.Errorf("Expected %d unique messages, got %d", numMessages, len(received))
381
- }
382
-
383
- clientSecure.Close()
384
- serverSecure.Close()
385
-}
386
-
387
-// TestInvalidIdentity tests identity validation
388
-func TestInvalidIdentity(t *testing.T) {
389
- cred, _ := NewCredential()
390
-
391
- // Valid identity
392
- validIdentity := &rdsec.Identity{
393
- Id: cred.ID(),
394
- PublicKey: cred.PublicKey(),
395
- }
396
- if !ValidateIdentity(validIdentity) {
397
- t.Error("Valid identity should pass validation")
398
- }
399
-
400
- // Wrong ID
401
- invalidIdentity := &rdsec.Identity{
402
- Id: "WRONG_ID",
403
- PublicKey: cred.PublicKey(),
404
- }
405
- if ValidateIdentity(invalidIdentity) {
406
- t.Error("Identity with wrong ID should fail validation")
407
- }
408
-
409
- // Wrong key size
410
- invalidIdentity2 := &rdsec.Identity{
411
- Id: cred.ID(),
412
- PublicKey: []byte{1, 2, 3},
413
- }
414
- if ValidateIdentity(invalidIdentity2) {
415
- t.Error("Identity with wrong key size should fail validation")
416
- }
417
-}
418
-
419
-// TestGenerateX25519KeyPair tests X25519 key pair generation
420
-func TestGenerateX25519KeyPair(t *testing.T) {
421
- priv1, pub1, err := generateX25519KeyPair()
422
- if err != nil {
423
- t.Fatalf("Failed to generate key pair: %v", err)
424
- }
425
-
426
- if len(priv1) != curve25519.ScalarSize {
427
- t.Errorf("Expected private key size %d, got %d", curve25519.ScalarSize, len(priv1))
428
- }
429
- if len(pub1) != curve25519.PointSize {
430
- t.Errorf("Expected public key size %d, got %d", curve25519.PointSize, len(pub1))
431
- }
432
-
433
- // Generate another pair and ensure they're different
434
- priv2, pub2, err := generateX25519KeyPair()
435
- if err != nil {
436
- t.Fatalf("Failed to generate second key pair: %v", err)
437
- }
438
-
439
- if bytes.Equal(priv1, priv2) {
440
- t.Error("Generated same private key twice")
441
- }
442
- if bytes.Equal(pub1, pub2) {
443
- t.Error("Generated same public key twice")
444
- }
445
-
446
- // Verify public key derivation
447
- derivedPub, err := curve25519.X25519(priv1, curve25519.Basepoint)
448
- if err != nil {
449
- t.Fatalf("Failed to derive public key: %v", err)
450
- }
451
- if !bytes.Equal(pub1, derivedPub) {
452
- t.Error("Public key doesn't match derived key")
453
- }
454
-}
455
-
456
-// TestDeriveKey tests HKDF key derivation
457
-func TestDeriveKey(t *testing.T) {
458
- sharedSecret := make([]byte, 32)
459
- rand.Read(sharedSecret)
460
-
461
- salt1 := []byte("salt1")
462
- salt2 := []byte("salt2")
463
- info1 := []byte(clientKeyInfo)
464
- info2 := []byte(serverKeyInfo)
465
-
466
- key1 := deriveKey(sharedSecret, salt1, info1)
467
- key2 := deriveKey(sharedSecret, salt2, info1)
468
- key3 := deriveKey(sharedSecret, salt1, info2)
469
- key4 := deriveKey(sharedSecret, salt1, info1) // Same as key1
470
-
471
- // Check key size
472
- if len(key1) != sessionKeySize {
473
- t.Errorf("Expected key size %d, got %d", sessionKeySize, len(key1))
474
- }
475
-
476
- // Keys with different salts should be different
477
- if bytes.Equal(key1, key2) {
478
- t.Error("Keys with different salts are the same")
479
- }
480
-
481
- // Keys with different info should be different
482
- if bytes.Equal(key1, key3) {
483
- t.Error("Keys with different info are the same")
484
- }
485
-
486
- // Same inputs should produce same key
487
- if !bytes.Equal(key1, key4) {
488
- t.Error("Same inputs produced different keys")
489
- }
490
-}
491
-
492
-// TestValidateTimestamp tests timestamp validation
493
-func TestValidateTimestamp(t *testing.T) {
494
- now := time.Now().Unix()
495
-
496
- testCases := []struct {
497
- name string
498
- timestamp int64
499
- expectErr bool
500
- }{
501
- {"Current", now, false},
502
- {"5 seconds ago", now - 5, false},
503
- {"5 seconds future", now + 5, false},
504
- {"30 seconds ago", now - 30, false},
505
- {"30 seconds future", now + 30, false},
506
- {"31 seconds ago", now - 31, true},
507
- {"31 seconds future", now + 31, true},
508
- {"100 seconds ago", now - 100, true},
509
- {"100 seconds future", now + 100, true},
510
- }
511
-
512
- for _, tc := range testCases {
513
- t.Run(tc.name, func(t *testing.T) {
514
- err := validateTimestamp(tc.timestamp)
515
- if tc.expectErr && err == nil {
516
- t.Error("Expected error but got none")
517
- }
518
- if !tc.expectErr && err != nil {
519
- t.Errorf("Unexpected error: %v", err)
520
- }
521
- })
522
- }
523
-}
524
-
525
-// TestLengthPrefixedReadWrite tests length-prefixed message encoding
526
-func TestLengthPrefixedReadWrite(t *testing.T) {
527
- testMessages := [][]byte{
528
- {},
529
- {0x01},
530
- []byte("Hello, World!"),
531
- bytes.Repeat([]byte("A"), 1000),
532
- make([]byte, 0),
533
- }
534
-
535
- for i, msg := range testMessages {
536
- t.Run(string(rune('A'+i)), func(t *testing.T) {
537
- var buf bytes.Buffer
538
-
539
- // Write
540
- err := writeLengthPrefixed(&buf, msg)
541
- if err != nil {
542
- t.Fatalf("Write failed: %v", err)
543
- }
544
-
545
- // Read
546
- received, err := readLengthPrefixed(&buf)
547
- if err != nil {
548
- t.Fatalf("Read failed: %v", err)
549
- }
550
-
551
- if !bytes.Equal(msg, received) {
552
- t.Errorf("Message mismatch: expected %v, got %v", msg, received)
553
- }
554
- })
555
- }
556
-}
557
-
558
-// TestReadLengthPrefixedTooLarge tests reading message exceeding size limit
559
-func TestReadLengthPrefixedTooLarge(t *testing.T) {
560
- var buf bytes.Buffer
561
-
562
- // Write length exceeding maxRawPacketSize
563
- tooLarge := uint32(maxRawPacketSize + 1)
564
- lengthBytes := []byte{
565
- byte(tooLarge >> 24),
566
- byte(tooLarge >> 16),
567
- byte(tooLarge >> 8),
568
- byte(tooLarge),
569
- }
570
- buf.Write(lengthBytes)
571
-
572
- _, err := readLengthPrefixed(&buf)
573
- if err != ErrHandshakeFailed {
574
- t.Errorf("Expected ErrHandshakeFailed for oversized message, got %v", err)
575
- }
576
-}
577
-
578
-// TestWipeMemory tests memory wiping functionality
579
-func TestWipeMemory(t *testing.T) {
580
- data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
581
- originalCap := cap(data)
582
-
583
- wipeMemory(data)
584
-
585
- // Check that all bytes in the capacity are zeroed
586
- fullData := data[:originalCap]
587
- for i, b := range fullData {
588
- if b != 0 {
589
- t.Errorf("Byte at index %d not wiped: %02x", i, b)
590
- }
591
- }
592
-}
593
-
594
-// TestBufferManagement tests buffer acquisition and release
595
-func TestBufferManagement(t *testing.T) {
596
- // Acquire buffer
597
- buf := acquireBuffer(1024)
598
- if buf == nil {
599
- t.Fatal("acquireBuffer returned nil")
600
- }
601
- if cap(buf.B) < 1024 {
602
- t.Errorf("Expected capacity >= 1024, got %d", cap(buf.B))
603
- }
604
-
605
- // Write some data
606
- testData := []byte("sensitive data")
607
- buf.B = append(buf.B, testData...)
608
-
609
- // Release and verify wiping
610
- releaseBuffer(buf)
611
-
612
- // Acquire again and verify it's clean
613
- buf2 := acquireBuffer(1024)
614
- for i := 0; i < len(testData) && i < len(buf2.B); i++ {
615
- if buf2.B[i] != 0 {
616
- t.Errorf("Buffer not properly wiped at index %d", i)
617
- }
618
- }
619
- releaseBuffer(buf2)
620
-}
621
-
622
-// TestSecureConnectionPartialRead tests reading when buffer is smaller than message
623
-func TestSecureConnectionPartialRead(t *testing.T) {
624
- clientCred, _ := NewCredential()
625
- serverCred, _ := NewCredential()
626
-
627
- clientConn, serverConn := pipeConn()
628
-
629
- clientHandshaker := NewHandshaker(clientCred)
630
- serverHandshaker := NewHandshaker(serverCred)
631
-
632
- var clientSecure, serverSecure *SecureConnection
633
- var wg sync.WaitGroup
634
- wg.Add(2)
635
-
636
- go func() {
637
- defer wg.Done()
638
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
639
- }()
640
-
641
- go func() {
642
- defer wg.Done()
643
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
644
- }()
645
-
646
- wg.Wait()
647
-
648
- message := []byte("This is a longer message that will be read in parts")
649
-
650
- // Send message
651
- _, err := clientSecure.Write(message)
652
- if err != nil {
653
- t.Fatalf("Write failed: %v", err)
654
- }
655
-
656
- // Read in small chunks
657
- received := make([]byte, 0, len(message))
658
- smallBuf := make([]byte, 10) // Smaller than message
659
-
660
- for len(received) < len(message) {
661
- n, err := serverSecure.Read(smallBuf)
662
- if err != nil {
663
- t.Fatalf("Read failed: %v", err)
664
- }
665
- received = append(received, smallBuf[:n]...)
666
- }
667
-
668
- if !bytes.Equal(message, received) {
669
- t.Error("Message mismatch with partial reads")
670
- }
671
-
672
- clientSecure.Close()
673
- serverSecure.Close()
674
-}
675
-
676
-// TestRealNetworkConnection tests with actual TCP connection
677
-func TestRealNetworkConnection(t *testing.T) {
678
- if testing.Short() {
679
- t.Skip("Skipping network test in short mode")
680
- }
681
-
682
- clientCred, _ := NewCredential()
683
- serverCred, _ := NewCredential()
684
-
685
- // Start server
686
- listener, err := net.Listen("tcp", "127.0.0.1:0")
687
- if err != nil {
688
- t.Fatalf("Failed to start listener: %v", err)
689
- }
690
- defer listener.Close()
691
-
692
- serverAddr := listener.Addr().String()
693
-
694
- var serverSecure *SecureConnection
695
- var serverErr error
696
- serverDone := make(chan struct{})
697
-
698
- go func() {
699
- defer close(serverDone)
700
- conn, err := listener.Accept()
701
- if err != nil {
702
- serverErr = err
703
- return
704
- }
705
-
706
- serverHandshaker := NewHandshaker(serverCred)
707
- serverSecure, serverErr = serverHandshaker.ServerHandshake(conn, []string{"test-alpn"})
708
- }()
709
-
710
- // Connect client
711
- clientConn, err := net.Dial("tcp", serverAddr)
712
- if err != nil {
713
- t.Fatalf("Failed to connect: %v", err)
714
- }
715
-
716
- clientHandshaker := NewHandshaker(clientCred)
717
- clientSecure, clientErr := clientHandshaker.ClientHandshake(clientConn, "test-alpn")
718
-
719
- <-serverDone
720
-
721
- if clientErr != nil {
722
- t.Fatalf("Client handshake failed: %v", clientErr)
723
- }
724
- if serverErr != nil {
725
- t.Fatalf("Server handshake failed: %v", serverErr)
726
- }
727
-
728
- // Test communication
729
- testMessage := []byte("Hello over real network!")
730
-
731
- _, err = clientSecure.Write(testMessage)
732
- if err != nil {
733
- t.Fatalf("Write failed: %v", err)
734
- }
735
-
736
- received := make([]byte, len(testMessage))
737
- _, err = io.ReadFull(serverSecure, received)
738
- if err != nil {
739
- t.Fatalf("Read failed: %v", err)
740
- }
741
-
742
- if !bytes.Equal(testMessage, received) {
743
- t.Error("Message mismatch over network")
744
- }
745
-
746
- clientSecure.Close()
747
- serverSecure.Close()
748
-}
749
-
750
-// BenchmarkHandshake benchmarks the handshake process
751
-func BenchmarkHandshake(b *testing.B) {
752
- clientCred, _ := NewCredential()
753
- serverCred, _ := NewCredential()
754
-
755
- b.ResetTimer()
756
- for range b.N {
757
- clientConn, serverConn := pipeConn()
758
-
759
- clientHandshaker := NewHandshaker(clientCred)
760
- serverHandshaker := NewHandshaker(serverCred)
761
-
762
- var wg sync.WaitGroup
763
- wg.Add(2)
764
-
765
- go func() {
766
- defer wg.Done()
767
- clientHandshaker.ClientHandshake(clientConn, "test-alpn")
768
- }()
769
-
770
- go func() {
771
- defer wg.Done()
772
- serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
773
- }()
774
-
775
- wg.Wait()
776
- }
777
-}
778
-
779
-// BenchmarkEncryption benchmarks encryption throughput
780
-func BenchmarkEncryption(b *testing.B) {
781
- clientCred, _ := NewCredential()
782
- serverCred, _ := NewCredential()
783
-
784
- clientConn, serverConn := pipeConn()
785
-
786
- clientHandshaker := NewHandshaker(clientCred)
787
- serverHandshaker := NewHandshaker(serverCred)
788
-
789
- var clientSecure, serverSecure *SecureConnection
790
- var wg sync.WaitGroup
791
- wg.Add(2)
792
-
793
- go func() {
794
- defer wg.Done()
795
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
796
- }()
797
-
798
- go func() {
799
- defer wg.Done()
800
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
801
- }()
802
-
803
- wg.Wait()
804
-
805
- message := bytes.Repeat([]byte("A"), 1024) // 1KB message
806
-
807
- go func() {
808
- buf := make([]byte, 1024)
809
- for {
810
- serverSecure.Read(buf)
811
- }
812
- }()
813
-
814
- b.ResetTimer()
815
- b.SetBytes(int64(len(message)))
816
-
817
- for range b.N {
818
- clientSecure.Write(message)
819
- }
820
-
821
- clientSecure.Close()
822
- serverSecure.Close()
823
-}
824
-
825
-// TestConcurrentReadClose tests that closing the connection while reading is safe
826
-func TestConcurrentReadClose(t *testing.T) {
827
- clientCred, _ := NewCredential()
828
- serverCred, _ := NewCredential()
829
-
830
- clientConn, serverConn := pipeConn()
831
-
832
- clientHandshaker := NewHandshaker(clientCred)
833
- serverHandshaker := NewHandshaker(serverCred)
834
-
835
- var clientSecure, serverSecure *SecureConnection
836
- var wg sync.WaitGroup
837
- wg.Add(2)
838
-
839
- go func() {
840
- defer wg.Done()
841
- clientSecure, _ = clientHandshaker.ClientHandshake(clientConn, "test-alpn")
842
- }()
843
-
844
- go func() {
845
- defer wg.Done()
846
- serverSecure, _ = serverHandshaker.ServerHandshake(serverConn, []string{"test-alpn"})
847
- }()
848
-
849
- wg.Wait()
850
-
851
- // Start a goroutine that reads continuously
852
- readErrCh := make(chan error, 1)
853
- go func() {
854
- buf := make([]byte, 1024)
855
- _, err := clientSecure.Read(buf)
856
- readErrCh <- err
857
- }()
858
-
859
- // Give the reader a moment to start and block
860
- time.Sleep(10 * time.Millisecond)
861
-
862
- // Close the connection
863
- clientSecure.Close()
864
-
865
- // Check the read error
866
- select {
867
- case err := <-readErrCh:
868
- if err == nil {
869
- t.Error("Expected error from Read after Close, got nil")
870
- }
871
- // We expect either net.ErrClosed or an IO error depending on timing
872
- case <-time.After(1 * time.Second):
873
- t.Error("Read did not return after Close")
874
- }
875
-
876
- serverSecure.Close()
877
-}
portal/core/cryptoops/identity.go
deleted
-41
@@ -1,41 +0,0 @@
1
-package cryptoops
2
-
3
-import (
4
- "crypto/ed25519"
5
- "errors"
6
-
7
- "gosuda.org/portal/portal/core/proto/rdsec"
8
-)
9
-
10
-func ValidateIdentity(identity *rdsec.Identity) bool {
11
- if identity == nil {
12
- return false
13
- }
14
-
15
- if len(identity.PublicKey) != ed25519.PublicKeySize {
16
- return false
17
- }
18
-
19
- id := DeriveID(identity.PublicKey)
20
-
21
- return id == identity.Id
22
-}
23
-
24
-var (
25
- ErrInvalidMessage = errors.New("invalid message")
26
-)
27
-
28
-func VerifySignedPayload(message *rdsec.SignedPayload, id *rdsec.Identity) bool {
29
- if message == nil {
30
- return false
31
- }
32
- if id == nil {
33
- return false
34
- }
35
-
36
- if !ValidateIdentity(id) {
37
- return false
38
- }
39
-
40
- return ed25519.Verify(id.PublicKey, message.Data, message.Signature)
41
-}
portal/core/cryptoops/sig.go
deleted
-71
@@ -1,71 +0,0 @@
1
-package cryptoops
2
-
3
-import (
4
- "crypto/ed25519"
5
- "crypto/hmac"
6
- "crypto/rand"
7
- "crypto/sha256"
8
- "encoding/base32"
9
- "errors"
10
-)
11
-
12
-var _id_magic = []byte("RDVERB_PROTOCOL_VER_01_SHA256_ID")
13
-var _base32_encoding = base32.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567").WithPadding(base32.NoPadding)
14
-
15
-func DeriveID(publickey ed25519.PublicKey) string {
16
- h := hmac.New(sha256.New, _id_magic)
17
- h.Write(publickey)
18
- hash := h.Sum(nil)
19
- return _base32_encoding.EncodeToString(hash[:16])
20
-}
21
-
22
-type Credential struct {
23
- privateKey ed25519.PrivateKey
24
- publicKey ed25519.PublicKey
25
- id string
26
-}
27
-
28
-func NewCredentialFromPrivateKey(privateKey ed25519.PrivateKey) (*Credential, error) {
29
- if len(privateKey) != ed25519.PrivateKeySize {
30
- return nil, errors.New("invalid private key length")
31
- }
32
-
33
- publicKey := privateKey.Public().(ed25519.PublicKey)
34
- return &Credential{
35
- privateKey: privateKey,
36
- publicKey: publicKey,
37
- id: DeriveID(publicKey),
38
- }, nil
39
-}
40
-
41
-func NewCredential() (*Credential, error) {
42
- _, privateKey, err := ed25519.GenerateKey(rand.Reader)
43
- if err != nil {
44
- return nil, err
45
- }
46
-
47
- return NewCredentialFromPrivateKey(privateKey)
48
-}
49
-
50
-func (c *Credential) ID() string {
51
- return c.id
52
-}
53
-
54
-func (c *Credential) Sign(data []byte) []byte {
55
- return ed25519.Sign(c.privateKey, data)
56
-}
57
-
58
-func (c *Credential) Verify(data, sig []byte) bool {
59
- if len(sig) != ed25519.SignatureSize {
60
- return false
61
- }
62
- return ed25519.Verify(c.publicKey, data, sig)
63
-}
64
-
65
-func (c *Credential) PublicKey() ed25519.PublicKey {
66
- return c.publicKey
67
-}
68
-
69
-func (c *Credential) PrivateKey() ed25519.PrivateKey {
70
- return c.privateKey
71
-}
portal/core/proto/rdsec/rdsec.pb.go
deleted
-206
@@ -1,206 +0,0 @@
1
-// Code generated by protoc-gen-go. DO NOT EDIT.
2
-// versions:
3
-// protoc-gen-go v1.36.11
4
-// protoc v3.21.12
5
-// source: portal/core/proto/rdsec/rdsec.proto
6
-// Reflection-free version for TinyGo compatibility
7
-
8
-package rdsec
9
-
10
-type ProtocolVersion int32
11
-
12
-const (
13
- ProtocolVersion_PROTOCOL_VERSION_1 ProtocolVersion = 0
14
-)
15
-
16
-// Enum value maps for ProtocolVersion.
17
-var (
18
- ProtocolVersion_name = map[int32]string{
19
- 0: "PROTOCOL_VERSION_1",
20
- }
21
- ProtocolVersion_value = map[string]int32{
22
- "PROTOCOL_VERSION_1": 0,
23
- }
24
-)
25
-
26
-func (x ProtocolVersion) Enum() *ProtocolVersion {
27
- p := new(ProtocolVersion)
28
- *p = x
29
- return p
30
-}
31
-
32
-func (x ProtocolVersion) String() string {
33
- s, ok := ProtocolVersion_name[int32(x)]
34
- if !ok {
35
- return "ProtocolVersion(" + string(rune('0'+x)) + ")"
36
- }
37
- return s
38
-}
39
-
40
-type Identity struct {
41
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
42
- PublicKey []byte `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
43
- unknownFields []byte
44
-}
45
-
46
-func (x *Identity) Reset() {
47
- *x = Identity{}
48
-}
49
-
50
-func (x *Identity) ProtoMessage() {} // Stub for vtproto compatibility
51
-
52
-func (x *Identity) GetId() string {
53
- if x != nil {
54
- return x.Id
55
- }
56
- return ""
57
-}
58
-
59
-func (x *Identity) GetPublicKey() []byte {
60
- if x != nil {
61
- return x.PublicKey
62
- }
63
- return nil
64
-}
65
-
66
-type ClientInitPayload struct {
67
- Version ProtocolVersion `protobuf:"varint,1,opt,name=version,proto3,enum=rdsec.ProtocolVersion" json:"version,omitempty"`
68
- Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
69
- Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
70
- Identity *Identity `protobuf:"bytes,4,opt,name=identity,proto3" json:"identity,omitempty"`
71
- Alpn string `protobuf:"bytes,5,opt,name=alpn,proto3" json:"alpn,omitempty"`
72
- SessionPublicKey []byte `protobuf:"bytes,6,opt,name=session_public_key,json=sessionPublicKey,proto3" json:"session_public_key,omitempty"`
73
- unknownFields []byte
74
-}
75
-
76
-func (x *ClientInitPayload) Reset() {
77
- *x = ClientInitPayload{}
78
-}
79
-
80
-func (x *ClientInitPayload) ProtoMessage() {} // Stub for vtproto compatibility
81
-
82
-func (x *ClientInitPayload) GetVersion() ProtocolVersion {
83
- if x != nil {
84
- return x.Version
85
- }
86
- return ProtocolVersion_PROTOCOL_VERSION_1
87
-}
88
-
89
-func (x *ClientInitPayload) GetNonce() []byte {
90
- if x != nil {
91
- return x.Nonce
92
- }
93
- return nil
94
-}
95
-
96
-func (x *ClientInitPayload) GetTimestamp() int64 {
97
- if x != nil {
98
- return x.Timestamp
99
- }
100
- return 0
101
-}
102
-
103
-func (x *ClientInitPayload) GetIdentity() *Identity {
104
- if x != nil {
105
- return x.Identity
106
- }
107
- return nil
108
-}
109
-
110
-func (x *ClientInitPayload) GetAlpn() string {
111
- if x != nil {
112
- return x.Alpn
113
- }
114
- return ""
115
-}
116
-
117
-func (x *ClientInitPayload) GetSessionPublicKey() []byte {
118
- if x != nil {
119
- return x.SessionPublicKey
120
- }
121
- return nil
122
-}
123
-
124
-type SignedPayload struct {
125
- Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
126
- Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
127
- unknownFields []byte
128
-}
129
-
130
-func (x *SignedPayload) Reset() {
131
- *x = SignedPayload{}
132
-}
133
-
134
-func (x *SignedPayload) ProtoMessage() {} // Stub for vtproto compatibility
135
-
136
-func (x *SignedPayload) GetData() []byte {
137
- if x != nil {
138
- return x.Data
139
- }
140
- return nil
141
-}
142
-
143
-func (x *SignedPayload) GetSignature() []byte {
144
- if x != nil {
145
- return x.Signature
146
- }
147
- return nil
148
-}
149
-
150
-type ServerInitPayload struct {
151
- Version ProtocolVersion `protobuf:"varint,1,opt,name=version,proto3,enum=rdsec.ProtocolVersion" json:"version,omitempty"`
152
- Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
153
- Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
154
- Identity *Identity `protobuf:"bytes,4,opt,name=identity,proto3" json:"identity,omitempty"`
155
- Alpn string `protobuf:"bytes,5,opt,name=alpn,proto3" json:"alpn,omitempty"`
156
- SessionPublicKey []byte `protobuf:"bytes,6,opt,name=session_public_key,json=sessionPublicKey,proto3" json:"session_public_key,omitempty"`
157
- unknownFields []byte
158
-}
159
-
160
-func (x *ServerInitPayload) Reset() {
161
- *x = ServerInitPayload{}
162
-}
163
-
164
-func (x *ServerInitPayload) ProtoMessage() {} // Stub for vtproto compatibility
165
-
166
-func (x *ServerInitPayload) GetVersion() ProtocolVersion {
167
- if x != nil {
168
- return x.Version
169
- }
170
- return ProtocolVersion_PROTOCOL_VERSION_1
171
-}
172
-
173
-func (x *ServerInitPayload) GetNonce() []byte {
174
- if x != nil {
175
- return x.Nonce
176
- }
177
- return nil
178
-}
179
-
180
-func (x *ServerInitPayload) GetTimestamp() int64 {
181
- if x != nil {
182
- return x.Timestamp
183
- }
184
- return 0
185
-}
186
-
187
-func (x *ServerInitPayload) GetIdentity() *Identity {
188
- if x != nil {
189
- return x.Identity
190
- }
191
- return nil
192
-}
193
-
194
-func (x *ServerInitPayload) GetAlpn() string {
195
- if x != nil {
196
- return x.Alpn
197
- }
198
- return ""
199
-}
200
-
201
-func (x *ServerInitPayload) GetSessionPublicKey() []byte {
202
- if x != nil {
203
- return x.SessionPublicKey
204
- }
205
- return nil
206
-}
portal/core/proto/rdsec/rdsec.proto
deleted
-39
@@ -1,39 +0,0 @@
1
-syntax = "proto3";
2
-
3
-package rdsec;
4
-
5
-option go_package = "gosuda.org/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
-}
portal/core/proto/rdsec/rdsec_test.go
deleted
-925
@@ -1,925 +0,0 @@
1
-package rdsec
2
-
3
-import (
4
- "bytes"
5
- "testing"
6
-)
7
-
8
-// TestIdentity_MarshalVT_UnmarshalVT tests round-trip serialization for Identity
9
-func TestIdentity_MarshalVT_UnmarshalVT(t *testing.T) {
10
- tests := []struct {
11
- name string
12
- input *Identity
13
- wantErr bool
14
- }{
15
- {
16
- name: "empty",
17
- input: &Identity{},
18
- wantErr: false,
19
- },
20
- {
21
- name: "full",
22
- input: &Identity{
23
- Id: "test-id-12345",
24
- PublicKey: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
25
- },
26
- wantErr: false,
27
- },
28
- {
29
- name: "id only",
30
- input: &Identity{
31
- Id: "client-id",
32
- },
33
- wantErr: false,
34
- },
35
- {
36
- name: "public key only",
37
- input: &Identity{
38
- PublicKey: []byte{0xAA, 0xBB, 0xCC, 0xDD},
39
- },
40
- wantErr: false,
41
- },
42
- }
43
-
44
- for _, tt := range tests {
45
- t.Run(tt.name, func(t *testing.T) {
46
- data, err := tt.input.MarshalVT()
47
- if (err != nil) != tt.wantErr {
48
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
49
- return
50
- }
51
-
52
- got := &Identity{}
53
- err = got.UnmarshalVT(data)
54
- if (err != nil) != tt.wantErr {
55
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
56
- return
57
- }
58
-
59
- if !tt.input.EqualVT(got) {
60
- t.Errorf("roundtrip mismatch: got %+v, want %+v", got, tt.input)
61
- }
62
- })
63
- }
64
-}
65
-
66
-// TestIdentity_CloneVT tests that CloneVT creates an independent copy
67
-func TestIdentity_CloneVT(t *testing.T) {
68
- original := &Identity{
69
- Id: "original-id",
70
- PublicKey: []byte{0x01, 0x02, 0x03},
71
- }
72
-
73
- cloned := original.CloneVT()
74
-
75
- // Verify clone equals original
76
- if !original.EqualVT(cloned) {
77
- t.Error("clone does not equal original")
78
- }
79
-
80
- // Modify clone
81
- cloned.Id = "modified-id"
82
- cloned.PublicKey[0] = 0xFF
83
-
84
- // Verify original unchanged
85
- if original.Id != "original-id" {
86
- t.Error("original.Id was modified")
87
- }
88
- if original.PublicKey[0] != 0x01 {
89
- t.Error("original.PublicKey was modified")
90
- }
91
-}
92
-
93
-// TestIdentity_EqualVT tests equality comparison
94
-func TestIdentity_EqualVT(t *testing.T) {
95
- tests := []struct {
96
- name string
97
- a *Identity
98
- b *Identity
99
- want bool
100
- }{
101
- {
102
- name: "both nil",
103
- a: nil,
104
- b: nil,
105
- want: true,
106
- },
107
- {
108
- name: "same instance",
109
- a: &Identity{Id: "test"},
110
- b: &Identity{Id: "test"},
111
- want: true,
112
- },
113
- {
114
- name: "different id",
115
- a: &Identity{Id: "test-a"},
116
- b: &Identity{Id: "test-b"},
117
- want: false,
118
- },
119
- {
120
- name: "different public key",
121
- a: &Identity{PublicKey: []byte{0x01}},
122
- b: &Identity{PublicKey: []byte{0x02}},
123
- want: false,
124
- },
125
- {
126
- name: "one nil",
127
- a: &Identity{Id: "test"},
128
- b: nil,
129
- want: false,
130
- },
131
- }
132
-
133
- for _, tt := range tests {
134
- t.Run(tt.name, func(t *testing.T) {
135
- if got := tt.a.EqualVT(tt.b); got != tt.want {
136
- t.Errorf("EqualVT() = %v, want %v", got, tt.want)
137
- }
138
- })
139
- }
140
-}
141
-
142
-// TestIdentity_SizeVT tests size calculation
143
-func TestIdentity_SizeVT(t *testing.T) {
144
- msg := &Identity{
145
- Id: "test-id",
146
- PublicKey: []byte{0x01, 0x02, 0x03},
147
- }
148
-
149
- size := msg.SizeVT()
150
- data, err := msg.MarshalVT()
151
- if err != nil {
152
- t.Fatalf("MarshalVT() error = %v", err)
153
- }
154
-
155
- if size != len(data) {
156
- t.Errorf("SizeVT() = %v, but MarshalVT() produced %v bytes", size, len(data))
157
- }
158
-}
159
-
160
-// TestClientInitPayload_MarshalVT_UnmarshalVT tests round-trip serialization
161
-func TestClientInitPayload_MarshalVT_UnmarshalVT(t *testing.T) {
162
- tests := []struct {
163
- name string
164
- input *ClientInitPayload
165
- wantErr bool
166
- }{
167
- {
168
- name: "empty",
169
- input: &ClientInitPayload{},
170
- wantErr: false,
171
- },
172
- {
173
- name: "full",
174
- input: &ClientInitPayload{
175
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
176
- Nonce: []byte{0x01, 0x02, 0x03, 0x04},
177
- Timestamp: 1234567890,
178
- Identity: &Identity{Id: "client-id", PublicKey: []byte{0xAA, 0xBB}},
179
- Alpn: "h2",
180
- SessionPublicKey: []byte{0x11, 0x22, 0x33, 0x44},
181
- },
182
- wantErr: false,
183
- },
184
- {
185
- name: "with identity only",
186
- input: &ClientInitPayload{
187
- Identity: &Identity{Id: "test-client"},
188
- },
189
- wantErr: false,
190
- },
191
- }
192
-
193
- for _, tt := range tests {
194
- t.Run(tt.name, func(t *testing.T) {
195
- data, err := tt.input.MarshalVT()
196
- if (err != nil) != tt.wantErr {
197
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
198
- return
199
- }
200
-
201
- got := &ClientInitPayload{}
202
- err = got.UnmarshalVT(data)
203
- if (err != nil) != tt.wantErr {
204
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
205
- return
206
- }
207
-
208
- if !tt.input.EqualVT(got) {
209
- t.Errorf("roundtrip mismatch")
210
- }
211
- })
212
- }
213
-}
214
-
215
-// TestClientInitPayload_CloneVT tests deep cloning with nested Identity
216
-func TestClientInitPayload_CloneVT(t *testing.T) {
217
- original := &ClientInitPayload{
218
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
219
- Nonce: []byte{0x01, 0x02},
220
- Timestamp: 999,
221
- Identity: &Identity{Id: "nested-id", PublicKey: []byte{0x03, 0x04}},
222
- Alpn: "h2",
223
- }
224
-
225
- cloned := original.CloneVT()
226
-
227
- // Verify clone equals original
228
- if !original.EqualVT(cloned) {
229
- t.Error("clone does not equal original")
230
- }
231
-
232
- // Modify nested identity in clone
233
- cloned.Identity.Id = "modified-nested"
234
- cloned.Identity.PublicKey[0] = 0xFF
235
-
236
- // Verify original nested identity unchanged
237
- if original.Identity.Id != "nested-id" {
238
- t.Error("original.Identity.Id was modified")
239
- }
240
- if original.Identity.PublicKey[0] != 0x03 {
241
- t.Error("original.Identity.PublicKey was modified")
242
- }
243
-}
244
-
245
-// TestSignedPayload_MarshalVT_UnmarshalVT tests round-trip serialization
246
-func TestSignedPayload_MarshalVT_UnmarshalVT(t *testing.T) {
247
- tests := []struct {
248
- name string
249
- input *SignedPayload
250
- wantErr bool
251
- }{
252
- {
253
- name: "empty",
254
- input: &SignedPayload{},
255
- wantErr: false,
256
- },
257
- {
258
- name: "full",
259
- input: &SignedPayload{
260
- Data: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
261
- Signature: []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE},
262
- },
263
- wantErr: false,
264
- },
265
- {
266
- name: "data only",
267
- input: &SignedPayload{
268
- Data: []byte("payload data"),
269
- },
270
- wantErr: false,
271
- },
272
- {
273
- name: "signature only",
274
- input: &SignedPayload{
275
- Signature: []byte{0xFF, 0xFF, 0xFF},
276
- },
277
- wantErr: false,
278
- },
279
- }
280
-
281
- for _, tt := range tests {
282
- t.Run(tt.name, func(t *testing.T) {
283
- data, err := tt.input.MarshalVT()
284
- if (err != nil) != tt.wantErr {
285
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
286
- return
287
- }
288
-
289
- got := &SignedPayload{}
290
- err = got.UnmarshalVT(data)
291
- if (err != nil) != tt.wantErr {
292
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
293
- return
294
- }
295
-
296
- if !tt.input.EqualVT(got) {
297
- t.Errorf("roundtrip mismatch")
298
- }
299
- })
300
- }
301
-}
302
-
303
-// TestSignedPayload_CloneVT tests independent copy creation
304
-func TestSignedPayload_CloneVT(t *testing.T) {
305
- original := &SignedPayload{
306
- Data: []byte{0x01, 0x02, 0x03},
307
- Signature: []byte{0xAA, 0xBB, 0xCC},
308
- }
309
-
310
- cloned := original.CloneVT()
311
-
312
- // Modify clone
313
- cloned.Data[0] = 0xFF
314
- cloned.Signature[0] = 0x00
315
-
316
- // Verify original unchanged
317
- if original.Data[0] != 0x01 {
318
- t.Error("original.Data was modified")
319
- }
320
- if original.Signature[0] != 0xAA {
321
- t.Error("original.Signature was modified")
322
- }
323
-}
324
-
325
-// TestServerInitPayload_MarshalVT_UnmarshalVT tests round-trip serialization
326
-func TestServerInitPayload_MarshalVT_UnmarshalVT(t *testing.T) {
327
- tests := []struct {
328
- name string
329
- input *ServerInitPayload
330
- wantErr bool
331
- }{
332
- {
333
- name: "empty",
334
- input: &ServerInitPayload{},
335
- wantErr: false,
336
- },
337
- {
338
- name: "full",
339
- input: &ServerInitPayload{
340
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
341
- Nonce: []byte{0x01, 0x02, 0x03, 0x04},
342
- Timestamp: 9876543210,
343
- Identity: &Identity{Id: "server-id", PublicKey: []byte{0xAA, 0xBB}},
344
- Alpn: "h2",
345
- SessionPublicKey: []byte{0x11, 0x22, 0x33, 0x44},
346
- },
347
- wantErr: false,
348
- },
349
- {
350
- name: "with identity only",
351
- input: &ServerInitPayload{
352
- Identity: &Identity{Id: "test-server"},
353
- },
354
- wantErr: false,
355
- },
356
- }
357
-
358
- for _, tt := range tests {
359
- t.Run(tt.name, func(t *testing.T) {
360
- data, err := tt.input.MarshalVT()
361
- if (err != nil) != tt.wantErr {
362
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
363
- return
364
- }
365
-
366
- got := &ServerInitPayload{}
367
- err = got.UnmarshalVT(data)
368
- if (err != nil) != tt.wantErr {
369
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
370
- return
371
- }
372
-
373
- if !tt.input.EqualVT(got) {
374
- t.Errorf("roundtrip mismatch")
375
- }
376
- })
377
- }
378
-}
379
-
380
-// TestServerInitPayload_CloneVT tests deep cloning with nested Identity
381
-func TestServerInitPayload_CloneVT(t *testing.T) {
382
- original := &ServerInitPayload{
383
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
384
- Nonce: []byte{0x01, 0x02},
385
- Timestamp: 888,
386
- Identity: &Identity{Id: "server-nested", PublicKey: []byte{0x05, 0x06}},
387
- Alpn: "h2",
388
- }
389
-
390
- cloned := original.CloneVT()
391
-
392
- // Verify clone equals original
393
- if !original.EqualVT(cloned) {
394
- t.Error("clone does not equal original")
395
- }
396
-
397
- // Modify nested identity in clone
398
- cloned.Identity.Id = "modified-server"
399
-
400
- // Verify original nested identity unchanged
401
- if original.Identity.Id != "server-nested" {
402
- t.Error("original.Identity.Id was modified")
403
- }
404
-}
405
-
406
-// TestReset tests that Reset clears all fields
407
-func TestReset(t *testing.T) {
408
- // Test Identity reset
409
- ident := &Identity{
410
- Id: "test-id",
411
- PublicKey: []byte{0x01, 0x02},
412
- }
413
- ident.Reset()
414
- if ident.Id != "" {
415
- t.Error("Identity.Id not cleared after Reset()")
416
- }
417
- if ident.PublicKey != nil {
418
- t.Error("Identity.PublicKey not cleared after Reset()")
419
- }
420
-
421
- // Test ClientInitPayload reset
422
- payload := &ClientInitPayload{
423
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
424
- Nonce: []byte{0x01},
425
- Timestamp: 123,
426
- Identity: &Identity{Id: "test"},
427
- Alpn: "h2",
428
- }
429
- payload.Reset()
430
- if payload.Version != 0 {
431
- t.Error("ClientInitPayload.Version not cleared after Reset()")
432
- }
433
- if payload.Nonce != nil {
434
- t.Error("ClientInitPayload.Nonce not cleared after Reset()")
435
- }
436
- if payload.Timestamp != 0 {
437
- t.Error("ClientInitPayload.Timestamp not cleared after Reset()")
438
- }
439
- if payload.Identity != nil {
440
- t.Error("ClientInitPayload.Identity not cleared after Reset()")
441
- }
442
- if payload.Alpn != "" {
443
- t.Error("ClientInitPayload.Alpn not cleared after Reset()")
444
- }
445
-}
446
-
447
-// TestMarshalToSizedBufferVT tests buffer marshaling
448
-func TestMarshalToSizedBufferVT(t *testing.T) {
449
- msg := &Identity{
450
- Id: "buffer-test",
451
- PublicKey: []byte{0x01, 0x02, 0x03},
452
- }
453
-
454
- size := msg.SizeVT()
455
- buf := make([]byte, size)
456
-
457
- n, err := msg.MarshalToSizedBufferVT(buf)
458
- if err != nil {
459
- t.Fatalf("MarshalToSizedBufferVT() error = %v", err)
460
- }
461
-
462
- if n != size {
463
- t.Errorf("MarshalToSizedBufferVT() returned %v, want %v", n, size)
464
- }
465
-
466
- // Verify unmarshal works
467
- got := &Identity{}
468
- err = got.UnmarshalVT(buf[:n])
469
- if err != nil {
470
- t.Fatalf("UnmarshalVT() error = %v", err)
471
- }
472
-
473
- if !msg.EqualVT(got) {
474
- t.Error("roundtrip mismatch with MarshalToSizedBufferVT")
475
- }
476
-}
477
-
478
-// TestConcurrentSerialization tests concurrent marshal/unmarshal
479
-func TestConcurrentSerialization(t *testing.T) {
480
- msg := &ClientInitPayload{
481
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
482
- Nonce: []byte{0x01, 0x02, 0x03, 0x04},
483
- Timestamp: 1234567890,
484
- Identity: &Identity{Id: "concurrent-test", PublicKey: []byte{0xAA, 0xBB}},
485
- Alpn: "h2",
486
- SessionPublicKey: []byte{0x11, 0x22, 0x33, 0x44},
487
- }
488
-
489
- data, err := msg.MarshalVT()
490
- if err != nil {
491
- t.Fatalf("MarshalVT() error = %v", err)
492
- }
493
-
494
- // Run concurrent unmarshals
495
- done := make(chan bool, 10)
496
- for range 10 {
497
- go func() {
498
- got := &ClientInitPayload{}
499
- if err := got.UnmarshalVT(data); err != nil {
500
- t.Errorf("concurrent UnmarshalVT() error = %v", err)
501
- }
502
- if !msg.EqualVT(got) {
503
- t.Error("concurrent roundtrip mismatch")
504
- }
505
- done <- true
506
- }()
507
- }
508
-
509
- for range 10 {
510
- <-done
511
- }
512
-}
513
-
514
-// TestProtoMessage tests ProtoMessage stub exists
515
-func TestProtoMessage(t *testing.T) {
516
- // These tests just verify the stub methods exist and don't panic
517
- var (
518
- ident = &Identity{}
519
- clientInit = &ClientInitPayload{}
520
- signedPayload = &SignedPayload{}
521
- serverInit = &ServerInitPayload{}
522
- )
523
-
524
- // Should not panic
525
- ident.ProtoMessage()
526
- clientInit.ProtoMessage()
527
- signedPayload.ProtoMessage()
528
- serverInit.ProtoMessage()
529
-}
530
-
531
-// TestGetters tests getter methods
532
-func TestGetters(t *testing.T) {
533
- ident := &Identity{
534
- Id: "test-id",
535
- PublicKey: []byte{0x01, 0x02},
536
- }
537
-
538
- if got := ident.GetId(); got != "test-id" {
539
- t.Errorf("GetId() = %v, want test-id", got)
540
- }
541
- if got := ident.GetPublicKey(); len(got) != 2 || got[0] != 0x01 {
542
- t.Errorf("GetPublicKey() = %v, want [0x01, 0x02]", got)
543
- }
544
-
545
- // Test nil case
546
- var nilIdent *Identity
547
- if got := nilIdent.GetId(); got != "" {
548
- t.Errorf("GetId() on nil = %v, want empty string", got)
549
- }
550
- if got := nilIdent.GetPublicKey(); got != nil {
551
- t.Errorf("GetPublicKey() on nil = %v, want nil", got)
552
- }
553
-}
554
-
555
-// TestNilHandling tests nil message handling
556
-func TestNilHandling(t *testing.T) {
557
- var nilIdent *Identity
558
-
559
- // MarshalVT on nil should return nil, nil
560
- if data, err := nilIdent.MarshalVT(); err != nil || data != nil {
561
- t.Errorf("MarshalVT() on nil = (%v, %v), want (nil, nil)", data, err)
562
- }
563
-
564
- // CloneVT on nil should return nil
565
- if cloned := nilIdent.CloneVT(); cloned != nil {
566
- t.Errorf("CloneVT() on nil = %v, want nil", cloned)
567
- }
568
-
569
- // SizeVT on nil should return 0
570
- if size := nilIdent.SizeVT(); size != 0 {
571
- t.Errorf("SizeVT() on nil = %v, want 0", size)
572
- }
573
-
574
- // EqualVT on nil with nil should return true
575
- if !nilIdent.EqualVT(nil) {
576
- t.Error("EqualVT(nil, nil) = false, want true")
577
- }
578
-
579
- // EqualVT on nil with non-nil should return false
580
- if nilIdent.EqualVT(&Identity{}) {
581
- t.Error("EqualVT(nil, &Identity{}) = true, want false")
582
- }
583
-
584
- // Test all message types handle nil correctly
585
- testCases := []struct {
586
- name string
587
- test func() // test function that verifies nil handling
588
- }{
589
- {"ClientInitPayload", func() {
590
- var msg *ClientInitPayload
591
- if data, err := msg.MarshalVT(); err != nil || data != nil {
592
- t.Errorf("MarshalVT() on nil ClientInitPayload = (%v, %v), want (nil, nil)", data, err)
593
- }
594
- if msg.CloneVT() != nil {
595
- t.Error("CloneVT() on nil ClientInitPayload should return nil")
596
- }
597
- if msg.SizeVT() != 0 {
598
- t.Error("SizeVT() on nil ClientInitPayload should return 0")
599
- }
600
- }},
601
- {"SignedPayload", func() {
602
- var msg *SignedPayload
603
- if data, err := msg.MarshalVT(); err != nil || data != nil {
604
- t.Errorf("MarshalVT() on nil SignedPayload = (%v, %v), want (nil, nil)", data, err)
605
- }
606
- if msg.CloneVT() != nil {
607
- t.Error("CloneVT() on nil SignedPayload should return nil")
608
- }
609
- }},
610
- {"ServerInitPayload", func() {
611
- var msg *ServerInitPayload
612
- if data, err := msg.MarshalVT(); err != nil || data != nil {
613
- t.Errorf("MarshalVT() on nil ServerInitPayload = (%v, %v), want (nil, nil)", data, err)
614
- }
615
- if msg.CloneVT() != nil {
616
- t.Error("CloneVT() on nil ServerInitPayload should return nil")
617
- }
618
- }},
619
- }
620
-
621
- for _, tc := range testCases {
622
- t.Run(tc.name, func(t *testing.T) {
623
- tc.test()
624
- })
625
- }
626
-}
627
-
628
-// TestMarshalVTStrict tests strict marshaling
629
-func TestMarshalVTStrict(t *testing.T) {
630
- msg := &Identity{
631
- Id: "strict-test",
632
- PublicKey: []byte{0x01, 0x02, 0x03},
633
- }
634
-
635
- data, err := msg.MarshalVTStrict()
636
- if err != nil {
637
- t.Fatalf("MarshalVTStrict() error = %v", err)
638
- }
639
-
640
- got := &Identity{}
641
- err = got.UnmarshalVT(data)
642
- if err != nil {
643
- t.Fatalf("UnmarshalVT() error = %v", err)
644
- }
645
-
646
- if !msg.EqualVT(got) {
647
- t.Error("MarshalVTStrict roundtrip mismatch")
648
- }
649
-}
650
-
651
-// TestUnmarshalVTUnsafe tests unsafe unmarshaling
652
-func TestUnmarshalVTUnsafe(t *testing.T) {
653
- msg := &ClientInitPayload{
654
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
655
- Nonce: []byte{0x01, 0x02, 0x03, 0x04},
656
- Timestamp: 1234567890,
657
- Identity: &Identity{Id: "unsafe-test", PublicKey: []byte{0xAA, 0xBB}},
658
- Alpn: "h2",
659
- SessionPublicKey: []byte{0x11, 0x22, 0x33, 0x44},
660
- }
661
-
662
- data, err := msg.MarshalVT()
663
- if err != nil {
664
- t.Fatalf("MarshalVT() error = %v", err)
665
- }
666
-
667
- got := &ClientInitPayload{}
668
- err = got.UnmarshalVTUnsafe(data)
669
- if err != nil {
670
- t.Fatalf("UnmarshalVTUnsafe() error = %v", err)
671
- }
672
-
673
- if !msg.EqualVT(got) {
674
- t.Error("UnmarshalVTUnsafe roundtrip mismatch")
675
- }
676
-}
677
-
678
-// BenchmarkIdentity_MarshalVT benchmarks marshaling
679
-func BenchmarkIdentity_MarshalVT(b *testing.B) {
680
- msg := &Identity{
681
- Id: "benchmark-test-id-12345",
682
- PublicKey: bytes.Repeat([]byte{0xAA}, 32),
683
- }
684
-
685
- b.ResetTimer()
686
- for range b.N {
687
- _, _ = msg.MarshalVT()
688
- }
689
-}
690
-
691
-// BenchmarkIdentity_UnmarshalVT benchmarks unmarshaling
692
-func BenchmarkIdentity_UnmarshalVT(b *testing.B) {
693
- msg := &Identity{
694
- Id: "benchmark-test-id-12345",
695
- PublicKey: bytes.Repeat([]byte{0xAA}, 32),
696
- }
697
-
698
- data, _ := msg.MarshalVT()
699
-
700
- b.ResetTimer()
701
- for range b.N {
702
- got := &Identity{}
703
- _ = got.UnmarshalVT(data)
704
- }
705
-}
706
-
707
-// BenchmarkClientInitPayload_MarshalVT benchmarks complex message marshaling
708
-func BenchmarkClientInitPayload_MarshalVT(b *testing.B) {
709
- msg := &ClientInitPayload{
710
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
711
- Nonce: bytes.Repeat([]byte{0x01}, 32),
712
- Timestamp: 1234567890,
713
- Identity: &Identity{Id: "benchmark-client", PublicKey: bytes.Repeat([]byte{0xAA}, 32)},
714
- Alpn: "h2",
715
- SessionPublicKey: bytes.Repeat([]byte{0xFF}, 32),
716
- }
717
-
718
- b.ResetTimer()
719
- for range b.N {
720
- _, _ = msg.MarshalVT()
721
- }
722
-}
723
-
724
-// BenchmarkClientInitPayload_UnmarshalVT benchmarks complex message unmarshaling
725
-func BenchmarkClientInitPayload_UnmarshalVT(b *testing.B) {
726
- msg := &ClientInitPayload{
727
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
728
- Nonce: bytes.Repeat([]byte{0x01}, 32),
729
- Timestamp: 1234567890,
730
- Identity: &Identity{Id: "benchmark-client", PublicKey: bytes.Repeat([]byte{0xAA}, 32)},
731
- Alpn: "h2",
732
- SessionPublicKey: bytes.Repeat([]byte{0xFF}, 32),
733
- }
734
-
735
- data, _ := msg.MarshalVT()
736
-
737
- b.ResetTimer()
738
- for range b.N {
739
- got := &ClientInitPayload{}
740
- _ = got.UnmarshalVT(data)
741
- }
742
-}
743
-
744
-// TestProtocolVersion_String tests enum String method
745
-func TestProtocolVersion_String(t *testing.T) {
746
- tests := []struct {
747
- name string
748
- enum ProtocolVersion
749
- want string
750
- }{
751
- {"PROTOCOL_VERSION_1", ProtocolVersion_PROTOCOL_VERSION_1, "PROTOCOL_VERSION_1"},
752
- }
753
-
754
- for _, tt := range tests {
755
- t.Run(tt.name, func(t *testing.T) {
756
- if got := tt.enum.String(); got != tt.want {
757
- t.Errorf("ProtocolVersion.String() = %v, want %v", got, tt.want)
758
- }
759
- })
760
- }
761
-
762
- // Test that invalid value returns a non-empty string
763
- invalid := ProtocolVersion(999).String()
764
- if invalid == "" {
765
- t.Error("ProtocolVersion(999).String() should return non-empty string")
766
- }
767
-}
768
-
769
-// TestProtocolVersion_Enum tests Enum method
770
-func TestProtocolVersion_Enum(t *testing.T) {
771
- if ProtocolVersion_PROTOCOL_VERSION_1.Enum() != nil && *ProtocolVersion_PROTOCOL_VERSION_1.Enum() != 0 {
772
- t.Error("ProtocolVersion.Enum() should return 0")
773
- }
774
-}
775
-
776
-// TestClientInitPayload_Getters tests all getter methods
777
-func TestClientInitPayload_Getters(t *testing.T) {
778
- msg := &ClientInitPayload{
779
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
780
- Nonce: []byte{0x01, 0x02, 0x03},
781
- Timestamp: 1234567890,
782
- Identity: &Identity{Id: "getter-test", PublicKey: []byte{0xAA}},
783
- Alpn: "h2",
784
- SessionPublicKey: []byte{0x11, 0x22},
785
- }
786
-
787
- if got := msg.GetVersion(); got != ProtocolVersion_PROTOCOL_VERSION_1 {
788
- t.Errorf("GetVersion() = %v, want %v", got, ProtocolVersion_PROTOCOL_VERSION_1)
789
- }
790
- if got := msg.GetNonce(); !bytes.Equal(got, []byte{0x01, 0x02, 0x03}) {
791
- t.Errorf("GetNonce() = %v, want [1 2 3]", got)
792
- }
793
- if got := msg.GetTimestamp(); got != 1234567890 {
794
- t.Errorf("GetTimestamp() = %v, want 1234567890", got)
795
- }
796
- if got := msg.GetIdentity(); got == nil || got.Id != "getter-test" {
797
- t.Errorf("GetIdentity() = %v, want Id='getter-test'", got)
798
- }
799
- if got := msg.GetAlpn(); got != "h2" {
800
- t.Errorf("GetAlpn() = %v, want h2", got)
801
- }
802
- if got := msg.GetSessionPublicKey(); !bytes.Equal(got, []byte{0x11, 0x22}) {
803
- t.Errorf("GetSessionPublicKey() = %v, want [17 34]", got)
804
- }
805
-
806
- // Test nil defaults
807
- empty := &ClientInitPayload{}
808
- if got := empty.GetVersion(); got != ProtocolVersion_PROTOCOL_VERSION_1 {
809
- t.Errorf("empty GetVersion() should return default")
810
- }
811
- if got := empty.GetNonce(); got != nil {
812
- t.Errorf("empty GetNonce() = %v, want nil", got)
813
- }
814
- if got := empty.GetIdentity(); got != nil {
815
- t.Errorf("empty GetIdentity() = %v, want nil", got)
816
- }
817
- if got := empty.GetAlpn(); got != "" {
818
- t.Errorf("empty GetAlpn() = %v, want empty string", got)
819
- }
820
-}
821
-
822
-// TestSignedPayload_Getters tests all getter methods
823
-func TestSignedPayload_Getters(t *testing.T) {
824
- msg := &SignedPayload{
825
- Data: []byte{0x01, 0x02, 0x03},
826
- Signature: []byte{0xAA, 0xBB},
827
- }
828
-
829
- if got := msg.GetData(); !bytes.Equal(got, []byte{0x01, 0x02, 0x03}) {
830
- t.Errorf("GetData() = %v, want [1 2 3]", got)
831
- }
832
- if got := msg.GetSignature(); !bytes.Equal(got, []byte{0xAA, 0xBB}) {
833
- t.Errorf("GetSignature() = %v, want [170 187]", got)
834
- }
835
-
836
- // Test nil defaults
837
- empty := &SignedPayload{}
838
- if got := empty.GetData(); got != nil {
839
- t.Errorf("empty GetData() = %v, want nil", got)
840
- }
841
- if got := empty.GetSignature(); got != nil {
842
- t.Errorf("empty GetSignature() = %v, want nil", got)
843
- }
844
-}
845
-
846
-// TestServerInitPayload_Getters tests all getter methods
847
-func TestServerInitPayload_Getters(t *testing.T) {
848
- msg := &ServerInitPayload{
849
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
850
- Nonce: []byte{0x01, 0x02},
851
- Timestamp: 9876543210,
852
- Identity: &Identity{Id: "server-test", PublicKey: []byte{}},
853
- Alpn: "h3",
854
- SessionPublicKey: []byte{0xCC, 0xDD},
855
- }
856
-
857
- if got := msg.GetVersion(); got != ProtocolVersion_PROTOCOL_VERSION_1 {
858
- t.Errorf("GetVersion() = %v, want %v", got, ProtocolVersion_PROTOCOL_VERSION_1)
859
- }
860
- if got := msg.GetNonce(); !bytes.Equal(got, []byte{0x01, 0x02}) {
861
- t.Errorf("GetNonce() = %v, want [1 2]", got)
862
- }
863
- if got := msg.GetTimestamp(); got != 9876543210 {
864
- t.Errorf("GetTimestamp() = %v, want 9876543210", got)
865
- }
866
- if got := msg.GetIdentity(); got == nil || got.Id != "server-test" {
867
- t.Errorf("GetIdentity() = %v, want Id='server-test'", got)
868
- }
869
- if got := msg.GetAlpn(); got != "h3" {
870
- t.Errorf("GetAlpn() = %v, want h3", got)
871
- }
872
- if got := msg.GetSessionPublicKey(); !bytes.Equal(got, []byte{0xCC, 0xDD}) {
873
- t.Errorf("GetSessionPublicKey() = %v, want [204 221]", got)
874
- }
875
-}
876
-
877
-// TestSignedPayload_Reset tests Reset method
878
-func TestSignedPayload_Reset(t *testing.T) {
879
- msg := &SignedPayload{
880
- Data: []byte{0x01, 0x02},
881
- Signature: []byte{0xAA, 0xBB},
882
- }
883
-
884
- msg.Reset()
885
-
886
- if msg.Data != nil {
887
- t.Error("Reset() did not clear Data")
888
- }
889
- if msg.Signature != nil {
890
- t.Error("Reset() did not clear Signature")
891
- }
892
-}
893
-
894
-// TestServerInitPayload_Reset tests Reset method
895
-func TestServerInitPayload_Reset(t *testing.T) {
896
- msg := &ServerInitPayload{
897
- Version: ProtocolVersion_PROTOCOL_VERSION_1,
898
- Nonce: []byte{0x01},
899
- Timestamp: 123,
900
- Identity: &Identity{Id: "test"},
901
- Alpn: "h2",
902
- SessionPublicKey: []byte{0xAA},
903
- }
904
-
905
- msg.Reset()
906
-
907
- if msg.Version != ProtocolVersion_PROTOCOL_VERSION_1 {
908
- t.Error("Reset() changed Version from default")
909
- }
910
- if msg.Nonce != nil {
911
- t.Error("Reset() did not clear Nonce")
912
- }
913
- if msg.Timestamp != 0 {
914
- t.Error("Reset() did not clear Timestamp")
915
- }
916
- if msg.Identity != nil {
917
- t.Error("Reset() did not clear Identity")
918
- }
919
- if msg.Alpn != "" {
920
- t.Error("Reset() did not clear Alpn")
921
- }
922
- if msg.SessionPublicKey != nil {
923
- t.Error("Reset() did not clear SessionPublicKey")
924
- }
925
-}
portal/core/proto/rdsec/rdsec_vtproto.pb.go
deleted
-2188
@@ -1,2188 +0,0 @@
1
-// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
2
-// protoc-gen-go-vtproto version: v0.6.0
3
-// source: portal/core/proto/rdsec/rdsec.proto
4
-
5
-package rdsec
6
-
7
-import (
8
- fmt "fmt"
9
- protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
10
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
11
- io "io"
12
- unsafe "unsafe"
13
-)
14
-
15
-const (
16
- // Verify that this generated code is sufficiently up-to-date.
17
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
18
- // Verify that runtime/protoimpl is sufficiently up-to-date.
19
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
20
-)
21
-
22
-func (m *Identity) CloneVT() *Identity {
23
- if m == nil {
24
- return (*Identity)(nil)
25
- }
26
- r := new(Identity)
27
- r.Id = m.Id
28
- if rhs := m.PublicKey; rhs != nil {
29
- tmpBytes := make([]byte, len(rhs))
30
- copy(tmpBytes, rhs)
31
- r.PublicKey = tmpBytes
32
- }
33
- if len(m.unknownFields) > 0 {
34
- r.unknownFields = make([]byte, len(m.unknownFields))
35
- copy(r.unknownFields, m.unknownFields)
36
- }
37
- return r
38
-}
39
-
40
-func (m *Identity) CloneMessageVT() any {
41
- return m.CloneVT()
42
-}
43
-
44
-func (m *ClientInitPayload) CloneVT() *ClientInitPayload {
45
- if m == nil {
46
- return (*ClientInitPayload)(nil)
47
- }
48
- r := new(ClientInitPayload)
49
- r.Version = m.Version
50
- r.Timestamp = m.Timestamp
51
- r.Identity = m.Identity.CloneVT()
52
- r.Alpn = m.Alpn
53
- if rhs := m.Nonce; rhs != nil {
54
- tmpBytes := make([]byte, len(rhs))
55
- copy(tmpBytes, rhs)
56
- r.Nonce = tmpBytes
57
- }
58
- if rhs := m.SessionPublicKey; rhs != nil {
59
- tmpBytes := make([]byte, len(rhs))
60
- copy(tmpBytes, rhs)
61
- r.SessionPublicKey = tmpBytes
62
- }
63
- if len(m.unknownFields) > 0 {
64
- r.unknownFields = make([]byte, len(m.unknownFields))
65
- copy(r.unknownFields, m.unknownFields)
66
- }
67
- return r
68
-}
69
-
70
-func (m *ClientInitPayload) CloneMessageVT() any {
71
- return m.CloneVT()
72
-}
73
-
74
-func (m *SignedPayload) CloneVT() *SignedPayload {
75
- if m == nil {
76
- return (*SignedPayload)(nil)
77
- }
78
- r := new(SignedPayload)
79
- if rhs := m.Data; rhs != nil {
80
- tmpBytes := make([]byte, len(rhs))
81
- copy(tmpBytes, rhs)
82
- r.Data = tmpBytes
83
- }
84
- if rhs := m.Signature; rhs != nil {
85
- tmpBytes := make([]byte, len(rhs))
86
- copy(tmpBytes, rhs)
87
- r.Signature = tmpBytes
88
- }
89
- if len(m.unknownFields) > 0 {
90
- r.unknownFields = make([]byte, len(m.unknownFields))
91
- copy(r.unknownFields, m.unknownFields)
92
- }
93
- return r
94
-}
95
-
96
-func (m *SignedPayload) CloneMessageVT() any {
97
- return m.CloneVT()
98
-}
99
-
100
-func (m *ServerInitPayload) CloneVT() *ServerInitPayload {
101
- if m == nil {
102
- return (*ServerInitPayload)(nil)
103
- }
104
- r := new(ServerInitPayload)
105
- r.Version = m.Version
106
- r.Timestamp = m.Timestamp
107
- r.Identity = m.Identity.CloneVT()
108
- r.Alpn = m.Alpn
109
- if rhs := m.Nonce; rhs != nil {
110
- tmpBytes := make([]byte, len(rhs))
111
- copy(tmpBytes, rhs)
112
- r.Nonce = tmpBytes
113
- }
114
- if rhs := m.SessionPublicKey; rhs != nil {
115
- tmpBytes := make([]byte, len(rhs))
116
- copy(tmpBytes, rhs)
117
- r.SessionPublicKey = tmpBytes
118
- }
119
- if len(m.unknownFields) > 0 {
120
- r.unknownFields = make([]byte, len(m.unknownFields))
121
- copy(r.unknownFields, m.unknownFields)
122
- }
123
- return r
124
-}
125
-
126
-func (m *ServerInitPayload) CloneMessageVT() any {
127
- return m.CloneVT()
128
-}
129
-
130
-func (this *Identity) EqualVT(that *Identity) bool {
131
- if this == that {
132
- return true
133
- } else if this == nil || that == nil {
134
- return false
135
- }
136
- if this.Id != that.Id {
137
- return false
138
- }
139
- if string(this.PublicKey) != string(that.PublicKey) {
140
- return false
141
- }
142
- return string(this.unknownFields) == string(that.unknownFields)
143
-}
144
-
145
-func (this *Identity) EqualMessageVT(thatMsg any) bool {
146
- that, ok := thatMsg.(*Identity)
147
- if !ok {
148
- return false
149
- }
150
- return this.EqualVT(that)
151
-}
152
-func (this *ClientInitPayload) EqualVT(that *ClientInitPayload) bool {
153
- if this == that {
154
- return true
155
- } else if this == nil || that == nil {
156
- return false
157
- }
158
- if this.Version != that.Version {
159
- return false
160
- }
161
- if string(this.Nonce) != string(that.Nonce) {
162
- return false
163
- }
164
- if this.Timestamp != that.Timestamp {
165
- return false
166
- }
167
- if !this.Identity.EqualVT(that.Identity) {
168
- return false
169
- }
170
- if this.Alpn != that.Alpn {
171
- return false
172
- }
173
- if string(this.SessionPublicKey) != string(that.SessionPublicKey) {
174
- return false
175
- }
176
- return string(this.unknownFields) == string(that.unknownFields)
177
-}
178
-
179
-func (this *ClientInitPayload) EqualMessageVT(thatMsg any) bool {
180
- that, ok := thatMsg.(*ClientInitPayload)
181
- if !ok {
182
- return false
183
- }
184
- return this.EqualVT(that)
185
-}
186
-func (this *SignedPayload) EqualVT(that *SignedPayload) bool {
187
- if this == that {
188
- return true
189
- } else if this == nil || that == nil {
190
- return false
191
- }
192
- if string(this.Data) != string(that.Data) {
193
- return false
194
- }
195
- if string(this.Signature) != string(that.Signature) {
196
- return false
197
- }
198
- return string(this.unknownFields) == string(that.unknownFields)
199
-}
200
-
201
-func (this *SignedPayload) EqualMessageVT(thatMsg any) bool {
202
- that, ok := thatMsg.(*SignedPayload)
203
- if !ok {
204
- return false
205
- }
206
- return this.EqualVT(that)
207
-}
208
-func (this *ServerInitPayload) EqualVT(that *ServerInitPayload) bool {
209
- if this == that {
210
- return true
211
- } else if this == nil || that == nil {
212
- return false
213
- }
214
- if this.Version != that.Version {
215
- return false
216
- }
217
- if string(this.Nonce) != string(that.Nonce) {
218
- return false
219
- }
220
- if this.Timestamp != that.Timestamp {
221
- return false
222
- }
223
- if !this.Identity.EqualVT(that.Identity) {
224
- return false
225
- }
226
- if this.Alpn != that.Alpn {
227
- return false
228
- }
229
- if string(this.SessionPublicKey) != string(that.SessionPublicKey) {
230
- return false
231
- }
232
- return string(this.unknownFields) == string(that.unknownFields)
233
-}
234
-
235
-func (this *ServerInitPayload) EqualMessageVT(thatMsg any) bool {
236
- that, ok := thatMsg.(*ServerInitPayload)
237
- if !ok {
238
- return false
239
- }
240
- return this.EqualVT(that)
241
-}
242
-func (m *Identity) MarshalVT() (dAtA []byte, err error) {
243
- if m == nil {
244
- return nil, nil
245
- }
246
- size := m.SizeVT()
247
- dAtA = make([]byte, size)
248
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
249
- if err != nil {
250
- return nil, err
251
- }
252
- return dAtA[:n], nil
253
-}
254
-
255
-func (m *Identity) MarshalToVT(dAtA []byte) (int, error) {
256
- size := m.SizeVT()
257
- return m.MarshalToSizedBufferVT(dAtA[:size])
258
-}
259
-
260
-func (m *Identity) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
261
- if m == nil {
262
- return 0, nil
263
- }
264
- i := len(dAtA)
265
- _ = i
266
- var l int
267
- _ = l
268
- if m.unknownFields != nil {
269
- i -= len(m.unknownFields)
270
- copy(dAtA[i:], m.unknownFields)
271
- }
272
- if len(m.PublicKey) > 0 {
273
- i -= len(m.PublicKey)
274
- copy(dAtA[i:], m.PublicKey)
275
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.PublicKey)))
276
- i--
277
- dAtA[i] = 0x12
278
- }
279
- if len(m.Id) > 0 {
280
- i -= len(m.Id)
281
- copy(dAtA[i:], m.Id)
282
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id)))
283
- i--
284
- dAtA[i] = 0xa
285
- }
286
- return len(dAtA) - i, nil
287
-}
288
-
289
-func (m *ClientInitPayload) MarshalVT() (dAtA []byte, err error) {
290
- if m == nil {
291
- return nil, nil
292
- }
293
- size := m.SizeVT()
294
- dAtA = make([]byte, size)
295
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
296
- if err != nil {
297
- return nil, err
298
- }
299
- return dAtA[:n], nil
300
-}
301
-
302
-func (m *ClientInitPayload) MarshalToVT(dAtA []byte) (int, error) {
303
- size := m.SizeVT()
304
- return m.MarshalToSizedBufferVT(dAtA[:size])
305
-}
306
-
307
-func (m *ClientInitPayload) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
308
- if m == nil {
309
- return 0, nil
310
- }
311
- i := len(dAtA)
312
- _ = i
313
- var l int
314
- _ = l
315
- if m.unknownFields != nil {
316
- i -= len(m.unknownFields)
317
- copy(dAtA[i:], m.unknownFields)
318
- }
319
- if len(m.SessionPublicKey) > 0 {
320
- i -= len(m.SessionPublicKey)
321
- copy(dAtA[i:], m.SessionPublicKey)
322
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SessionPublicKey)))
323
- i--
324
- dAtA[i] = 0x32
325
- }
326
- if len(m.Alpn) > 0 {
327
- i -= len(m.Alpn)
328
- copy(dAtA[i:], m.Alpn)
329
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn)))
330
- i--
331
- dAtA[i] = 0x2a
332
- }
333
- if m.Identity != nil {
334
- size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
335
- if err != nil {
336
- return 0, err
337
- }
338
- i -= size
339
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
340
- i--
341
- dAtA[i] = 0x22
342
- }
343
- if m.Timestamp != 0 {
344
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
345
- i--
346
- dAtA[i] = 0x18
347
- }
348
- if len(m.Nonce) > 0 {
349
- i -= len(m.Nonce)
350
- copy(dAtA[i:], m.Nonce)
351
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
352
- i--
353
- dAtA[i] = 0x12
354
- }
355
- if m.Version != 0 {
356
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Version))
357
- i--
358
- dAtA[i] = 0x8
359
- }
360
- return len(dAtA) - i, nil
361
-}
362
-
363
-func (m *SignedPayload) MarshalVT() (dAtA []byte, err error) {
364
- if m == nil {
365
- return nil, nil
366
- }
367
- size := m.SizeVT()
368
- dAtA = make([]byte, size)
369
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
370
- if err != nil {
371
- return nil, err
372
- }
373
- return dAtA[:n], nil
374
-}
375
-
376
-func (m *SignedPayload) MarshalToVT(dAtA []byte) (int, error) {
377
- size := m.SizeVT()
378
- return m.MarshalToSizedBufferVT(dAtA[:size])
379
-}
380
-
381
-func (m *SignedPayload) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
382
- if m == nil {
383
- return 0, nil
384
- }
385
- i := len(dAtA)
386
- _ = i
387
- var l int
388
- _ = l
389
- if m.unknownFields != nil {
390
- i -= len(m.unknownFields)
391
- copy(dAtA[i:], m.unknownFields)
392
- }
393
- if len(m.Signature) > 0 {
394
- i -= len(m.Signature)
395
- copy(dAtA[i:], m.Signature)
396
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Signature)))
397
- i--
398
- dAtA[i] = 0x12
399
- }
400
- if len(m.Data) > 0 {
401
- i -= len(m.Data)
402
- copy(dAtA[i:], m.Data)
403
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Data)))
404
- i--
405
- dAtA[i] = 0xa
406
- }
407
- return len(dAtA) - i, nil
408
-}
409
-
410
-func (m *ServerInitPayload) MarshalVT() (dAtA []byte, err error) {
411
- if m == nil {
412
- return nil, nil
413
- }
414
- size := m.SizeVT()
415
- dAtA = make([]byte, size)
416
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
417
- if err != nil {
418
- return nil, err
419
- }
420
- return dAtA[:n], nil
421
-}
422
-
423
-func (m *ServerInitPayload) MarshalToVT(dAtA []byte) (int, error) {
424
- size := m.SizeVT()
425
- return m.MarshalToSizedBufferVT(dAtA[:size])
426
-}
427
-
428
-func (m *ServerInitPayload) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
429
- if m == nil {
430
- return 0, nil
431
- }
432
- i := len(dAtA)
433
- _ = i
434
- var l int
435
- _ = l
436
- if m.unknownFields != nil {
437
- i -= len(m.unknownFields)
438
- copy(dAtA[i:], m.unknownFields)
439
- }
440
- if len(m.SessionPublicKey) > 0 {
441
- i -= len(m.SessionPublicKey)
442
- copy(dAtA[i:], m.SessionPublicKey)
443
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SessionPublicKey)))
444
- i--
445
- dAtA[i] = 0x32
446
- }
447
- if len(m.Alpn) > 0 {
448
- i -= len(m.Alpn)
449
- copy(dAtA[i:], m.Alpn)
450
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn)))
451
- i--
452
- dAtA[i] = 0x2a
453
- }
454
- if m.Identity != nil {
455
- size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
456
- if err != nil {
457
- return 0, err
458
- }
459
- i -= size
460
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
461
- i--
462
- dAtA[i] = 0x22
463
- }
464
- if m.Timestamp != 0 {
465
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
466
- i--
467
- dAtA[i] = 0x18
468
- }
469
- if len(m.Nonce) > 0 {
470
- i -= len(m.Nonce)
471
- copy(dAtA[i:], m.Nonce)
472
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
473
- i--
474
- dAtA[i] = 0x12
475
- }
476
- if m.Version != 0 {
477
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Version))
478
- i--
479
- dAtA[i] = 0x8
480
- }
481
- return len(dAtA) - i, nil
482
-}
483
-
484
-func (m *Identity) MarshalVTStrict() (dAtA []byte, err error) {
485
- if m == nil {
486
- return nil, nil
487
- }
488
- size := m.SizeVT()
489
- dAtA = make([]byte, size)
490
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
491
- if err != nil {
492
- return nil, err
493
- }
494
- return dAtA[:n], nil
495
-}
496
-
497
-func (m *Identity) MarshalToVTStrict(dAtA []byte) (int, error) {
498
- size := m.SizeVT()
499
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
500
-}
501
-
502
-func (m *Identity) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
503
- if m == nil {
504
- return 0, nil
505
- }
506
- i := len(dAtA)
507
- _ = i
508
- var l int
509
- _ = l
510
- if m.unknownFields != nil {
511
- i -= len(m.unknownFields)
512
- copy(dAtA[i:], m.unknownFields)
513
- }
514
- if len(m.PublicKey) > 0 {
515
- i -= len(m.PublicKey)
516
- copy(dAtA[i:], m.PublicKey)
517
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.PublicKey)))
518
- i--
519
- dAtA[i] = 0x12
520
- }
521
- if len(m.Id) > 0 {
522
- i -= len(m.Id)
523
- copy(dAtA[i:], m.Id)
524
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id)))
525
- i--
526
- dAtA[i] = 0xa
527
- }
528
- return len(dAtA) - i, nil
529
-}
530
-
531
-func (m *ClientInitPayload) MarshalVTStrict() (dAtA []byte, err error) {
532
- if m == nil {
533
- return nil, nil
534
- }
535
- size := m.SizeVT()
536
- dAtA = make([]byte, size)
537
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
538
- if err != nil {
539
- return nil, err
540
- }
541
- return dAtA[:n], nil
542
-}
543
-
544
-func (m *ClientInitPayload) MarshalToVTStrict(dAtA []byte) (int, error) {
545
- size := m.SizeVT()
546
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
547
-}
548
-
549
-func (m *ClientInitPayload) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
550
- if m == nil {
551
- return 0, nil
552
- }
553
- i := len(dAtA)
554
- _ = i
555
- var l int
556
- _ = l
557
- if m.unknownFields != nil {
558
- i -= len(m.unknownFields)
559
- copy(dAtA[i:], m.unknownFields)
560
- }
561
- if len(m.SessionPublicKey) > 0 {
562
- i -= len(m.SessionPublicKey)
563
- copy(dAtA[i:], m.SessionPublicKey)
564
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SessionPublicKey)))
565
- i--
566
- dAtA[i] = 0x32
567
- }
568
- if len(m.Alpn) > 0 {
569
- i -= len(m.Alpn)
570
- copy(dAtA[i:], m.Alpn)
571
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn)))
572
- i--
573
- dAtA[i] = 0x2a
574
- }
575
- if m.Identity != nil {
576
- size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
577
- if err != nil {
578
- return 0, err
579
- }
580
- i -= size
581
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
582
- i--
583
- dAtA[i] = 0x22
584
- }
585
- if m.Timestamp != 0 {
586
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
587
- i--
588
- dAtA[i] = 0x18
589
- }
590
- if len(m.Nonce) > 0 {
591
- i -= len(m.Nonce)
592
- copy(dAtA[i:], m.Nonce)
593
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
594
- i--
595
- dAtA[i] = 0x12
596
- }
597
- if m.Version != 0 {
598
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Version))
599
- i--
600
- dAtA[i] = 0x8
601
- }
602
- return len(dAtA) - i, nil
603
-}
604
-
605
-func (m *SignedPayload) MarshalVTStrict() (dAtA []byte, err error) {
606
- if m == nil {
607
- return nil, nil
608
- }
609
- size := m.SizeVT()
610
- dAtA = make([]byte, size)
611
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
612
- if err != nil {
613
- return nil, err
614
- }
615
- return dAtA[:n], nil
616
-}
617
-
618
-func (m *SignedPayload) MarshalToVTStrict(dAtA []byte) (int, error) {
619
- size := m.SizeVT()
620
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
621
-}
622
-
623
-func (m *SignedPayload) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
624
- if m == nil {
625
- return 0, nil
626
- }
627
- i := len(dAtA)
628
- _ = i
629
- var l int
630
- _ = l
631
- if m.unknownFields != nil {
632
- i -= len(m.unknownFields)
633
- copy(dAtA[i:], m.unknownFields)
634
- }
635
- if len(m.Signature) > 0 {
636
- i -= len(m.Signature)
637
- copy(dAtA[i:], m.Signature)
638
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Signature)))
639
- i--
640
- dAtA[i] = 0x12
641
- }
642
- if len(m.Data) > 0 {
643
- i -= len(m.Data)
644
- copy(dAtA[i:], m.Data)
645
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Data)))
646
- i--
647
- dAtA[i] = 0xa
648
- }
649
- return len(dAtA) - i, nil
650
-}
651
-
652
-func (m *ServerInitPayload) MarshalVTStrict() (dAtA []byte, err error) {
653
- if m == nil {
654
- return nil, nil
655
- }
656
- size := m.SizeVT()
657
- dAtA = make([]byte, size)
658
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
659
- if err != nil {
660
- return nil, err
661
- }
662
- return dAtA[:n], nil
663
-}
664
-
665
-func (m *ServerInitPayload) MarshalToVTStrict(dAtA []byte) (int, error) {
666
- size := m.SizeVT()
667
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
668
-}
669
-
670
-func (m *ServerInitPayload) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
671
- if m == nil {
672
- return 0, nil
673
- }
674
- i := len(dAtA)
675
- _ = i
676
- var l int
677
- _ = l
678
- if m.unknownFields != nil {
679
- i -= len(m.unknownFields)
680
- copy(dAtA[i:], m.unknownFields)
681
- }
682
- if len(m.SessionPublicKey) > 0 {
683
- i -= len(m.SessionPublicKey)
684
- copy(dAtA[i:], m.SessionPublicKey)
685
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SessionPublicKey)))
686
- i--
687
- dAtA[i] = 0x32
688
- }
689
- if len(m.Alpn) > 0 {
690
- i -= len(m.Alpn)
691
- copy(dAtA[i:], m.Alpn)
692
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn)))
693
- i--
694
- dAtA[i] = 0x2a
695
- }
696
- if m.Identity != nil {
697
- size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
698
- if err != nil {
699
- return 0, err
700
- }
701
- i -= size
702
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
703
- i--
704
- dAtA[i] = 0x22
705
- }
706
- if m.Timestamp != 0 {
707
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
708
- i--
709
- dAtA[i] = 0x18
710
- }
711
- if len(m.Nonce) > 0 {
712
- i -= len(m.Nonce)
713
- copy(dAtA[i:], m.Nonce)
714
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
715
- i--
716
- dAtA[i] = 0x12
717
- }
718
- if m.Version != 0 {
719
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Version))
720
- i--
721
- dAtA[i] = 0x8
722
- }
723
- return len(dAtA) - i, nil
724
-}
725
-
726
-func (m *Identity) SizeVT() (n int) {
727
- if m == nil {
728
- return 0
729
- }
730
- var l int
731
- _ = l
732
- l = len(m.Id)
733
- if l > 0 {
734
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
735
- }
736
- l = len(m.PublicKey)
737
- if l > 0 {
738
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
739
- }
740
- n += len(m.unknownFields)
741
- return n
742
-}
743
-
744
-func (m *ClientInitPayload) SizeVT() (n int) {
745
- if m == nil {
746
- return 0
747
- }
748
- var l int
749
- _ = l
750
- if m.Version != 0 {
751
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Version))
752
- }
753
- l = len(m.Nonce)
754
- if l > 0 {
755
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
756
- }
757
- if m.Timestamp != 0 {
758
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp))
759
- }
760
- if m.Identity != nil {
761
- l = m.Identity.SizeVT()
762
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
763
- }
764
- l = len(m.Alpn)
765
- if l > 0 {
766
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
767
- }
768
- l = len(m.SessionPublicKey)
769
- if l > 0 {
770
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
771
- }
772
- n += len(m.unknownFields)
773
- return n
774
-}
775
-
776
-func (m *SignedPayload) SizeVT() (n int) {
777
- if m == nil {
778
- return 0
779
- }
780
- var l int
781
- _ = l
782
- l = len(m.Data)
783
- if l > 0 {
784
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
785
- }
786
- l = len(m.Signature)
787
- if l > 0 {
788
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
789
- }
790
- n += len(m.unknownFields)
791
- return n
792
-}
793
-
794
-func (m *ServerInitPayload) SizeVT() (n int) {
795
- if m == nil {
796
- return 0
797
- }
798
- var l int
799
- _ = l
800
- if m.Version != 0 {
801
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Version))
802
- }
803
- l = len(m.Nonce)
804
- if l > 0 {
805
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
806
- }
807
- if m.Timestamp != 0 {
808
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp))
809
- }
810
- if m.Identity != nil {
811
- l = m.Identity.SizeVT()
812
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
813
- }
814
- l = len(m.Alpn)
815
- if l > 0 {
816
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
817
- }
818
- l = len(m.SessionPublicKey)
819
- if l > 0 {
820
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
821
- }
822
- n += len(m.unknownFields)
823
- return n
824
-}
825
-
826
-func (m *Identity) UnmarshalVT(dAtA []byte) error {
827
- l := len(dAtA)
828
- iNdEx := 0
829
- for iNdEx < l {
830
- preIndex := iNdEx
831
- var wire uint64
832
- for shift := uint(0); ; shift += 7 {
833
- if shift >= 64 {
834
- return protohelpers.ErrIntOverflow
835
- }
836
- if iNdEx >= l {
837
- return io.ErrUnexpectedEOF
838
- }
839
- b := dAtA[iNdEx]
840
- iNdEx++
841
- wire |= uint64(b&0x7F) << shift
842
- if b < 0x80 {
843
- break
844
- }
845
- }
846
- fieldNum := int32(wire >> 3)
847
- wireType := int(wire & 0x7)
848
- if wireType == 4 {
849
- return fmt.Errorf("proto: Identity: wiretype end group for non-group")
850
- }
851
- if fieldNum <= 0 {
852
- return fmt.Errorf("proto: Identity: illegal tag %d (wire type %d)", fieldNum, wire)
853
- }
854
- switch fieldNum {
855
- case 1:
856
- if wireType != 2 {
857
- return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType)
858
- }
859
- var stringLen uint64
860
- for shift := uint(0); ; shift += 7 {
861
- if shift >= 64 {
862
- return protohelpers.ErrIntOverflow
863
- }
864
- if iNdEx >= l {
865
- return io.ErrUnexpectedEOF
866
- }
867
- b := dAtA[iNdEx]
868
- iNdEx++
869
- stringLen |= uint64(b&0x7F) << shift
870
- if b < 0x80 {
871
- break
872
- }
873
- }
874
- intStringLen := int(stringLen)
875
- if intStringLen < 0 {
876
- return protohelpers.ErrInvalidLength
877
- }
878
- postIndex := iNdEx + intStringLen
879
- if postIndex < 0 {
880
- return protohelpers.ErrInvalidLength
881
- }
882
- if postIndex > l {
883
- return io.ErrUnexpectedEOF
884
- }
885
- m.Id = string(dAtA[iNdEx:postIndex])
886
- iNdEx = postIndex
887
- case 2:
888
- if wireType != 2 {
889
- return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType)
890
- }
891
- var byteLen int
892
- for shift := uint(0); ; shift += 7 {
893
- if shift >= 64 {
894
- return protohelpers.ErrIntOverflow
895
- }
896
- if iNdEx >= l {
897
- return io.ErrUnexpectedEOF
898
- }
899
- b := dAtA[iNdEx]
900
- iNdEx++
901
- byteLen |= int(b&0x7F) << shift
902
- if b < 0x80 {
903
- break
904
- }
905
- }
906
- if byteLen < 0 {
907
- return protohelpers.ErrInvalidLength
908
- }
909
- postIndex := iNdEx + byteLen
910
- if postIndex < 0 {
911
- return protohelpers.ErrInvalidLength
912
- }
913
- if postIndex > l {
914
- return io.ErrUnexpectedEOF
915
- }
916
- m.PublicKey = append(m.PublicKey[:0], dAtA[iNdEx:postIndex]...)
917
- if m.PublicKey == nil {
918
- m.PublicKey = []byte{}
919
- }
920
- iNdEx = postIndex
921
- default:
922
- iNdEx = preIndex
923
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
924
- if err != nil {
925
- return err
926
- }
927
- if (skippy < 0) || (iNdEx+skippy) < 0 {
928
- return protohelpers.ErrInvalidLength
929
- }
930
- if (iNdEx + skippy) > l {
931
- return io.ErrUnexpectedEOF
932
- }
933
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
934
- iNdEx += skippy
935
- }
936
- }
937
-
938
- if iNdEx > l {
939
- return io.ErrUnexpectedEOF
940
- }
941
- return nil
942
-}
943
-func (m *ClientInitPayload) UnmarshalVT(dAtA []byte) error {
944
- l := len(dAtA)
945
- iNdEx := 0
946
- for iNdEx < l {
947
- preIndex := iNdEx
948
- var wire uint64
949
- for shift := uint(0); ; shift += 7 {
950
- if shift >= 64 {
951
- return protohelpers.ErrIntOverflow
952
- }
953
- if iNdEx >= l {
954
- return io.ErrUnexpectedEOF
955
- }
956
- b := dAtA[iNdEx]
957
- iNdEx++
958
- wire |= uint64(b&0x7F) << shift
959
- if b < 0x80 {
960
- break
961
- }
962
- }
963
- fieldNum := int32(wire >> 3)
964
- wireType := int(wire & 0x7)
965
- if wireType == 4 {
966
- return fmt.Errorf("proto: ClientInitPayload: wiretype end group for non-group")
967
- }
968
- if fieldNum <= 0 {
969
- return fmt.Errorf("proto: ClientInitPayload: illegal tag %d (wire type %d)", fieldNum, wire)
970
- }
971
- switch fieldNum {
972
- case 1:
973
- if wireType != 0 {
974
- return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
975
- }
976
- m.Version = 0
977
- for shift := uint(0); ; shift += 7 {
978
- if shift >= 64 {
979
- return protohelpers.ErrIntOverflow
980
- }
981
- if iNdEx >= l {
982
- return io.ErrUnexpectedEOF
983
- }
984
- b := dAtA[iNdEx]
985
- iNdEx++
986
- m.Version |= ProtocolVersion(b&0x7F) << shift
987
- if b < 0x80 {
988
- break
989
- }
990
- }
991
- case 2:
992
- if wireType != 2 {
993
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
994
- }
995
- var byteLen int
996
- for shift := uint(0); ; shift += 7 {
997
- if shift >= 64 {
998
- return protohelpers.ErrIntOverflow
999
- }
1000
- if iNdEx >= l {
1001
- return io.ErrUnexpectedEOF
1002
- }
1003
- b := dAtA[iNdEx]
1004
- iNdEx++
1005
- byteLen |= int(b&0x7F) << shift
1006
- if b < 0x80 {
1007
- break
1008
- }
1009
- }
1010
- if byteLen < 0 {
1011
- return protohelpers.ErrInvalidLength
1012
- }
1013
- postIndex := iNdEx + byteLen
1014
- if postIndex < 0 {
1015
- return protohelpers.ErrInvalidLength
1016
- }
1017
- if postIndex > l {
1018
- return io.ErrUnexpectedEOF
1019
- }
1020
- m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
1021
- if m.Nonce == nil {
1022
- m.Nonce = []byte{}
1023
- }
1024
- iNdEx = postIndex
1025
- case 3:
1026
- if wireType != 0 {
1027
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
1028
- }
1029
- m.Timestamp = 0
1030
- for shift := uint(0); ; shift += 7 {
1031
- if shift >= 64 {
1032
- return protohelpers.ErrIntOverflow
1033
- }
1034
- if iNdEx >= l {
1035
- return io.ErrUnexpectedEOF
1036
- }
1037
- b := dAtA[iNdEx]
1038
- iNdEx++
1039
- m.Timestamp |= int64(b&0x7F) << shift
1040
- if b < 0x80 {
1041
- break
1042
- }
1043
- }
1044
- case 4:
1045
- if wireType != 2 {
1046
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
1047
- }
1048
- var msglen int
1049
- for shift := uint(0); ; shift += 7 {
1050
- if shift >= 64 {
1051
- return protohelpers.ErrIntOverflow
1052
- }
1053
- if iNdEx >= l {
1054
- return io.ErrUnexpectedEOF
1055
- }
1056
- b := dAtA[iNdEx]
1057
- iNdEx++
1058
- msglen |= int(b&0x7F) << shift
1059
- if b < 0x80 {
1060
- break
1061
- }
1062
- }
1063
- if msglen < 0 {
1064
- return protohelpers.ErrInvalidLength
1065
- }
1066
- postIndex := iNdEx + msglen
1067
- if postIndex < 0 {
1068
- return protohelpers.ErrInvalidLength
1069
- }
1070
- if postIndex > l {
1071
- return io.ErrUnexpectedEOF
1072
- }
1073
- if m.Identity == nil {
1074
- m.Identity = &Identity{}
1075
- }
1076
- if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
1077
- return err
1078
- }
1079
- iNdEx = postIndex
1080
- case 5:
1081
- if wireType != 2 {
1082
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
1083
- }
1084
- var stringLen uint64
1085
- for shift := uint(0); ; shift += 7 {
1086
- if shift >= 64 {
1087
- return protohelpers.ErrIntOverflow
1088
- }
1089
- if iNdEx >= l {
1090
- return io.ErrUnexpectedEOF
1091
- }
1092
- b := dAtA[iNdEx]
1093
- iNdEx++
1094
- stringLen |= uint64(b&0x7F) << shift
1095
- if b < 0x80 {
1096
- break
1097
- }
1098
- }
1099
- intStringLen := int(stringLen)
1100
- if intStringLen < 0 {
1101
- return protohelpers.ErrInvalidLength
1102
- }
1103
- postIndex := iNdEx + intStringLen
1104
- if postIndex < 0 {
1105
- return protohelpers.ErrInvalidLength
1106
- }
1107
- if postIndex > l {
1108
- return io.ErrUnexpectedEOF
1109
- }
1110
- m.Alpn = string(dAtA[iNdEx:postIndex])
1111
- iNdEx = postIndex
1112
- case 6:
1113
- if wireType != 2 {
1114
- return fmt.Errorf("proto: wrong wireType = %d for field SessionPublicKey", wireType)
1115
- }
1116
- var byteLen int
1117
- for shift := uint(0); ; shift += 7 {
1118
- if shift >= 64 {
1119
- return protohelpers.ErrIntOverflow
1120
- }
1121
- if iNdEx >= l {
1122
- return io.ErrUnexpectedEOF
1123
- }
1124
- b := dAtA[iNdEx]
1125
- iNdEx++
1126
- byteLen |= int(b&0x7F) << shift
1127
- if b < 0x80 {
1128
- break
1129
- }
1130
- }
1131
- if byteLen < 0 {
1132
- return protohelpers.ErrInvalidLength
1133
- }
1134
- postIndex := iNdEx + byteLen
1135
- if postIndex < 0 {
1136
- return protohelpers.ErrInvalidLength
1137
- }
1138
- if postIndex > l {
1139
- return io.ErrUnexpectedEOF
1140
- }
1141
- m.SessionPublicKey = append(m.SessionPublicKey[:0], dAtA[iNdEx:postIndex]...)
1142
- if m.SessionPublicKey == nil {
1143
- m.SessionPublicKey = []byte{}
1144
- }
1145
- iNdEx = postIndex
1146
- default:
1147
- iNdEx = preIndex
1148
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1149
- if err != nil {
1150
- return err
1151
- }
1152
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1153
- return protohelpers.ErrInvalidLength
1154
- }
1155
- if (iNdEx + skippy) > l {
1156
- return io.ErrUnexpectedEOF
1157
- }
1158
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1159
- iNdEx += skippy
1160
- }
1161
- }
1162
-
1163
- if iNdEx > l {
1164
- return io.ErrUnexpectedEOF
1165
- }
1166
- return nil
1167
-}
1168
-func (m *SignedPayload) UnmarshalVT(dAtA []byte) error {
1169
- l := len(dAtA)
1170
- iNdEx := 0
1171
- for iNdEx < l {
1172
- preIndex := iNdEx
1173
- var wire uint64
1174
- for shift := uint(0); ; shift += 7 {
1175
- if shift >= 64 {
1176
- return protohelpers.ErrIntOverflow
1177
- }
1178
- if iNdEx >= l {
1179
- return io.ErrUnexpectedEOF
1180
- }
1181
- b := dAtA[iNdEx]
1182
- iNdEx++
1183
- wire |= uint64(b&0x7F) << shift
1184
- if b < 0x80 {
1185
- break
1186
- }
1187
- }
1188
- fieldNum := int32(wire >> 3)
1189
- wireType := int(wire & 0x7)
1190
- if wireType == 4 {
1191
- return fmt.Errorf("proto: SignedPayload: wiretype end group for non-group")
1192
- }
1193
- if fieldNum <= 0 {
1194
- return fmt.Errorf("proto: SignedPayload: illegal tag %d (wire type %d)", fieldNum, wire)
1195
- }
1196
- switch fieldNum {
1197
- case 1:
1198
- if wireType != 2 {
1199
- return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
1200
- }
1201
- var byteLen int
1202
- for shift := uint(0); ; shift += 7 {
1203
- if shift >= 64 {
1204
- return protohelpers.ErrIntOverflow
1205
- }
1206
- if iNdEx >= l {
1207
- return io.ErrUnexpectedEOF
1208
- }
1209
- b := dAtA[iNdEx]
1210
- iNdEx++
1211
- byteLen |= int(b&0x7F) << shift
1212
- if b < 0x80 {
1213
- break
1214
- }
1215
- }
1216
- if byteLen < 0 {
1217
- return protohelpers.ErrInvalidLength
1218
- }
1219
- postIndex := iNdEx + byteLen
1220
- if postIndex < 0 {
1221
- return protohelpers.ErrInvalidLength
1222
- }
1223
- if postIndex > l {
1224
- return io.ErrUnexpectedEOF
1225
- }
1226
- m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
1227
- if m.Data == nil {
1228
- m.Data = []byte{}
1229
- }
1230
- iNdEx = postIndex
1231
- case 2:
1232
- if wireType != 2 {
1233
- return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
1234
- }
1235
- var byteLen int
1236
- for shift := uint(0); ; shift += 7 {
1237
- if shift >= 64 {
1238
- return protohelpers.ErrIntOverflow
1239
- }
1240
- if iNdEx >= l {
1241
- return io.ErrUnexpectedEOF
1242
- }
1243
- b := dAtA[iNdEx]
1244
- iNdEx++
1245
- byteLen |= int(b&0x7F) << shift
1246
- if b < 0x80 {
1247
- break
1248
- }
1249
- }
1250
- if byteLen < 0 {
1251
- return protohelpers.ErrInvalidLength
1252
- }
1253
- postIndex := iNdEx + byteLen
1254
- if postIndex < 0 {
1255
- return protohelpers.ErrInvalidLength
1256
- }
1257
- if postIndex > l {
1258
- return io.ErrUnexpectedEOF
1259
- }
1260
- m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...)
1261
- if m.Signature == nil {
1262
- m.Signature = []byte{}
1263
- }
1264
- iNdEx = postIndex
1265
- default:
1266
- iNdEx = preIndex
1267
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1268
- if err != nil {
1269
- return err
1270
- }
1271
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1272
- return protohelpers.ErrInvalidLength
1273
- }
1274
- if (iNdEx + skippy) > l {
1275
- return io.ErrUnexpectedEOF
1276
- }
1277
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1278
- iNdEx += skippy
1279
- }
1280
- }
1281
-
1282
- if iNdEx > l {
1283
- return io.ErrUnexpectedEOF
1284
- }
1285
- return nil
1286
-}
1287
-func (m *ServerInitPayload) UnmarshalVT(dAtA []byte) error {
1288
- l := len(dAtA)
1289
- iNdEx := 0
1290
- for iNdEx < l {
1291
- preIndex := iNdEx
1292
- var wire uint64
1293
- for shift := uint(0); ; shift += 7 {
1294
- if shift >= 64 {
1295
- return protohelpers.ErrIntOverflow
1296
- }
1297
- if iNdEx >= l {
1298
- return io.ErrUnexpectedEOF
1299
- }
1300
- b := dAtA[iNdEx]
1301
- iNdEx++
1302
- wire |= uint64(b&0x7F) << shift
1303
- if b < 0x80 {
1304
- break
1305
- }
1306
- }
1307
- fieldNum := int32(wire >> 3)
1308
- wireType := int(wire & 0x7)
1309
- if wireType == 4 {
1310
- return fmt.Errorf("proto: ServerInitPayload: wiretype end group for non-group")
1311
- }
1312
- if fieldNum <= 0 {
1313
- return fmt.Errorf("proto: ServerInitPayload: illegal tag %d (wire type %d)", fieldNum, wire)
1314
- }
1315
- switch fieldNum {
1316
- case 1:
1317
- if wireType != 0 {
1318
- return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
1319
- }
1320
- m.Version = 0
1321
- for shift := uint(0); ; shift += 7 {
1322
- if shift >= 64 {
1323
- return protohelpers.ErrIntOverflow
1324
- }
1325
- if iNdEx >= l {
1326
- return io.ErrUnexpectedEOF
1327
- }
1328
- b := dAtA[iNdEx]
1329
- iNdEx++
1330
- m.Version |= ProtocolVersion(b&0x7F) << shift
1331
- if b < 0x80 {
1332
- break
1333
- }
1334
- }
1335
- case 2:
1336
- if wireType != 2 {
1337
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
1338
- }
1339
- var byteLen int
1340
- for shift := uint(0); ; shift += 7 {
1341
- if shift >= 64 {
1342
- return protohelpers.ErrIntOverflow
1343
- }
1344
- if iNdEx >= l {
1345
- return io.ErrUnexpectedEOF
1346
- }
1347
- b := dAtA[iNdEx]
1348
- iNdEx++
1349
- byteLen |= int(b&0x7F) << shift
1350
- if b < 0x80 {
1351
- break
1352
- }
1353
- }
1354
- if byteLen < 0 {
1355
- return protohelpers.ErrInvalidLength
1356
- }
1357
- postIndex := iNdEx + byteLen
1358
- if postIndex < 0 {
1359
- return protohelpers.ErrInvalidLength
1360
- }
1361
- if postIndex > l {
1362
- return io.ErrUnexpectedEOF
1363
- }
1364
- m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
1365
- if m.Nonce == nil {
1366
- m.Nonce = []byte{}
1367
- }
1368
- iNdEx = postIndex
1369
- case 3:
1370
- if wireType != 0 {
1371
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
1372
- }
1373
- m.Timestamp = 0
1374
- for shift := uint(0); ; shift += 7 {
1375
- if shift >= 64 {
1376
- return protohelpers.ErrIntOverflow
1377
- }
1378
- if iNdEx >= l {
1379
- return io.ErrUnexpectedEOF
1380
- }
1381
- b := dAtA[iNdEx]
1382
- iNdEx++
1383
- m.Timestamp |= int64(b&0x7F) << shift
1384
- if b < 0x80 {
1385
- break
1386
- }
1387
- }
1388
- case 4:
1389
- if wireType != 2 {
1390
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
1391
- }
1392
- var msglen int
1393
- for shift := uint(0); ; shift += 7 {
1394
- if shift >= 64 {
1395
- return protohelpers.ErrIntOverflow
1396
- }
1397
- if iNdEx >= l {
1398
- return io.ErrUnexpectedEOF
1399
- }
1400
- b := dAtA[iNdEx]
1401
- iNdEx++
1402
- msglen |= int(b&0x7F) << shift
1403
- if b < 0x80 {
1404
- break
1405
- }
1406
- }
1407
- if msglen < 0 {
1408
- return protohelpers.ErrInvalidLength
1409
- }
1410
- postIndex := iNdEx + msglen
1411
- if postIndex < 0 {
1412
- return protohelpers.ErrInvalidLength
1413
- }
1414
- if postIndex > l {
1415
- return io.ErrUnexpectedEOF
1416
- }
1417
- if m.Identity == nil {
1418
- m.Identity = &Identity{}
1419
- }
1420
- if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
1421
- return err
1422
- }
1423
- iNdEx = postIndex
1424
- case 5:
1425
- if wireType != 2 {
1426
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
1427
- }
1428
- var stringLen uint64
1429
- for shift := uint(0); ; shift += 7 {
1430
- if shift >= 64 {
1431
- return protohelpers.ErrIntOverflow
1432
- }
1433
- if iNdEx >= l {
1434
- return io.ErrUnexpectedEOF
1435
- }
1436
- b := dAtA[iNdEx]
1437
- iNdEx++
1438
- stringLen |= uint64(b&0x7F) << shift
1439
- if b < 0x80 {
1440
- break
1441
- }
1442
- }
1443
- intStringLen := int(stringLen)
1444
- if intStringLen < 0 {
1445
- return protohelpers.ErrInvalidLength
1446
- }
1447
- postIndex := iNdEx + intStringLen
1448
- if postIndex < 0 {
1449
- return protohelpers.ErrInvalidLength
1450
- }
1451
- if postIndex > l {
1452
- return io.ErrUnexpectedEOF
1453
- }
1454
- m.Alpn = string(dAtA[iNdEx:postIndex])
1455
- iNdEx = postIndex
1456
- case 6:
1457
- if wireType != 2 {
1458
- return fmt.Errorf("proto: wrong wireType = %d for field SessionPublicKey", wireType)
1459
- }
1460
- var byteLen int
1461
- for shift := uint(0); ; shift += 7 {
1462
- if shift >= 64 {
1463
- return protohelpers.ErrIntOverflow
1464
- }
1465
- if iNdEx >= l {
1466
- return io.ErrUnexpectedEOF
1467
- }
1468
- b := dAtA[iNdEx]
1469
- iNdEx++
1470
- byteLen |= int(b&0x7F) << shift
1471
- if b < 0x80 {
1472
- break
1473
- }
1474
- }
1475
- if byteLen < 0 {
1476
- return protohelpers.ErrInvalidLength
1477
- }
1478
- postIndex := iNdEx + byteLen
1479
- if postIndex < 0 {
1480
- return protohelpers.ErrInvalidLength
1481
- }
1482
- if postIndex > l {
1483
- return io.ErrUnexpectedEOF
1484
- }
1485
- m.SessionPublicKey = append(m.SessionPublicKey[:0], dAtA[iNdEx:postIndex]...)
1486
- if m.SessionPublicKey == nil {
1487
- m.SessionPublicKey = []byte{}
1488
- }
1489
- iNdEx = postIndex
1490
- default:
1491
- iNdEx = preIndex
1492
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1493
- if err != nil {
1494
- return err
1495
- }
1496
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1497
- return protohelpers.ErrInvalidLength
1498
- }
1499
- if (iNdEx + skippy) > l {
1500
- return io.ErrUnexpectedEOF
1501
- }
1502
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1503
- iNdEx += skippy
1504
- }
1505
- }
1506
-
1507
- if iNdEx > l {
1508
- return io.ErrUnexpectedEOF
1509
- }
1510
- return nil
1511
-}
1512
-func (m *Identity) UnmarshalVTUnsafe(dAtA []byte) error {
1513
- l := len(dAtA)
1514
- iNdEx := 0
1515
- for iNdEx < l {
1516
- preIndex := iNdEx
1517
- var wire uint64
1518
- for shift := uint(0); ; shift += 7 {
1519
- if shift >= 64 {
1520
- return protohelpers.ErrIntOverflow
1521
- }
1522
- if iNdEx >= l {
1523
- return io.ErrUnexpectedEOF
1524
- }
1525
- b := dAtA[iNdEx]
1526
- iNdEx++
1527
- wire |= uint64(b&0x7F) << shift
1528
- if b < 0x80 {
1529
- break
1530
- }
1531
- }
1532
- fieldNum := int32(wire >> 3)
1533
- wireType := int(wire & 0x7)
1534
- if wireType == 4 {
1535
- return fmt.Errorf("proto: Identity: wiretype end group for non-group")
1536
- }
1537
- if fieldNum <= 0 {
1538
- return fmt.Errorf("proto: Identity: illegal tag %d (wire type %d)", fieldNum, wire)
1539
- }
1540
- switch fieldNum {
1541
- case 1:
1542
- if wireType != 2 {
1543
- return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType)
1544
- }
1545
- var stringLen uint64
1546
- for shift := uint(0); ; shift += 7 {
1547
- if shift >= 64 {
1548
- return protohelpers.ErrIntOverflow
1549
- }
1550
- if iNdEx >= l {
1551
- return io.ErrUnexpectedEOF
1552
- }
1553
- b := dAtA[iNdEx]
1554
- iNdEx++
1555
- stringLen |= uint64(b&0x7F) << shift
1556
- if b < 0x80 {
1557
- break
1558
- }
1559
- }
1560
- intStringLen := int(stringLen)
1561
- if intStringLen < 0 {
1562
- return protohelpers.ErrInvalidLength
1563
- }
1564
- postIndex := iNdEx + intStringLen
1565
- if postIndex < 0 {
1566
- return protohelpers.ErrInvalidLength
1567
- }
1568
- if postIndex > l {
1569
- return io.ErrUnexpectedEOF
1570
- }
1571
- var stringValue string
1572
- if intStringLen > 0 {
1573
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
1574
- }
1575
- m.Id = stringValue
1576
- iNdEx = postIndex
1577
- case 2:
1578
- if wireType != 2 {
1579
- return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType)
1580
- }
1581
- var byteLen int
1582
- for shift := uint(0); ; shift += 7 {
1583
- if shift >= 64 {
1584
- return protohelpers.ErrIntOverflow
1585
- }
1586
- if iNdEx >= l {
1587
- return io.ErrUnexpectedEOF
1588
- }
1589
- b := dAtA[iNdEx]
1590
- iNdEx++
1591
- byteLen |= int(b&0x7F) << shift
1592
- if b < 0x80 {
1593
- break
1594
- }
1595
- }
1596
- if byteLen < 0 {
1597
- return protohelpers.ErrInvalidLength
1598
- }
1599
- postIndex := iNdEx + byteLen
1600
- if postIndex < 0 {
1601
- return protohelpers.ErrInvalidLength
1602
- }
1603
- if postIndex > l {
1604
- return io.ErrUnexpectedEOF
1605
- }
1606
- m.PublicKey = dAtA[iNdEx:postIndex]
1607
- iNdEx = postIndex
1608
- default:
1609
- iNdEx = preIndex
1610
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1611
- if err != nil {
1612
- return err
1613
- }
1614
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1615
- return protohelpers.ErrInvalidLength
1616
- }
1617
- if (iNdEx + skippy) > l {
1618
- return io.ErrUnexpectedEOF
1619
- }
1620
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1621
- iNdEx += skippy
1622
- }
1623
- }
1624
-
1625
- if iNdEx > l {
1626
- return io.ErrUnexpectedEOF
1627
- }
1628
- return nil
1629
-}
1630
-func (m *ClientInitPayload) UnmarshalVTUnsafe(dAtA []byte) error {
1631
- l := len(dAtA)
1632
- iNdEx := 0
1633
- for iNdEx < l {
1634
- preIndex := iNdEx
1635
- var wire uint64
1636
- for shift := uint(0); ; shift += 7 {
1637
- if shift >= 64 {
1638
- return protohelpers.ErrIntOverflow
1639
- }
1640
- if iNdEx >= l {
1641
- return io.ErrUnexpectedEOF
1642
- }
1643
- b := dAtA[iNdEx]
1644
- iNdEx++
1645
- wire |= uint64(b&0x7F) << shift
1646
- if b < 0x80 {
1647
- break
1648
- }
1649
- }
1650
- fieldNum := int32(wire >> 3)
1651
- wireType := int(wire & 0x7)
1652
- if wireType == 4 {
1653
- return fmt.Errorf("proto: ClientInitPayload: wiretype end group for non-group")
1654
- }
1655
- if fieldNum <= 0 {
1656
- return fmt.Errorf("proto: ClientInitPayload: illegal tag %d (wire type %d)", fieldNum, wire)
1657
- }
1658
- switch fieldNum {
1659
- case 1:
1660
- if wireType != 0 {
1661
- return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
1662
- }
1663
- m.Version = 0
1664
- for shift := uint(0); ; shift += 7 {
1665
- if shift >= 64 {
1666
- return protohelpers.ErrIntOverflow
1667
- }
1668
- if iNdEx >= l {
1669
- return io.ErrUnexpectedEOF
1670
- }
1671
- b := dAtA[iNdEx]
1672
- iNdEx++
1673
- m.Version |= ProtocolVersion(b&0x7F) << shift
1674
- if b < 0x80 {
1675
- break
1676
- }
1677
- }
1678
- case 2:
1679
- if wireType != 2 {
1680
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
1681
- }
1682
- var byteLen int
1683
- for shift := uint(0); ; shift += 7 {
1684
- if shift >= 64 {
1685
- return protohelpers.ErrIntOverflow
1686
- }
1687
- if iNdEx >= l {
1688
- return io.ErrUnexpectedEOF
1689
- }
1690
- b := dAtA[iNdEx]
1691
- iNdEx++
1692
- byteLen |= int(b&0x7F) << shift
1693
- if b < 0x80 {
1694
- break
1695
- }
1696
- }
1697
- if byteLen < 0 {
1698
- return protohelpers.ErrInvalidLength
1699
- }
1700
- postIndex := iNdEx + byteLen
1701
- if postIndex < 0 {
1702
- return protohelpers.ErrInvalidLength
1703
- }
1704
- if postIndex > l {
1705
- return io.ErrUnexpectedEOF
1706
- }
1707
- m.Nonce = dAtA[iNdEx:postIndex]
1708
- iNdEx = postIndex
1709
- case 3:
1710
- if wireType != 0 {
1711
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
1712
- }
1713
- m.Timestamp = 0
1714
- for shift := uint(0); ; shift += 7 {
1715
- if shift >= 64 {
1716
- return protohelpers.ErrIntOverflow
1717
- }
1718
- if iNdEx >= l {
1719
- return io.ErrUnexpectedEOF
1720
- }
1721
- b := dAtA[iNdEx]
1722
- iNdEx++
1723
- m.Timestamp |= int64(b&0x7F) << shift
1724
- if b < 0x80 {
1725
- break
1726
- }
1727
- }
1728
- case 4:
1729
- if wireType != 2 {
1730
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
1731
- }
1732
- var msglen int
1733
- for shift := uint(0); ; shift += 7 {
1734
- if shift >= 64 {
1735
- return protohelpers.ErrIntOverflow
1736
- }
1737
- if iNdEx >= l {
1738
- return io.ErrUnexpectedEOF
1739
- }
1740
- b := dAtA[iNdEx]
1741
- iNdEx++
1742
- msglen |= int(b&0x7F) << shift
1743
- if b < 0x80 {
1744
- break
1745
- }
1746
- }
1747
- if msglen < 0 {
1748
- return protohelpers.ErrInvalidLength
1749
- }
1750
- postIndex := iNdEx + msglen
1751
- if postIndex < 0 {
1752
- return protohelpers.ErrInvalidLength
1753
- }
1754
- if postIndex > l {
1755
- return io.ErrUnexpectedEOF
1756
- }
1757
- if m.Identity == nil {
1758
- m.Identity = &Identity{}
1759
- }
1760
- if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
1761
- return err
1762
- }
1763
- iNdEx = postIndex
1764
- case 5:
1765
- if wireType != 2 {
1766
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
1767
- }
1768
- var stringLen uint64
1769
- for shift := uint(0); ; shift += 7 {
1770
- if shift >= 64 {
1771
- return protohelpers.ErrIntOverflow
1772
- }
1773
- if iNdEx >= l {
1774
- return io.ErrUnexpectedEOF
1775
- }
1776
- b := dAtA[iNdEx]
1777
- iNdEx++
1778
- stringLen |= uint64(b&0x7F) << shift
1779
- if b < 0x80 {
1780
- break
1781
- }
1782
- }
1783
- intStringLen := int(stringLen)
1784
- if intStringLen < 0 {
1785
- return protohelpers.ErrInvalidLength
1786
- }
1787
- postIndex := iNdEx + intStringLen
1788
- if postIndex < 0 {
1789
- return protohelpers.ErrInvalidLength
1790
- }
1791
- if postIndex > l {
1792
- return io.ErrUnexpectedEOF
1793
- }
1794
- var stringValue string
1795
- if intStringLen > 0 {
1796
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
1797
- }
1798
- m.Alpn = stringValue
1799
- iNdEx = postIndex
1800
- case 6:
1801
- if wireType != 2 {
1802
- return fmt.Errorf("proto: wrong wireType = %d for field SessionPublicKey", wireType)
1803
- }
1804
- var byteLen int
1805
- for shift := uint(0); ; shift += 7 {
1806
- if shift >= 64 {
1807
- return protohelpers.ErrIntOverflow
1808
- }
1809
- if iNdEx >= l {
1810
- return io.ErrUnexpectedEOF
1811
- }
1812
- b := dAtA[iNdEx]
1813
- iNdEx++
1814
- byteLen |= int(b&0x7F) << shift
1815
- if b < 0x80 {
1816
- break
1817
- }
1818
- }
1819
- if byteLen < 0 {
1820
- return protohelpers.ErrInvalidLength
1821
- }
1822
- postIndex := iNdEx + byteLen
1823
- if postIndex < 0 {
1824
- return protohelpers.ErrInvalidLength
1825
- }
1826
- if postIndex > l {
1827
- return io.ErrUnexpectedEOF
1828
- }
1829
- m.SessionPublicKey = dAtA[iNdEx:postIndex]
1830
- iNdEx = postIndex
1831
- default:
1832
- iNdEx = preIndex
1833
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1834
- if err != nil {
1835
- return err
1836
- }
1837
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1838
- return protohelpers.ErrInvalidLength
1839
- }
1840
- if (iNdEx + skippy) > l {
1841
- return io.ErrUnexpectedEOF
1842
- }
1843
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1844
- iNdEx += skippy
1845
- }
1846
- }
1847
-
1848
- if iNdEx > l {
1849
- return io.ErrUnexpectedEOF
1850
- }
1851
- return nil
1852
-}
1853
-func (m *SignedPayload) UnmarshalVTUnsafe(dAtA []byte) error {
1854
- l := len(dAtA)
1855
- iNdEx := 0
1856
- for iNdEx < l {
1857
- preIndex := iNdEx
1858
- var wire uint64
1859
- for shift := uint(0); ; shift += 7 {
1860
- if shift >= 64 {
1861
- return protohelpers.ErrIntOverflow
1862
- }
1863
- if iNdEx >= l {
1864
- return io.ErrUnexpectedEOF
1865
- }
1866
- b := dAtA[iNdEx]
1867
- iNdEx++
1868
- wire |= uint64(b&0x7F) << shift
1869
- if b < 0x80 {
1870
- break
1871
- }
1872
- }
1873
- fieldNum := int32(wire >> 3)
1874
- wireType := int(wire & 0x7)
1875
- if wireType == 4 {
1876
- return fmt.Errorf("proto: SignedPayload: wiretype end group for non-group")
1877
- }
1878
- if fieldNum <= 0 {
1879
- return fmt.Errorf("proto: SignedPayload: illegal tag %d (wire type %d)", fieldNum, wire)
1880
- }
1881
- switch fieldNum {
1882
- case 1:
1883
- if wireType != 2 {
1884
- return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
1885
- }
1886
- var byteLen int
1887
- for shift := uint(0); ; shift += 7 {
1888
- if shift >= 64 {
1889
- return protohelpers.ErrIntOverflow
1890
- }
1891
- if iNdEx >= l {
1892
- return io.ErrUnexpectedEOF
1893
- }
1894
- b := dAtA[iNdEx]
1895
- iNdEx++
1896
- byteLen |= int(b&0x7F) << shift
1897
- if b < 0x80 {
1898
- break
1899
- }
1900
- }
1901
- if byteLen < 0 {
1902
- return protohelpers.ErrInvalidLength
1903
- }
1904
- postIndex := iNdEx + byteLen
1905
- if postIndex < 0 {
1906
- return protohelpers.ErrInvalidLength
1907
- }
1908
- if postIndex > l {
1909
- return io.ErrUnexpectedEOF
1910
- }
1911
- m.Data = dAtA[iNdEx:postIndex]
1912
- iNdEx = postIndex
1913
- case 2:
1914
- if wireType != 2 {
1915
- return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
1916
- }
1917
- var byteLen int
1918
- for shift := uint(0); ; shift += 7 {
1919
- if shift >= 64 {
1920
- return protohelpers.ErrIntOverflow
1921
- }
1922
- if iNdEx >= l {
1923
- return io.ErrUnexpectedEOF
1924
- }
1925
- b := dAtA[iNdEx]
1926
- iNdEx++
1927
- byteLen |= int(b&0x7F) << shift
1928
- if b < 0x80 {
1929
- break
1930
- }
1931
- }
1932
- if byteLen < 0 {
1933
- return protohelpers.ErrInvalidLength
1934
- }
1935
- postIndex := iNdEx + byteLen
1936
- if postIndex < 0 {
1937
- return protohelpers.ErrInvalidLength
1938
- }
1939
- if postIndex > l {
1940
- return io.ErrUnexpectedEOF
1941
- }
1942
- m.Signature = dAtA[iNdEx:postIndex]
1943
- iNdEx = postIndex
1944
- default:
1945
- iNdEx = preIndex
1946
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1947
- if err != nil {
1948
- return err
1949
- }
1950
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1951
- return protohelpers.ErrInvalidLength
1952
- }
1953
- if (iNdEx + skippy) > l {
1954
- return io.ErrUnexpectedEOF
1955
- }
1956
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1957
- iNdEx += skippy
1958
- }
1959
- }
1960
-
1961
- if iNdEx > l {
1962
- return io.ErrUnexpectedEOF
1963
- }
1964
- return nil
1965
-}
1966
-func (m *ServerInitPayload) UnmarshalVTUnsafe(dAtA []byte) error {
1967
- l := len(dAtA)
1968
- iNdEx := 0
1969
- for iNdEx < l {
1970
- preIndex := iNdEx
1971
- var wire uint64
1972
- for shift := uint(0); ; shift += 7 {
1973
- if shift >= 64 {
1974
- return protohelpers.ErrIntOverflow
1975
- }
1976
- if iNdEx >= l {
1977
- return io.ErrUnexpectedEOF
1978
- }
1979
- b := dAtA[iNdEx]
1980
- iNdEx++
1981
- wire |= uint64(b&0x7F) << shift
1982
- if b < 0x80 {
1983
- break
1984
- }
1985
- }
1986
- fieldNum := int32(wire >> 3)
1987
- wireType := int(wire & 0x7)
1988
- if wireType == 4 {
1989
- return fmt.Errorf("proto: ServerInitPayload: wiretype end group for non-group")
1990
- }
1991
- if fieldNum <= 0 {
1992
- return fmt.Errorf("proto: ServerInitPayload: illegal tag %d (wire type %d)", fieldNum, wire)
1993
- }
1994
- switch fieldNum {
1995
- case 1:
1996
- if wireType != 0 {
1997
- return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
1998
- }
1999
- m.Version = 0
2000
- for shift := uint(0); ; shift += 7 {
2001
- if shift >= 64 {
2002
- return protohelpers.ErrIntOverflow
2003
- }
2004
- if iNdEx >= l {
2005
- return io.ErrUnexpectedEOF
2006
- }
2007
- b := dAtA[iNdEx]
2008
- iNdEx++
2009
- m.Version |= ProtocolVersion(b&0x7F) << shift
2010
- if b < 0x80 {
2011
- break
2012
- }
2013
- }
2014
- case 2:
2015
- if wireType != 2 {
2016
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
2017
- }
2018
- var byteLen int
2019
- for shift := uint(0); ; shift += 7 {
2020
- if shift >= 64 {
2021
- return protohelpers.ErrIntOverflow
2022
- }
2023
- if iNdEx >= l {
2024
- return io.ErrUnexpectedEOF
2025
- }
2026
- b := dAtA[iNdEx]
2027
- iNdEx++
2028
- byteLen |= int(b&0x7F) << shift
2029
- if b < 0x80 {
2030
- break
2031
- }
2032
- }
2033
- if byteLen < 0 {
2034
- return protohelpers.ErrInvalidLength
2035
- }
2036
- postIndex := iNdEx + byteLen
2037
- if postIndex < 0 {
2038
- return protohelpers.ErrInvalidLength
2039
- }
2040
- if postIndex > l {
2041
- return io.ErrUnexpectedEOF
2042
- }
2043
- m.Nonce = dAtA[iNdEx:postIndex]
2044
- iNdEx = postIndex
2045
- case 3:
2046
- if wireType != 0 {
2047
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
2048
- }
2049
- m.Timestamp = 0
2050
- for shift := uint(0); ; shift += 7 {
2051
- if shift >= 64 {
2052
- return protohelpers.ErrIntOverflow
2053
- }
2054
- if iNdEx >= l {
2055
- return io.ErrUnexpectedEOF
2056
- }
2057
- b := dAtA[iNdEx]
2058
- iNdEx++
2059
- m.Timestamp |= int64(b&0x7F) << shift
2060
- if b < 0x80 {
2061
- break
2062
- }
2063
- }
2064
- case 4:
2065
- if wireType != 2 {
2066
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
2067
- }
2068
- var msglen int
2069
- for shift := uint(0); ; shift += 7 {
2070
- if shift >= 64 {
2071
- return protohelpers.ErrIntOverflow
2072
- }
2073
- if iNdEx >= l {
2074
- return io.ErrUnexpectedEOF
2075
- }
2076
- b := dAtA[iNdEx]
2077
- iNdEx++
2078
- msglen |= int(b&0x7F) << shift
2079
- if b < 0x80 {
2080
- break
2081
- }
2082
- }
2083
- if msglen < 0 {
2084
- return protohelpers.ErrInvalidLength
2085
- }
2086
- postIndex := iNdEx + msglen
2087
- if postIndex < 0 {
2088
- return protohelpers.ErrInvalidLength
2089
- }
2090
- if postIndex > l {
2091
- return io.ErrUnexpectedEOF
2092
- }
2093
- if m.Identity == nil {
2094
- m.Identity = &Identity{}
2095
- }
2096
- if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
2097
- return err
2098
- }
2099
- iNdEx = postIndex
2100
- case 5:
2101
- if wireType != 2 {
2102
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
2103
- }
2104
- var stringLen uint64
2105
- for shift := uint(0); ; shift += 7 {
2106
- if shift >= 64 {
2107
- return protohelpers.ErrIntOverflow
2108
- }
2109
- if iNdEx >= l {
2110
- return io.ErrUnexpectedEOF
2111
- }
2112
- b := dAtA[iNdEx]
2113
- iNdEx++
2114
- stringLen |= uint64(b&0x7F) << shift
2115
- if b < 0x80 {
2116
- break
2117
- }
2118
- }
2119
- intStringLen := int(stringLen)
2120
- if intStringLen < 0 {
2121
- return protohelpers.ErrInvalidLength
2122
- }
2123
- postIndex := iNdEx + intStringLen
2124
- if postIndex < 0 {
2125
- return protohelpers.ErrInvalidLength
2126
- }
2127
- if postIndex > l {
2128
- return io.ErrUnexpectedEOF
2129
- }
2130
- var stringValue string
2131
- if intStringLen > 0 {
2132
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
2133
- }
2134
- m.Alpn = stringValue
2135
- iNdEx = postIndex
2136
- case 6:
2137
- if wireType != 2 {
2138
- return fmt.Errorf("proto: wrong wireType = %d for field SessionPublicKey", wireType)
2139
- }
2140
- var byteLen int
2141
- for shift := uint(0); ; shift += 7 {
2142
- if shift >= 64 {
2143
- return protohelpers.ErrIntOverflow
2144
- }
2145
- if iNdEx >= l {
2146
- return io.ErrUnexpectedEOF
2147
- }
2148
- b := dAtA[iNdEx]
2149
- iNdEx++
2150
- byteLen |= int(b&0x7F) << shift
2151
- if b < 0x80 {
2152
- break
2153
- }
2154
- }
2155
- if byteLen < 0 {
2156
- return protohelpers.ErrInvalidLength
2157
- }
2158
- postIndex := iNdEx + byteLen
2159
- if postIndex < 0 {
2160
- return protohelpers.ErrInvalidLength
2161
- }
2162
- if postIndex > l {
2163
- return io.ErrUnexpectedEOF
2164
- }
2165
- m.SessionPublicKey = dAtA[iNdEx:postIndex]
2166
- iNdEx = postIndex
2167
- default:
2168
- iNdEx = preIndex
2169
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2170
- if err != nil {
2171
- return err
2172
- }
2173
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2174
- return protohelpers.ErrInvalidLength
2175
- }
2176
- if (iNdEx + skippy) > l {
2177
- return io.ErrUnexpectedEOF
2178
- }
2179
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2180
- iNdEx += skippy
2181
- }
2182
- }
2183
-
2184
- if iNdEx > l {
2185
- return io.ErrUnexpectedEOF
2186
- }
2187
- return nil
2188
-}
portal/core/proto/rdverb/rdverb.pb.go
deleted
-397
@@ -1,397 +0,0 @@
1
-// Code generated by protoc-gen-go. DO NOT EDIT.
2
-// versions:
3
-// protoc-gen-go v1.36.11
4
-// protoc v3.21.12
5
-// source: portal/core/proto/rdverb/rdverb.proto
6
-// Reflection-free version for TinyGo compatibility
7
-
8
-package rdverb
9
-
10
-import (
11
- rdsec "gosuda.org/portal/portal/core/proto/rdsec"
12
-)
13
-
14
-type PacketType int32
15
-
16
-const (
17
- PacketType_PACKET_TYPE_RELAY_INFO_REQUEST PacketType = 0
18
- PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE PacketType = 1
19
- PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST PacketType = 2
20
- PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE PacketType = 3
21
- PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST PacketType = 4
22
- PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE PacketType = 5
23
- PacketType_PACKET_TYPE_CONNECTION_REQUEST PacketType = 6
24
- PacketType_PACKET_TYPE_CONNECTION_RESPONSE PacketType = 7
25
-)
26
-
27
-// Enum value maps for PacketType.
28
-var (
29
- PacketType_name = map[int32]string{
30
- 0: "PACKET_TYPE_RELAY_INFO_REQUEST",
31
- 1: "PACKET_TYPE_RELAY_INFO_RESPONSE",
32
- 2: "PACKET_TYPE_LEASE_UPDATE_REQUEST",
33
- 3: "PACKET_TYPE_LEASE_UPDATE_RESPONSE",
34
- 4: "PACKET_TYPE_LEASE_DELETE_REQUEST",
35
- 5: "PACKET_TYPE_LEASE_DELETE_RESPONSE",
36
- 6: "PACKET_TYPE_CONNECTION_REQUEST",
37
- 7: "PACKET_TYPE_CONNECTION_RESPONSE",
38
- }
39
- PacketType_value = map[string]int32{
40
- "PACKET_TYPE_RELAY_INFO_REQUEST": 0,
41
- "PACKET_TYPE_RELAY_INFO_RESPONSE": 1,
42
- "PACKET_TYPE_LEASE_UPDATE_REQUEST": 2,
43
- "PACKET_TYPE_LEASE_UPDATE_RESPONSE": 3,
44
- "PACKET_TYPE_LEASE_DELETE_REQUEST": 4,
45
- "PACKET_TYPE_LEASE_DELETE_RESPONSE": 5,
46
- "PACKET_TYPE_CONNECTION_REQUEST": 6,
47
- "PACKET_TYPE_CONNECTION_RESPONSE": 7,
48
- }
49
-)
50
-
51
-func (x PacketType) Enum() *PacketType {
52
- p := new(PacketType)
53
- *p = x
54
- return p
55
-}
56
-
57
-func (x PacketType) String() string {
58
- s, ok := PacketType_name[int32(x)]
59
- if !ok {
60
- return "PacketType(" + string(rune('0'+x)) + ")"
61
- }
62
- return s
63
-}
64
-
65
-type ResponseCode int32
66
-
67
-const (
68
- ResponseCode_RESPONSE_CODE_UNKNOWN ResponseCode = 0
69
- ResponseCode_RESPONSE_CODE_ACCEPTED ResponseCode = 1
70
- ResponseCode_RESPONSE_CODE_INVALID_EXPIRES ResponseCode = 2
71
- ResponseCode_RESPONSE_CODE_INVALID_IDENTITY ResponseCode = 3
72
- ResponseCode_RESPONSE_CODE_INVALID_NAME ResponseCode = 4
73
- ResponseCode_RESPONSE_CODE_INVALID_ALPN ResponseCode = 5
74
- ResponseCode_RESPONSE_CODE_REJECTED ResponseCode = 6
75
-)
76
-
77
-// Enum value maps for ResponseCode.
78
-var (
79
- ResponseCode_name = map[int32]string{
80
- 0: "RESPONSE_CODE_UNKNOWN",
81
- 1: "RESPONSE_CODE_ACCEPTED",
82
- 2: "RESPONSE_CODE_INVALID_EXPIRES",
83
- 3: "RESPONSE_CODE_INVALID_IDENTITY",
84
- 4: "RESPONSE_CODE_INVALID_NAME",
85
- 5: "RESPONSE_CODE_INVALID_ALPN",
86
- 6: "RESPONSE_CODE_REJECTED",
87
- }
88
- ResponseCode_value = map[string]int32{
89
- "RESPONSE_CODE_UNKNOWN": 0,
90
- "RESPONSE_CODE_ACCEPTED": 1,
91
- "RESPONSE_CODE_INVALID_EXPIRES": 2,
92
- "RESPONSE_CODE_INVALID_IDENTITY": 3,
93
- "RESPONSE_CODE_INVALID_NAME": 4,
94
- "RESPONSE_CODE_INVALID_ALPN": 5,
95
- "RESPONSE_CODE_REJECTED": 6,
96
- }
97
-)
98
-
99
-func (x ResponseCode) Enum() *ResponseCode {
100
- p := new(ResponseCode)
101
- *p = x
102
- return p
103
-}
104
-
105
-func (x ResponseCode) String() string {
106
- s, ok := ResponseCode_name[int32(x)]
107
- if !ok {
108
- return "ResponseCode(" + string(rune('0'+x)) + ")"
109
- }
110
- return s
111
-}
112
-
113
-type Packet struct {
114
- Type PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=rdverb.PacketType" json:"type,omitempty"`
115
- Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
116
- unknownFields []byte
117
-}
118
-
119
-func (x *Packet) Reset() {
120
- *x = Packet{}
121
-}
122
-
123
-func (x *Packet) ProtoMessage() {} // Stub for vtproto compatibility
124
-
125
-func (x *Packet) GetType() PacketType {
126
- if x != nil {
127
- return x.Type
128
- }
129
- return PacketType_PACKET_TYPE_RELAY_INFO_REQUEST
130
-}
131
-
132
-func (x *Packet) GetPayload() []byte {
133
- if x != nil {
134
- return x.Payload
135
- }
136
- return nil
137
-}
138
-
139
-type RelayInfo struct {
140
- Identity *rdsec.Identity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
141
- Address []string `protobuf:"bytes,2,rep,name=address,proto3" json:"address,omitempty"`
142
- Leases []*Lease `protobuf:"bytes,3,rep,name=leases,proto3" json:"leases,omitempty"`
143
- unknownFields []byte
144
-}
145
-
146
-func (x *RelayInfo) Reset() {
147
- *x = RelayInfo{}
148
-}
149
-
150
-func (x *RelayInfo) ProtoMessage() {} // Stub for vtproto compatibility
151
-
152
-func (x *RelayInfo) GetIdentity() *rdsec.Identity {
153
- if x != nil {
154
- return x.Identity
155
- }
156
- return nil
157
-}
158
-
159
-func (x *RelayInfo) GetAddress() []string {
160
- if x != nil {
161
- return x.Address
162
- }
163
- return nil
164
-}
165
-
166
-func (x *RelayInfo) GetLeases() []*Lease {
167
- if x != nil {
168
- return x.Leases
169
- }
170
- return nil
171
-}
172
-
173
-type RelayInfoRequest struct {
174
- unknownFields []byte
175
-}
176
-
177
-func (x *RelayInfoRequest) Reset() {
178
- *x = RelayInfoRequest{}
179
-}
180
-
181
-func (x *RelayInfoRequest) ProtoMessage() {} // Stub for vtproto compatibility
182
-
183
-type RelayInfoResponse struct {
184
- RelayInfo *RelayInfo `protobuf:"bytes,1,opt,name=relay_info,json=relayInfo,proto3" json:"relay_info,omitempty"`
185
- unknownFields []byte
186
-}
187
-
188
-func (x *RelayInfoResponse) Reset() {
189
- *x = RelayInfoResponse{}
190
-}
191
-
192
-func (x *RelayInfoResponse) ProtoMessage() {} // Stub for vtproto compatibility
193
-
194
-func (x *RelayInfoResponse) GetRelayInfo() *RelayInfo {
195
- if x != nil {
196
- return x.RelayInfo
197
- }
198
- return nil
199
-}
200
-
201
-type Lease struct {
202
- Identity *rdsec.Identity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
203
- Expires int64 `protobuf:"varint,2,opt,name=expires,proto3" json:"expires,omitempty"`
204
- Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
205
- Alpn []string `protobuf:"bytes,4,rep,name=alpn,proto3" json:"alpn,omitempty"`
206
- Metadata string `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"`
207
- unknownFields []byte
208
-}
209
-
210
-func (x *Lease) Reset() {
211
- *x = Lease{}
212
-}
213
-
214
-func (x *Lease) ProtoMessage() {} // Stub for vtproto compatibility
215
-
216
-func (x *Lease) GetIdentity() *rdsec.Identity {
217
- if x != nil {
218
- return x.Identity
219
- }
220
- return nil
221
-}
222
-
223
-func (x *Lease) GetExpires() int64 {
224
- if x != nil {
225
- return x.Expires
226
- }
227
- return 0
228
-}
229
-
230
-func (x *Lease) GetName() string {
231
- if x != nil {
232
- return x.Name
233
- }
234
- return ""
235
-}
236
-
237
-func (x *Lease) GetAlpn() []string {
238
- if x != nil {
239
- return x.Alpn
240
- }
241
- return nil
242
-}
243
-
244
-func (x *Lease) GetMetadata() string {
245
- if x != nil {
246
- return x.Metadata
247
- }
248
- return ""
249
-}
250
-
251
-type LeaseUpdateRequest struct {
252
- Lease *Lease `protobuf:"bytes,1,opt,name=lease,proto3" json:"lease,omitempty"`
253
- Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
254
- Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
255
- unknownFields []byte
256
-}
257
-
258
-func (x *LeaseUpdateRequest) Reset() {
259
- *x = LeaseUpdateRequest{}
260
-}
261
-
262
-func (x *LeaseUpdateRequest) ProtoMessage() {} // Stub for vtproto compatibility
263
-
264
-func (x *LeaseUpdateRequest) GetLease() *Lease {
265
- if x != nil {
266
- return x.Lease
267
- }
268
- return nil
269
-}
270
-
271
-func (x *LeaseUpdateRequest) GetNonce() []byte {
272
- if x != nil {
273
- return x.Nonce
274
- }
275
- return nil
276
-}
277
-
278
-func (x *LeaseUpdateRequest) GetTimestamp() int64 {
279
- if x != nil {
280
- return x.Timestamp
281
- }
282
- return 0
283
-}
284
-
285
-type LeaseUpdateResponse struct {
286
- Code ResponseCode `protobuf:"varint,1,opt,name=code,proto3,enum=rdverb.ResponseCode" json:"code,omitempty"`
287
- unknownFields []byte
288
-}
289
-
290
-func (x *LeaseUpdateResponse) Reset() {
291
- *x = LeaseUpdateResponse{}
292
-}
293
-
294
-func (x *LeaseUpdateResponse) ProtoMessage() {} // Stub for vtproto compatibility
295
-
296
-func (x *LeaseUpdateResponse) GetCode() ResponseCode {
297
- if x != nil {
298
- return x.Code
299
- }
300
- return ResponseCode_RESPONSE_CODE_UNKNOWN
301
-}
302
-
303
-type LeaseDeleteRequest struct {
304
- Identity *rdsec.Identity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
305
- Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
306
- Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
307
- unknownFields []byte
308
-}
309
-
310
-func (x *LeaseDeleteRequest) Reset() {
311
- *x = LeaseDeleteRequest{}
312
-}
313
-
314
-func (x *LeaseDeleteRequest) ProtoMessage() {} // Stub for vtproto compatibility
315
-
316
-func (x *LeaseDeleteRequest) GetIdentity() *rdsec.Identity {
317
- if x != nil {
318
- return x.Identity
319
- }
320
- return nil
321
-}
322
-
323
-func (x *LeaseDeleteRequest) GetNonce() []byte {
324
- if x != nil {
325
- return x.Nonce
326
- }
327
- return nil
328
-}
329
-
330
-func (x *LeaseDeleteRequest) GetTimestamp() int64 {
331
- if x != nil {
332
- return x.Timestamp
333
- }
334
- return 0
335
-}
336
-
337
-type LeaseDeleteResponse struct {
338
- Code ResponseCode `protobuf:"varint,1,opt,name=code,proto3,enum=rdverb.ResponseCode" json:"code,omitempty"`
339
- unknownFields []byte
340
-}
341
-
342
-func (x *LeaseDeleteResponse) Reset() {
343
- *x = LeaseDeleteResponse{}
344
-}
345
-
346
-func (x *LeaseDeleteResponse) ProtoMessage() {} // Stub for vtproto compatibility
347
-
348
-func (x *LeaseDeleteResponse) GetCode() ResponseCode {
349
- if x != nil {
350
- return x.Code
351
- }
352
- return ResponseCode_RESPONSE_CODE_UNKNOWN
353
-}
354
-
355
-type ConnectionRequest struct {
356
- LeaseId string `protobuf:"bytes,1,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"`
357
- ClientIdentity *rdsec.Identity `protobuf:"bytes,2,opt,name=client_identity,json=clientIdentity,proto3" json:"client_identity,omitempty"`
358
- unknownFields []byte
359
-}
360
-
361
-func (x *ConnectionRequest) Reset() {
362
- *x = ConnectionRequest{}
363
-}
364
-
365
-func (x *ConnectionRequest) ProtoMessage() {} // Stub for vtproto compatibility
366
-
367
-func (x *ConnectionRequest) GetLeaseId() string {
368
- if x != nil {
369
- return x.LeaseId
370
- }
371
- return ""
372
-}
373
-
374
-func (x *ConnectionRequest) GetClientIdentity() *rdsec.Identity {
375
- if x != nil {
376
- return x.ClientIdentity
377
- }
378
- return nil
379
-}
380
-
381
-type ConnectionResponse struct {
382
- Code ResponseCode `protobuf:"varint,1,opt,name=code,proto3,enum=rdverb.ResponseCode" json:"code,omitempty"`
383
- unknownFields []byte
384
-}
385
-
386
-func (x *ConnectionResponse) Reset() {
387
- *x = ConnectionResponse{}
388
-}
389
-
390
-func (x *ConnectionResponse) ProtoMessage() {} // Stub for vtproto compatibility
391
-
392
-func (x *ConnectionResponse) GetCode() ResponseCode {
393
- if x != nil {
394
- return x.Code
395
- }
396
- return ResponseCode_RESPONSE_CODE_UNKNOWN
397
-}
portal/core/proto/rdverb/rdverb.proto
deleted
-87
@@ -1,87 +0,0 @@
1
-syntax = "proto3";
2
-
3
-package rdverb;
4
-
5
-import "portal/core/proto/rdsec/rdsec.proto";
6
-
7
-option go_package = "gosuda.org/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 Lease 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
- string metadata = 5;
58
-}
59
-
60
-message LeaseUpdateRequest {
61
- Lease lease = 1;
62
- bytes nonce = 2;
63
- int64 timestamp = 3;
64
-}
65
-
66
-message LeaseUpdateResponse {
67
- ResponseCode code = 1;
68
-}
69
-
70
-message LeaseDeleteRequest {
71
- rdsec.Identity identity = 1;
72
- bytes nonce = 2;
73
- int64 timestamp = 3;
74
-}
75
-
76
-message LeaseDeleteResponse {
77
- ResponseCode code = 1;
78
-}
79
-
80
-message ConnectionRequest {
81
- string lease_id = 1;
82
- rdsec.Identity client_identity = 2;
83
-}
84
-
85
-message ConnectionResponse {
86
- ResponseCode code = 1;
87
-}
portal/core/proto/rdverb/rdverb_test.go
deleted
-1300
@@ -1,1300 +0,0 @@
1
-package rdverb
2
-
3
-import (
4
- "bytes"
5
- "testing"
6
-
7
- rdsec "gosuda.org/portal/portal/core/proto/rdsec"
8
-)
9
-
10
-// TestPacket_MarshalVT_UnmarshalVT tests round-trip serialization for Packet
11
-func TestPacket_MarshalVT_UnmarshalVT(t *testing.T) {
12
- tests := []struct {
13
- name string
14
- input *Packet
15
- wantErr bool
16
- }{
17
- {
18
- name: "empty",
19
- input: &Packet{},
20
- wantErr: false,
21
- },
22
- {
23
- name: "full",
24
- input: &Packet{
25
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
26
- Payload: []byte{0x01, 0x02, 0x03, 0x04},
27
- },
28
- wantErr: false,
29
- },
30
- {
31
- name: "type only",
32
- input: &Packet{
33
- Type: PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST,
34
- },
35
- wantErr: false,
36
- },
37
- {
38
- name: "payload only",
39
- input: &Packet{
40
- Payload: []byte("test payload"),
41
- },
42
- wantErr: false,
43
- },
44
- }
45
-
46
- for _, tt := range tests {
47
- t.Run(tt.name, func(t *testing.T) {
48
- data, err := tt.input.MarshalVT()
49
- if (err != nil) != tt.wantErr {
50
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
51
- return
52
- }
53
-
54
- got := &Packet{}
55
- err = got.UnmarshalVT(data)
56
- if (err != nil) != tt.wantErr {
57
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
58
- return
59
- }
60
-
61
- if !tt.input.EqualVT(got) {
62
- t.Errorf("roundtrip mismatch")
63
- }
64
- })
65
- }
66
-}
67
-
68
-// TestPacket_AllPacketTypes tests serialization of all packet types
69
-func TestPacket_AllPacketTypes(t *testing.T) {
70
- packetTypes := []PacketType{
71
- PacketType_PACKET_TYPE_RELAY_INFO_REQUEST,
72
- PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE,
73
- PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST,
74
- PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE,
75
- PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST,
76
- PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE,
77
- PacketType_PACKET_TYPE_CONNECTION_REQUEST,
78
- PacketType_PACKET_TYPE_CONNECTION_RESPONSE,
79
- }
80
-
81
- for _, pt := range packetTypes {
82
- t.Run(pt.String(), func(t *testing.T) {
83
- msg := &Packet{
84
- Type: pt,
85
- Payload: []byte("test payload"),
86
- }
87
-
88
- data, err := msg.MarshalVT()
89
- if err != nil {
90
- t.Fatalf("MarshalVT() error = %v", err)
91
- }
92
-
93
- got := &Packet{}
94
- if err := got.UnmarshalVT(data); err != nil {
95
- t.Fatalf("UnmarshalVT() error = %v", err)
96
- }
97
-
98
- if !msg.EqualVT(got) {
99
- t.Errorf("roundtrip mismatch for %v", pt)
100
- }
101
- })
102
- }
103
-}
104
-
105
-// TestRelayInfo_MarshalVT_UnmarshalVT tests round-trip serialization
106
-func TestRelayInfo_MarshalVT_UnmarshalVT(t *testing.T) {
107
- tests := []struct {
108
- name string
109
- input *RelayInfo
110
- wantErr bool
111
- }{
112
- {
113
- name: "empty",
114
- input: &RelayInfo{},
115
- wantErr: false,
116
- },
117
- {
118
- name: "full",
119
- input: &RelayInfo{
120
- Identity: &rdsec.Identity{
121
- Id: "relay-id",
122
- PublicKey: []byte{0x01, 0x02},
123
- },
124
- Address: []string{"addr1.example.com:8080", "addr2.example.com:8080"},
125
- Leases: []*Lease{
126
- {
127
- Identity: &rdsec.Identity{Id: "lease1-id"},
128
- Expires: 1234567890,
129
- Name: "lease1",
130
- Alpn: []string{"h2", "http/1.1"},
131
- Metadata: "metadata1",
132
- },
133
- {
134
- Identity: &rdsec.Identity{Id: "lease2-id"},
135
- Expires: 9876543210,
136
- Name: "lease2",
137
- Alpn: []string{"h2"},
138
- },
139
- },
140
- },
141
- wantErr: false,
142
- },
143
- {
144
- name: "with identity only",
145
- input: &RelayInfo{
146
- Identity: &rdsec.Identity{Id: "test-relay"},
147
- },
148
- wantErr: false,
149
- },
150
- {
151
- name: "with multiple addresses",
152
- input: &RelayInfo{
153
- Identity: &rdsec.Identity{Id: "multi-addr"},
154
- Address: []string{"addr1:8080", "addr2:8080", "addr3:8080"},
155
- },
156
- wantErr: false,
157
- },
158
- }
159
-
160
- for _, tt := range tests {
161
- t.Run(tt.name, func(t *testing.T) {
162
- data, err := tt.input.MarshalVT()
163
- if (err != nil) != tt.wantErr {
164
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
165
- return
166
- }
167
-
168
- got := &RelayInfo{}
169
- err = got.UnmarshalVT(data)
170
- if (err != nil) != tt.wantErr {
171
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
172
- return
173
- }
174
-
175
- if !tt.input.EqualVT(got) {
176
- t.Errorf("roundtrip mismatch")
177
- }
178
- })
179
- }
180
-}
181
-
182
-// TestRelayInfo_WithMultipleLeases tests array handling for leases
183
-func TestRelayInfo_WithMultipleLeases(t *testing.T) {
184
- leases := []*Lease{
185
- {Identity: &rdsec.Identity{Id: "l1"}, Expires: 100, Name: "lease1"},
186
- {Identity: &rdsec.Identity{Id: "l2"}, Expires: 200, Name: "lease2"},
187
- {Identity: &rdsec.Identity{Id: "l3"}, Expires: 300, Name: "lease3"},
188
- {Identity: &rdsec.Identity{Id: "l4"}, Expires: 400, Name: "lease4"},
189
- {Identity: &rdsec.Identity{Id: "l5"}, Expires: 500, Name: "lease5"},
190
- }
191
-
192
- msg := &RelayInfo{
193
- Identity: &rdsec.Identity{Id: "relay"},
194
- Leases: leases,
195
- }
196
-
197
- data, err := msg.MarshalVT()
198
- if err != nil {
199
- t.Fatalf("MarshalVT() error = %v", err)
200
- }
201
-
202
- got := &RelayInfo{}
203
- if err := got.UnmarshalVT(data); err != nil {
204
- t.Fatalf("UnmarshalVT() error = %v", err)
205
- }
206
-
207
- if len(got.Leases) != len(leases) {
208
- t.Fatalf("got %d leases, want %d", len(got.Leases), len(leases))
209
- }
210
-
211
- for i, want := range leases {
212
- if got.Leases[i].Name != want.Name {
213
- t.Errorf("lease[%d].Name = %v, want %v", i, got.Leases[i].Name, want.Name)
214
- }
215
- }
216
-}
217
-
218
-// TestLease_MarshalVT_UnmarshalVT tests round-trip serialization
219
-func TestLease_MarshalVT_UnmarshalVT(t *testing.T) {
220
- tests := []struct {
221
- name string
222
- input *Lease
223
- wantErr bool
224
- }{
225
- {
226
- name: "empty",
227
- input: &Lease{},
228
- wantErr: false,
229
- },
230
- {
231
- name: "full",
232
- input: &Lease{
233
- Identity: &rdsec.Identity{
234
- Id: "lease-id",
235
- PublicKey: []byte{0x01, 0x02},
236
- },
237
- Expires: 1234567890,
238
- Name: "my-lease",
239
- Alpn: []string{"h2", "http/1.1"},
240
- Metadata: "some metadata",
241
- },
242
- wantErr: false,
243
- },
244
- {
245
- name: "with alpn",
246
- input: &Lease{
247
- Identity: &rdsec.Identity{Id: "alpn-test"},
248
- Alpn: []string{"h2", "grpc"},
249
- },
250
- wantErr: false,
251
- },
252
- }
253
-
254
- for _, tt := range tests {
255
- t.Run(tt.name, func(t *testing.T) {
256
- data, err := tt.input.MarshalVT()
257
- if (err != nil) != tt.wantErr {
258
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
259
- return
260
- }
261
-
262
- got := &Lease{}
263
- err = got.UnmarshalVT(data)
264
- if (err != nil) != tt.wantErr {
265
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
266
- return
267
- }
268
-
269
- if !tt.input.EqualVT(got) {
270
- t.Errorf("roundtrip mismatch")
271
- }
272
- })
273
- }
274
-}
275
-
276
-// TestLeaseUpdateRequest_MarshalVT_UnmarshalVT tests round-trip serialization
277
-func TestLeaseUpdateRequest_MarshalVT_UnmarshalVT(t *testing.T) {
278
- lease := &Lease{
279
- Identity: &rdsec.Identity{Id: "update-lease"},
280
- Expires: 1234567890,
281
- Name: "lease-name",
282
- }
283
-
284
- tests := []struct {
285
- name string
286
- input *LeaseUpdateRequest
287
- wantErr bool
288
- }{
289
- {
290
- name: "full",
291
- input: &LeaseUpdateRequest{
292
- Lease: lease,
293
- Nonce: []byte{0x01, 0x02, 0x03, 0x04},
294
- Timestamp: 9876543210,
295
- },
296
- wantErr: false,
297
- },
298
- {
299
- name: "empty",
300
- input: &LeaseUpdateRequest{},
301
- wantErr: false,
302
- },
303
- }
304
-
305
- for _, tt := range tests {
306
- t.Run(tt.name, func(t *testing.T) {
307
- data, err := tt.input.MarshalVT()
308
- if (err != nil) != tt.wantErr {
309
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
310
- return
311
- }
312
-
313
- got := &LeaseUpdateRequest{}
314
- err = got.UnmarshalVT(data)
315
- if (err != nil) != tt.wantErr {
316
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
317
- return
318
- }
319
-
320
- if !tt.input.EqualVT(got) {
321
- t.Errorf("roundtrip mismatch")
322
- }
323
- })
324
- }
325
-}
326
-
327
-// TestResponseCode_AllValues tests all response code values
328
-func TestResponseCode_AllValues(t *testing.T) {
329
- codes := []ResponseCode{
330
- ResponseCode_RESPONSE_CODE_UNKNOWN,
331
- ResponseCode_RESPONSE_CODE_ACCEPTED,
332
- ResponseCode_RESPONSE_CODE_INVALID_EXPIRES,
333
- ResponseCode_RESPONSE_CODE_INVALID_IDENTITY,
334
- ResponseCode_RESPONSE_CODE_INVALID_NAME,
335
- ResponseCode_RESPONSE_CODE_INVALID_ALPN,
336
- ResponseCode_RESPONSE_CODE_REJECTED,
337
- }
338
-
339
- for _, code := range codes {
340
- t.Run(code.String(), func(t *testing.T) {
341
- msg := &LeaseUpdateResponse{Code: code}
342
-
343
- data, err := msg.MarshalVT()
344
- if err != nil {
345
- t.Fatalf("MarshalVT() error = %v", err)
346
- }
347
-
348
- got := &LeaseUpdateResponse{}
349
- if err := got.UnmarshalVT(data); err != nil {
350
- t.Fatalf("UnmarshalVT() error = %v", err)
351
- }
352
-
353
- if got.Code != code {
354
- t.Errorf("Code = %v, want %v", got.Code, code)
355
- }
356
- })
357
- }
358
-}
359
-
360
-// TestLeaseDeleteRequest_MarshalVT_UnmarshalVT tests round-trip serialization
361
-func TestLeaseDeleteRequest_MarshalVT_UnmarshalVT(t *testing.T) {
362
- tests := []struct {
363
- name string
364
- input *LeaseDeleteRequest
365
- wantErr bool
366
- }{
367
- {
368
- name: "full",
369
- input: &LeaseDeleteRequest{
370
- Identity: &rdsec.Identity{Id: "delete-id", PublicKey: []byte{0x01}},
371
- Nonce: []byte{0x01, 0x02, 0x03},
372
- Timestamp: 1234567890,
373
- },
374
- wantErr: false,
375
- },
376
- {
377
- name: "empty",
378
- input: &LeaseDeleteRequest{},
379
- wantErr: false,
380
- },
381
- }
382
-
383
- for _, tt := range tests {
384
- t.Run(tt.name, func(t *testing.T) {
385
- data, err := tt.input.MarshalVT()
386
- if (err != nil) != tt.wantErr {
387
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
388
- return
389
- }
390
-
391
- got := &LeaseDeleteRequest{}
392
- err = got.UnmarshalVT(data)
393
- if (err != nil) != tt.wantErr {
394
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
395
- return
396
- }
397
-
398
- if !tt.input.EqualVT(got) {
399
- t.Errorf("roundtrip mismatch")
400
- }
401
- })
402
- }
403
-}
404
-
405
-// TestConnectionRequest_MarshalVT_UnmarshalVT tests round-trip serialization
406
-func TestConnectionRequest_MarshalVT_UnmarshalVT(t *testing.T) {
407
- tests := []struct {
408
- name string
409
- input *ConnectionRequest
410
- wantErr bool
411
- }{
412
- {
413
- name: "full",
414
- input: &ConnectionRequest{
415
- LeaseId: "lease-123",
416
- ClientIdentity: &rdsec.Identity{Id: "client-id", PublicKey: []byte{0xAA, 0xBB}},
417
- },
418
- wantErr: false,
419
- },
420
- {
421
- name: "empty",
422
- input: &ConnectionRequest{},
423
- wantErr: false,
424
- },
425
- {
426
- name: "lease id only",
427
- input: &ConnectionRequest{
428
- LeaseId: "lease-only",
429
- },
430
- wantErr: false,
431
- },
432
- }
433
-
434
- for _, tt := range tests {
435
- t.Run(tt.name, func(t *testing.T) {
436
- data, err := tt.input.MarshalVT()
437
- if (err != nil) != tt.wantErr {
438
- t.Errorf("MarshalVT() error = %v, wantErr %v", err, tt.wantErr)
439
- return
440
- }
441
-
442
- got := &ConnectionRequest{}
443
- err = got.UnmarshalVT(data)
444
- if (err != nil) != tt.wantErr {
445
- t.Errorf("UnmarshalVT() error = %v, wantErr %v", err, tt.wantErr)
446
- return
447
- }
448
-
449
- if !tt.input.EqualVT(got) {
450
- t.Errorf("roundtrip mismatch")
451
- }
452
- })
453
- }
454
-}
455
-
456
-// TestRelayInfoRequest_MarshalVT_UnmarshalVT tests empty message
457
-func TestRelayInfoRequest_MarshalVT_UnmarshalVT(t *testing.T) {
458
- msg := &RelayInfoRequest{}
459
-
460
- data, err := msg.MarshalVT()
461
- if err != nil {
462
- t.Fatalf("MarshalVT() error = %v", err)
463
- }
464
-
465
- got := &RelayInfoRequest{}
466
- err = got.UnmarshalVT(data)
467
- if err != nil {
468
- t.Fatalf("UnmarshalVT() error = %v", err)
469
- }
470
-
471
- if !msg.EqualVT(got) {
472
- t.Error("roundtrip mismatch for empty RelayInfoRequest")
473
- }
474
-}
475
-
476
-// TestRelayInfoResponse_MarshalVT_UnmarshalVT tests with RelayInfo
477
-func TestRelayInfoResponse_MarshalVT_UnmarshalVT(t *testing.T) {
478
- relayInfo := &RelayInfo{
479
- Identity: &rdsec.Identity{Id: "response-relay"},
480
- Address: []string{"addr1:8080"},
481
- Leases: []*Lease{
482
- {Identity: &rdsec.Identity{Id: "l1"}, Name: "lease1"},
483
- },
484
- }
485
-
486
- msg := &RelayInfoResponse{RelayInfo: relayInfo}
487
-
488
- data, err := msg.MarshalVT()
489
- if err != nil {
490
- t.Fatalf("MarshalVT() error = %v", err)
491
- }
492
-
493
- got := &RelayInfoResponse{}
494
- err = got.UnmarshalVT(data)
495
- if err != nil {
496
- t.Fatalf("UnmarshalVT() error = %v", err)
497
- }
498
-
499
- if !msg.EqualVT(got) {
500
- t.Error("roundtrip mismatch")
501
- }
502
-}
503
-
504
-// TestCloneVT tests cloning creates independent copies
505
-func TestCloneVT(t *testing.T) {
506
- t.Run("Packet", func(t *testing.T) {
507
- original := &Packet{
508
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
509
- Payload: []byte{0x01, 0x02, 0x03},
510
- }
511
- cloned := original.CloneVT()
512
-
513
- cloned.Type = PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST
514
- cloned.Payload[0] = 0xFF
515
-
516
- if original.Type != PacketType_PACKET_TYPE_CONNECTION_REQUEST {
517
- t.Error("original.Type was modified")
518
- }
519
- if original.Payload[0] != 0x01 {
520
- t.Error("original.Payload was modified")
521
- }
522
- })
523
-
524
- t.Run("RelayInfo", func(t *testing.T) {
525
- original := &RelayInfo{
526
- Identity: &rdsec.Identity{Id: "test"},
527
- Address: []string{"addr1"},
528
- Leases: []*Lease{
529
- {Identity: &rdsec.Identity{Id: "l1"}, Name: "lease1"},
530
- },
531
- }
532
- cloned := original.CloneVT()
533
-
534
- cloned.Identity.Id = "modified"
535
- cloned.Address[0] = "modified-addr"
536
- cloned.Leases[0].Name = "modified-lease"
537
-
538
- if original.Identity.Id != "test" {
539
- t.Error("original.Identity.Id was modified")
540
- }
541
- if original.Address[0] != "addr1" {
542
- t.Error("original.Address was modified")
543
- }
544
- if original.Leases[0].Name != "lease1" {
545
- t.Error("original.Leases[0].Name was modified")
546
- }
547
- })
548
-
549
- t.Run("Lease", func(t *testing.T) {
550
- original := &Lease{
551
- Identity: &rdsec.Identity{Id: "lease-clone"},
552
- Expires: 12345,
553
- Name: "clone-lease",
554
- Alpn: []string{"h2"},
555
- }
556
- cloned := original.CloneVT()
557
-
558
- cloned.Identity.Id = "modified"
559
- cloned.Expires = 99999
560
- cloned.Name = "modified"
561
- cloned.Alpn[0] = "modified"
562
-
563
- if original.Identity.Id != "lease-clone" {
564
- t.Error("original.Identity.Id was modified")
565
- }
566
- if original.Expires != 12345 {
567
- t.Error("original.Expires was modified")
568
- }
569
- if original.Name != "clone-lease" {
570
- t.Error("original.Name was modified")
571
- }
572
- if original.Alpn[0] != "h2" {
573
- t.Error("original.Alpn was modified")
574
- }
575
- })
576
-}
577
-
578
-// TestEqualVT tests equality comparison
579
-func TestEqualVT(t *testing.T) {
580
- t.Run("Packet", func(t *testing.T) {
581
- a := &Packet{Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST, Payload: []byte{0x01}}
582
- b := &Packet{Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST, Payload: []byte{0x01}}
583
- c := &Packet{Type: PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST, Payload: []byte{0x01}}
584
-
585
- if !a.EqualVT(b) {
586
- t.Error("Equal packets should be equal")
587
- }
588
- if a.EqualVT(c) {
589
- t.Error("Different packet types should not be equal")
590
- }
591
- if a.EqualVT(nil) {
592
- t.Error("Packet should not equal nil")
593
- }
594
- if !(*Packet)(nil).EqualVT(nil) {
595
- t.Error("nil should equal nil")
596
- }
597
- })
598
-
599
- t.Run("Lease", func(t *testing.T) {
600
- identity := &rdsec.Identity{Id: "test"}
601
- a := &Lease{Identity: identity, Expires: 123, Name: "test"}
602
- b := &Lease{Identity: identity, Expires: 123, Name: "test"}
603
- c := &Lease{Identity: identity, Expires: 456, Name: "test"}
604
-
605
- if !a.EqualVT(b) {
606
- t.Error("Equal leases should be equal")
607
- }
608
- if a.EqualVT(c) {
609
- t.Error("Leases with different Expires should not be equal")
610
- }
611
- })
612
-}
613
-
614
-// TestSizeVT tests size calculation accuracy
615
-func TestSizeVT(t *testing.T) {
616
- t.Run("Packet", func(t *testing.T) {
617
- msg := &Packet{
618
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
619
- Payload: []byte("test payload"),
620
- }
621
-
622
- size := msg.SizeVT()
623
- data, err := msg.MarshalVT()
624
- if err != nil {
625
- t.Fatalf("MarshalVT() error = %v", err)
626
- }
627
-
628
- if size != len(data) {
629
- t.Errorf("SizeVT() = %v, MarshalVT() produced %v bytes", size, len(data))
630
- }
631
- })
632
-
633
- t.Run("RelayInfo", func(t *testing.T) {
634
- msg := &RelayInfo{
635
- Identity: &rdsec.Identity{Id: "size-test"},
636
- Address: []string{"addr1:8080", "addr2:8080"},
637
- Leases: []*Lease{
638
- {Identity: &rdsec.Identity{Id: "l1"}, Name: "lease1"},
639
- },
640
- }
641
-
642
- size := msg.SizeVT()
643
- data, err := msg.MarshalVT()
644
- if err != nil {
645
- t.Fatalf("MarshalVT() error = %v", err)
646
- }
647
-
648
- if size != len(data) {
649
- t.Errorf("SizeVT() = %v, MarshalVT() produced %v bytes", size, len(data))
650
- }
651
- })
652
-
653
- t.Run("Lease", func(t *testing.T) {
654
- msg := &Lease{
655
- Identity: &rdsec.Identity{Id: "size-lease"},
656
- Expires: 1234567890,
657
- Name: "size-test",
658
- Alpn: []string{"h2", "http/1.1"},
659
- Metadata: "size metadata",
660
- }
661
-
662
- size := msg.SizeVT()
663
- data, err := msg.MarshalVT()
664
- if err != nil {
665
- t.Fatalf("MarshalVT() error = %v", err)
666
- }
667
-
668
- if size != len(data) {
669
- t.Errorf("SizeVT() = %v, MarshalVT() produced %v bytes", size, len(data))
670
- }
671
- })
672
-}
673
-
674
-// TestReset tests Reset clears all fields
675
-func TestReset(t *testing.T) {
676
- t.Run("Packet", func(t *testing.T) {
677
- p := &Packet{Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST, Payload: []byte{0x01}}
678
- p.Reset()
679
- if p.Type != 0 || p.Payload != nil {
680
- t.Error("Packet not properly reset")
681
- }
682
- })
683
-
684
- t.Run("Lease", func(t *testing.T) {
685
- l := &Lease{
686
- Identity: &rdsec.Identity{Id: "test"},
687
- Expires: 123,
688
- Name: "test-name",
689
- Alpn: []string{"h2"},
690
- Metadata: "meta",
691
- }
692
- l.Reset()
693
- if l.Identity != nil || l.Expires != 0 || l.Name != "" || l.Alpn != nil || l.Metadata != "" {
694
- t.Error("Lease not properly reset")
695
- }
696
- })
697
-}
698
-
699
-// TestGetters tests getter methods
700
-func TestGetters(t *testing.T) {
701
- t.Run("Packet", func(t *testing.T) {
702
- p := &Packet{
703
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
704
- Payload: []byte{0x01, 0x02},
705
- }
706
-
707
- if p.GetType() != PacketType_PACKET_TYPE_CONNECTION_REQUEST {
708
- t.Error("GetType() returned wrong value")
709
- }
710
- if !bytes.Equal(p.GetPayload(), []byte{0x01, 0x02}) {
711
- t.Error("GetPayload() returned wrong value")
712
- }
713
- })
714
-
715
- t.Run("Lease", func(t *testing.T) {
716
- identity := &rdsec.Identity{Id: "lease-id", PublicKey: []byte{0x01}}
717
- l := &Lease{
718
- Identity: identity,
719
- Expires: 12345,
720
- Name: "lease-name",
721
- Alpn: []string{"h2", "grpc"},
722
- Metadata: "lease-metadata",
723
- }
724
-
725
- if l.GetIdentity() != identity {
726
- t.Error("GetIdentity() returned wrong value")
727
- }
728
- if l.GetExpires() != 12345 {
729
- t.Error("GetExpires() returned wrong value")
730
- }
731
- if l.GetName() != "lease-name" {
732
- t.Error("GetName() returned wrong value")
733
- }
734
- if len(l.GetAlpn()) != 2 {
735
- t.Error("GetAlpn() returned wrong value")
736
- }
737
- if l.GetMetadata() != "lease-metadata" {
738
- t.Error("GetMetadata() returned wrong value")
739
- }
740
- })
741
-
742
- t.Run("nil Lease", func(t *testing.T) {
743
- var l *Lease
744
- if l.GetIdentity() != nil {
745
- t.Error("GetIdentity() on nil should return nil")
746
- }
747
- if l.GetExpires() != 0 {
748
- t.Error("GetExpires() on nil should return 0")
749
- }
750
- if l.GetName() != "" {
751
- t.Error("GetName() on nil should return empty string")
752
- }
753
- if l.GetAlpn() != nil {
754
- t.Error("GetAlpn() on nil should return nil")
755
- }
756
- if l.GetMetadata() != "" {
757
- t.Error("GetMetadata() on nil should return empty string")
758
- }
759
- })
760
-}
761
-
762
-// TestNilHandling tests nil message handling
763
-func TestNilHandling(t *testing.T) {
764
- testCases := []struct {
765
- name string
766
- test func(t *testing.T)
767
- }{
768
- {"Packet", func(t *testing.T) {
769
- var msg *Packet
770
- if data, err := msg.MarshalVT(); err != nil || data != nil {
771
- t.Errorf("MarshalVT() on nil Packet = (%v, %v), want (nil, nil)", data, err)
772
- }
773
- if msg.CloneVT() != nil {
774
- t.Error("CloneVT() on nil Packet should return nil")
775
- }
776
- if msg.SizeVT() != 0 {
777
- t.Error("SizeVT() on nil Packet should return 0")
778
- }
779
- }},
780
- {"RelayInfo", func(t *testing.T) {
781
- var msg *RelayInfo
782
- if msg.CloneVT() != nil {
783
- t.Error("CloneVT() on nil RelayInfo should return nil")
784
- }
785
- }},
786
- {"Lease", func(t *testing.T) {
787
- var msg *Lease
788
- if msg.CloneVT() != nil {
789
- t.Error("CloneVT() on nil Lease should return nil")
790
- }
791
- }},
792
- {"LeaseUpdateRequest", func(t *testing.T) {
793
- var msg *LeaseUpdateRequest
794
- if msg.CloneVT() != nil {
795
- t.Error("CloneVT() on nil LeaseUpdateRequest should return nil")
796
- }
797
- }},
798
- {"LeaseUpdateResponse", func(t *testing.T) {
799
- var msg *LeaseUpdateResponse
800
- if msg.CloneVT() != nil {
801
- t.Error("CloneVT() on nil LeaseUpdateResponse should return nil")
802
- }
803
- }},
804
- {"LeaseDeleteRequest", func(t *testing.T) {
805
- var msg *LeaseDeleteRequest
806
- if msg.CloneVT() != nil {
807
- t.Error("CloneVT() on nil LeaseDeleteRequest should return nil")
808
- }
809
- }},
810
- {"LeaseDeleteResponse", func(t *testing.T) {
811
- var msg *LeaseDeleteResponse
812
- if msg.CloneVT() != nil {
813
- t.Error("CloneVT() on nil LeaseDeleteResponse should return nil")
814
- }
815
- }},
816
- {"ConnectionRequest", func(t *testing.T) {
817
- var msg *ConnectionRequest
818
- if msg.CloneVT() != nil {
819
- t.Error("CloneVT() on nil ConnectionRequest should return nil")
820
- }
821
- }},
822
- {"ConnectionResponse", func(t *testing.T) {
823
- var msg *ConnectionResponse
824
- if msg.CloneVT() != nil {
825
- t.Error("CloneVT() on nil ConnectionResponse should return nil")
826
- }
827
- }},
828
- }
829
-
830
- for _, tc := range testCases {
831
- t.Run(tc.name, tc.test)
832
- }
833
-}
834
-
835
-// TestMarshalVTStrict tests strict marshaling
836
-func TestMarshalVTStrict(t *testing.T) {
837
- msg := &Packet{
838
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
839
- Payload: []byte{0x01, 0x02, 0x03},
840
- }
841
-
842
- data, err := msg.MarshalVTStrict()
843
- if err != nil {
844
- t.Fatalf("MarshalVTStrict() error = %v", err)
845
- }
846
-
847
- got := &Packet{}
848
- err = got.UnmarshalVT(data)
849
- if err != nil {
850
- t.Fatalf("UnmarshalVT() error = %v", err)
851
- }
852
-
853
- if !msg.EqualVT(got) {
854
- t.Error("MarshalVTStrict roundtrip mismatch")
855
- }
856
-}
857
-
858
-// TestUnmarshalVTUnsafe tests unsafe unmarshaling
859
-func TestUnmarshalVTUnsafe(t *testing.T) {
860
- msg := &ConnectionRequest{
861
- LeaseId: "unsafe-test",
862
- ClientIdentity: &rdsec.Identity{Id: "unsafe-client", PublicKey: []byte{0xAA, 0xBB}},
863
- }
864
-
865
- data, err := msg.MarshalVT()
866
- if err != nil {
867
- t.Fatalf("MarshalVT() error = %v", err)
868
- }
869
-
870
- got := &ConnectionRequest{}
871
- err = got.UnmarshalVTUnsafe(data)
872
- if err != nil {
873
- t.Fatalf("UnmarshalVTUnsafe() error = %v", err)
874
- }
875
-
876
- if !msg.EqualVT(got) {
877
- t.Error("UnmarshalVTUnsafe roundtrip mismatch")
878
- }
879
-}
880
-
881
-// TestEmptyResponseMessages tests response messages
882
-func TestEmptyResponseMessages(t *testing.T) {
883
- responses := []ResponseCode{
884
- ResponseCode_RESPONSE_CODE_UNKNOWN,
885
- ResponseCode_RESPONSE_CODE_ACCEPTED,
886
- ResponseCode_RESPONSE_CODE_INVALID_EXPIRES,
887
- ResponseCode_RESPONSE_CODE_INVALID_IDENTITY,
888
- ResponseCode_RESPONSE_CODE_INVALID_NAME,
889
- ResponseCode_RESPONSE_CODE_INVALID_ALPN,
890
- ResponseCode_RESPONSE_CODE_REJECTED,
891
- }
892
-
893
- for _, code := range responses {
894
- t.Run(code.String(), func(t *testing.T) {
895
- updateResp := &LeaseUpdateResponse{Code: code}
896
- data, err := updateResp.MarshalVT()
897
- if err != nil {
898
- t.Fatalf("MarshalVT() error = %v", err)
899
- }
900
-
901
- got := &LeaseUpdateResponse{}
902
- if err := got.UnmarshalVT(data); err != nil {
903
- t.Fatalf("UnmarshalVT() error = %v", err)
904
- }
905
-
906
- if got.Code != code {
907
- t.Errorf("Code = %v, want %v", got.Code, code)
908
- }
909
- })
910
- }
911
-}
912
-
913
-// TestComplexRelayInfo tests complex RelayInfo with multiple nested elements
914
-func TestComplexRelayInfo(t *testing.T) {
915
- // Create a complex RelayInfo with multiple addresses and leases
916
- msg := &RelayInfo{
917
- Identity: &rdsec.Identity{
918
- Id: "complex-relay",
919
- PublicKey: bytes.Repeat([]byte{0xAA}, 32),
920
- },
921
- Address: []string{
922
- "relay1.example.com:443",
923
- "relay2.example.com:443",
924
- "relay3.example.com:443",
925
- },
926
- Leases: []*Lease{
927
- {
928
- Identity: &rdsec.Identity{
929
- Id: "lease-1",
930
- PublicKey: bytes.Repeat([]byte{0x01}, 32),
931
- },
932
- Expires: 1000000000,
933
- Name: "service-1",
934
- Alpn: []string{"h2", "grpc"},
935
- Metadata: "production service 1",
936
- },
937
- {
938
- Identity: &rdsec.Identity{
939
- Id: "lease-2",
940
- PublicKey: bytes.Repeat([]byte{0x02}, 32),
941
- },
942
- Expires: 2000000000,
943
- Name: "service-2",
944
- Alpn: []string{"h2"},
945
- Metadata: "production service 2",
946
- },
947
- },
948
- }
949
-
950
- data, err := msg.MarshalVT()
951
- if err != nil {
952
- t.Fatalf("MarshalVT() error = %v", err)
953
- }
954
-
955
- got := &RelayInfo{}
956
- err = got.UnmarshalVT(data)
957
- if err != nil {
958
- t.Fatalf("UnmarshalVT() error = %v", err)
959
- }
960
-
961
- if !msg.EqualVT(got) {
962
- t.Error("complex RelayInfo roundtrip mismatch")
963
- }
964
-
965
- // Verify all fields
966
- if len(got.Address) != 3 {
967
- t.Errorf("got %d addresses, want 3", len(got.Address))
968
- }
969
- if len(got.Leases) != 2 {
970
- t.Errorf("got %d leases, want 2", len(got.Leases))
971
- }
972
-}
973
-
974
-// BenchmarkPacket_MarshalVT benchmarks packet marshaling
975
-func BenchmarkPacket_MarshalVT(b *testing.B) {
976
- msg := &Packet{
977
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
978
- Payload: bytes.Repeat([]byte{0x01}, 1024),
979
- }
980
-
981
- b.ResetTimer()
982
- for range b.N {
983
- _, _ = msg.MarshalVT()
984
- }
985
-}
986
-
987
-// BenchmarkPacket_UnmarshalVT benchmarks packet unmarshaling
988
-func BenchmarkPacket_UnmarshalVT(b *testing.B) {
989
- msg := &Packet{
990
- Type: PacketType_PACKET_TYPE_CONNECTION_REQUEST,
991
- Payload: bytes.Repeat([]byte{0x01}, 1024),
992
- }
993
-
994
- data, _ := msg.MarshalVT()
995
-
996
- b.ResetTimer()
997
- for range b.N {
998
- got := &Packet{}
999
- _ = got.UnmarshalVT(data)
1000
- }
1001
-}
1002
-
1003
-// BenchmarkRelayInfo_MarshalVT benchmarks complex relay info marshaling
1004
-func BenchmarkRelayInfo_MarshalVT(b *testing.B) {
1005
- msg := &RelayInfo{
1006
- Identity: &rdsec.Identity{
1007
- Id: "benchmark-relay",
1008
- PublicKey: bytes.Repeat([]byte{0xAA}, 32),
1009
- },
1010
- Address: []string{"addr1:8080", "addr2:8080", "addr3:8080"},
1011
- Leases: []*Lease{
1012
- {
1013
- Identity: &rdsec.Identity{Id: "l1", PublicKey: bytes.Repeat([]byte{0x01}, 32)},
1014
- Expires: 1234567890,
1015
- Name: "lease1",
1016
- Alpn: []string{"h2", "grpc"},
1017
- },
1018
- {
1019
- Identity: &rdsec.Identity{Id: "l2", PublicKey: bytes.Repeat([]byte{0x02}, 32)},
1020
- Expires: 9876543210,
1021
- Name: "lease2",
1022
- Alpn: []string{"h2"},
1023
- },
1024
- },
1025
- }
1026
-
1027
- b.ResetTimer()
1028
- for range b.N {
1029
- _, _ = msg.MarshalVT()
1030
- }
1031
-}
1032
-
1033
-// BenchmarkLease_MarshalVT benchmarks lease marshaling
1034
-func BenchmarkLease_MarshalVT(b *testing.B) {
1035
- msg := &Lease{
1036
- Identity: &rdsec.Identity{
1037
- Id: "benchmark-lease",
1038
- PublicKey: bytes.Repeat([]byte{0xBB}, 32),
1039
- },
1040
- Expires: 1234567890,
1041
- Name: "benchmark-lease",
1042
- Alpn: []string{"h2", "grpc", "http/1.1"},
1043
- Metadata: "benchmark metadata",
1044
- }
1045
-
1046
- b.ResetTimer()
1047
- for range b.N {
1048
- _, _ = msg.MarshalVT()
1049
- }
1050
-}
1051
-
1052
-// TestPacketType_String tests enum String method
1053
-func TestPacketType_String(t *testing.T) {
1054
- tests := []struct {
1055
- name string
1056
- enum PacketType
1057
- want string
1058
- }{
1059
- {"PACKET_TYPE_RELAY_INFO_REQUEST", PacketType_PACKET_TYPE_RELAY_INFO_REQUEST, "PACKET_TYPE_RELAY_INFO_REQUEST"},
1060
- {"PACKET_TYPE_RELAY_INFO_RESPONSE", PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE, "PACKET_TYPE_RELAY_INFO_RESPONSE"},
1061
- {"PACKET_TYPE_LEASE_UPDATE_REQUEST", PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST, "PACKET_TYPE_LEASE_UPDATE_REQUEST"},
1062
- {"PACKET_TYPE_LEASE_UPDATE_RESPONSE", PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE, "PACKET_TYPE_LEASE_UPDATE_RESPONSE"},
1063
- {"PACKET_TYPE_LEASE_DELETE_REQUEST", PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST, "PACKET_TYPE_LEASE_DELETE_REQUEST"},
1064
- {"PACKET_TYPE_LEASE_DELETE_RESPONSE", PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE, "PACKET_TYPE_LEASE_DELETE_RESPONSE"},
1065
- {"PACKET_TYPE_CONNECTION_REQUEST", PacketType_PACKET_TYPE_CONNECTION_REQUEST, "PACKET_TYPE_CONNECTION_REQUEST"},
1066
- {"PACKET_TYPE_CONNECTION_RESPONSE", PacketType_PACKET_TYPE_CONNECTION_RESPONSE, "PACKET_TYPE_CONNECTION_RESPONSE"},
1067
- }
1068
-
1069
- for _, tt := range tests {
1070
- t.Run(tt.name, func(t *testing.T) {
1071
- if got := tt.enum.String(); got != tt.want {
1072
- t.Errorf("PacketType.String() = %v, want %v", got, tt.want)
1073
- }
1074
- })
1075
- }
1076
-}
1077
-
1078
-// TestPacketType_Enum tests Enum method
1079
-func TestPacketType_Enum(t *testing.T) {
1080
- if PacketType_PACKET_TYPE_CONNECTION_REQUEST.Enum() != nil && *PacketType_PACKET_TYPE_CONNECTION_REQUEST.Enum() != 6 {
1081
- t.Error("PacketType.Enum() returned wrong value")
1082
- }
1083
- if PacketType_PACKET_TYPE_RELAY_INFO_REQUEST.Enum() != nil && *PacketType_PACKET_TYPE_RELAY_INFO_REQUEST.Enum() != 0 {
1084
- t.Error("PACKET_TYPE_RELAY_INFO_REQUEST.Enum() should be 0")
1085
- }
1086
-}
1087
-
1088
-// TestResponseCode_String tests enum String method
1089
-func TestResponseCode_String(t *testing.T) {
1090
- tests := []struct {
1091
- name string
1092
- enum ResponseCode
1093
- want string
1094
- }{
1095
- {"RESPONSE_CODE_UNKNOWN", ResponseCode_RESPONSE_CODE_UNKNOWN, "RESPONSE_CODE_UNKNOWN"},
1096
- {"RESPONSE_CODE_ACCEPTED", ResponseCode_RESPONSE_CODE_ACCEPTED, "RESPONSE_CODE_ACCEPTED"},
1097
- {"RESPONSE_CODE_INVALID_EXPIRES", ResponseCode_RESPONSE_CODE_INVALID_EXPIRES, "RESPONSE_CODE_INVALID_EXPIRES"},
1098
- {"RESPONSE_CODE_INVALID_IDENTITY", ResponseCode_RESPONSE_CODE_INVALID_IDENTITY, "RESPONSE_CODE_INVALID_IDENTITY"},
1099
- {"RESPONSE_CODE_INVALID_NAME", ResponseCode_RESPONSE_CODE_INVALID_NAME, "RESPONSE_CODE_INVALID_NAME"},
1100
- {"RESPONSE_CODE_INVALID_ALPN", ResponseCode_RESPONSE_CODE_INVALID_ALPN, "RESPONSE_CODE_INVALID_ALPN"},
1101
- {"RESPONSE_CODE_REJECTED", ResponseCode_RESPONSE_CODE_REJECTED, "RESPONSE_CODE_REJECTED"},
1102
- }
1103
-
1104
- for _, tt := range tests {
1105
- t.Run(tt.name, func(t *testing.T) {
1106
- if got := tt.enum.String(); got != tt.want {
1107
- t.Errorf("ResponseCode.String() = %v, want %v", got, tt.want)
1108
- }
1109
- })
1110
- }
1111
-}
1112
-
1113
-// TestResponseCode_Enum tests Enum method
1114
-func TestResponseCode_Enum(t *testing.T) {
1115
- if ResponseCode_RESPONSE_CODE_ACCEPTED.Enum() != nil && *ResponseCode_RESPONSE_CODE_ACCEPTED.Enum() != 1 {
1116
- t.Error("ResponseCode.Enum() returned wrong value")
1117
- }
1118
- if ResponseCode_RESPONSE_CODE_UNKNOWN.Enum() != nil && *ResponseCode_RESPONSE_CODE_UNKNOWN.Enum() != 0 {
1119
- t.Error("RESPONSE_CODE_UNKNOWN.Enum() should be 0")
1120
- }
1121
-}
1122
-
1123
-// TestRelayInfo_Getters tests all getter methods
1124
-func TestRelayInfo_Getters(t *testing.T) {
1125
- identity := &rdsec.Identity{Id: "relay-test", PublicKey: []byte{0xAA}}
1126
- msg := &RelayInfo{
1127
- Identity: identity,
1128
- Address: []string{"addr1:8080", "addr2:8080"},
1129
- Leases: []*Lease{{Name: "lease1"}},
1130
- }
1131
-
1132
- if got := msg.GetIdentity(); got == nil || got.Id != "relay-test" {
1133
- t.Errorf("GetIdentity() = %v, want Id='relay-test'", got)
1134
- }
1135
- if got := msg.GetAddress(); len(got) != 2 {
1136
- t.Errorf("GetAddress() length = %v, want 2", len(got))
1137
- }
1138
- if got := msg.GetLeases(); len(got) != 1 {
1139
- t.Errorf("GetLeases() length = %v, want 1", len(got))
1140
- }
1141
-
1142
- // Test nil defaults
1143
- empty := &RelayInfo{}
1144
- if got := empty.GetIdentity(); got != nil {
1145
- t.Errorf("empty GetIdentity() = %v, want nil", got)
1146
- }
1147
- if got := empty.GetAddress(); got != nil {
1148
- t.Errorf("empty GetAddress() = %v, want nil", got)
1149
- }
1150
- if got := empty.GetLeases(); got != nil {
1151
- t.Errorf("empty GetLeases() = %v, want nil", got)
1152
- }
1153
-}
1154
-
1155
-// TestRelayInfo_Reset tests Reset method
1156
-func TestRelayInfo_Reset(t *testing.T) {
1157
- msg := &RelayInfo{
1158
- Identity: &rdsec.Identity{Id: "test"},
1159
- Address: []string{"addr1"},
1160
- Leases: []*Lease{{Name: "lease1"}},
1161
- }
1162
-
1163
- msg.Reset()
1164
-
1165
- if msg.Identity != nil {
1166
- t.Error("Reset() did not clear Identity")
1167
- }
1168
- if msg.Address != nil {
1169
- t.Error("Reset() did not clear Address")
1170
- }
1171
- if msg.Leases != nil {
1172
- t.Error("Reset() did not clear Leases")
1173
- }
1174
-}
1175
-
1176
-// TestRelayInfoRequest_Reset tests Reset method
1177
-func TestRelayInfoRequest_Reset(t *testing.T) {
1178
- msg := &RelayInfoRequest{}
1179
- msg.Reset() // Should not panic
1180
-}
1181
-
1182
-// TestRelayInfoResponse_Getters tests all getter methods
1183
-func TestRelayInfoResponse_Getters(t *testing.T) {
1184
- relay := &RelayInfo{Identity: &rdsec.Identity{Id: "response-test"}}
1185
- msg := &RelayInfoResponse{
1186
- RelayInfo: relay,
1187
- }
1188
-
1189
- if got := msg.GetRelayInfo(); got == nil || got.Identity.Id != "response-test" {
1190
- t.Errorf("GetRelayInfo() = %v, want Id='response-test'", got)
1191
- }
1192
-
1193
- // Test nil defaults
1194
- empty := &RelayInfoResponse{}
1195
- if got := empty.GetRelayInfo(); got != nil {
1196
- t.Errorf("empty GetRelayInfo() = %v, want nil", got)
1197
- }
1198
-}
1199
-
1200
-// TestRelayInfoResponse_Reset tests Reset method
1201
-func TestRelayInfoResponse_Reset(t *testing.T) {
1202
- msg := &RelayInfoResponse{
1203
- RelayInfo: &RelayInfo{Identity: &rdsec.Identity{Id: "test"}},
1204
- }
1205
-
1206
- msg.Reset()
1207
-
1208
- if msg.RelayInfo != nil {
1209
- t.Error("Reset() did not clear RelayInfo")
1210
- }
1211
-}
1212
-
1213
-// TestLeaseUpdateRequest_Getters tests all getter methods
1214
-func TestLeaseUpdateRequest_Getters(t *testing.T) {
1215
- lease := &Lease{Name: "update-lease"}
1216
- msg := &LeaseUpdateRequest{
1217
- Lease: lease,
1218
- Nonce: []byte{0x01, 0x02},
1219
- Timestamp: 1234567890,
1220
- }
1221
-
1222
- if got := msg.GetLease(); got == nil || got.Name != "update-lease" {
1223
- t.Errorf("GetLease() = %v, want Name='update-lease'", got)
1224
- }
1225
- if got := msg.GetNonce(); !bytes.Equal(got, []byte{0x01, 0x02}) {
1226
- t.Errorf("GetNonce() = %v, want [1 2]", got)
1227
- }
1228
- if got := msg.GetTimestamp(); got != 1234567890 {
1229
- t.Errorf("GetTimestamp() = %v, want 1234567890", got)
1230
- }
1231
-
1232
- // Test nil defaults
1233
- empty := &LeaseUpdateRequest{}
1234
- if got := empty.GetLease(); got != nil {
1235
- t.Errorf("empty GetLease() = %v, want nil", got)
1236
- }
1237
- if got := empty.GetNonce(); got != nil {
1238
- t.Errorf("empty GetNonce() = %v, want nil", got)
1239
- }
1240
-}
1241
-
1242
-// TestLeaseUpdateRequest_Reset tests Reset method
1243
-func TestLeaseUpdateRequest_Reset(t *testing.T) {
1244
- msg := &LeaseUpdateRequest{
1245
- Lease: &Lease{Name: "test"},
1246
- Nonce: []byte{0x01},
1247
- Timestamp: 123,
1248
- }
1249
-
1250
- msg.Reset()
1251
-
1252
- if msg.Lease != nil {
1253
- t.Error("Reset() did not clear Lease")
1254
- }
1255
- if msg.Nonce != nil {
1256
- t.Error("Reset() did not clear Nonce")
1257
- }
1258
- if msg.Timestamp != 0 {
1259
- t.Error("Reset() did not clear Timestamp")
1260
- }
1261
-}
1262
-
1263
-// TestLeaseUpdateResponse_Reset tests Reset method
1264
-func TestLeaseUpdateResponse_Reset(t *testing.T) {
1265
- msg := &LeaseUpdateResponse{
1266
- Code: ResponseCode_RESPONSE_CODE_ACCEPTED,
1267
- }
1268
-
1269
- msg.Reset()
1270
-
1271
- if msg.Code != ResponseCode_RESPONSE_CODE_UNKNOWN {
1272
- t.Error("Reset() did not clear Code to default")
1273
- }
1274
-}
1275
-
1276
-// TestLeaseDeleteRequest_Reset tests Reset method
1277
-func TestLeaseDeleteRequest_Reset(t *testing.T) {
1278
- msg := &LeaseDeleteRequest{
1279
- Identity: &rdsec.Identity{Id: "delete-test"},
1280
- }
1281
-
1282
- msg.Reset()
1283
-
1284
- if msg.Identity != nil {
1285
- t.Error("Reset() did not clear Identity")
1286
- }
1287
-}
1288
-
1289
-// TestLeaseDeleteResponse_Reset tests Reset method
1290
-func TestLeaseDeleteResponse_Reset(t *testing.T) {
1291
- msg := &LeaseDeleteResponse{
1292
- Code: ResponseCode_RESPONSE_CODE_ACCEPTED,
1293
- }
1294
-
1295
- msg.Reset()
1296
-
1297
- if msg.Code != ResponseCode_RESPONSE_CODE_UNKNOWN {
1298
- t.Error("Reset() did not clear Code to default")
1299
- }
1300
-}
portal/core/proto/rdverb/rdverb_vtproto.pb.go
deleted
-4194
@@ -1,4194 +0,0 @@
1
-// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
2
-// protoc-gen-go-vtproto version: v0.6.0
3
-// source: portal/core/proto/rdverb/rdverb.proto
4
-
5
-package rdverb
6
-
7
-import (
8
- fmt "fmt"
9
- protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
10
- protoimpl "google.golang.org/protobuf/runtime/protoimpl"
11
- rdsec "gosuda.org/portal/portal/core/proto/rdsec"
12
- io "io"
13
- unsafe "unsafe"
14
-)
15
-
16
-const (
17
- // Verify that this generated code is sufficiently up-to-date.
18
- _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19
- // Verify that runtime/protoimpl is sufficiently up-to-date.
20
- _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21
-)
22
-
23
-func (m *Packet) CloneVT() *Packet {
24
- if m == nil {
25
- return (*Packet)(nil)
26
- }
27
- r := new(Packet)
28
- r.Type = m.Type
29
- if rhs := m.Payload; rhs != nil {
30
- tmpBytes := make([]byte, len(rhs))
31
- copy(tmpBytes, rhs)
32
- r.Payload = tmpBytes
33
- }
34
- if len(m.unknownFields) > 0 {
35
- r.unknownFields = make([]byte, len(m.unknownFields))
36
- copy(r.unknownFields, m.unknownFields)
37
- }
38
- return r
39
-}
40
-
41
-func (m *Packet) CloneMessageVT() any {
42
- return m.CloneVT()
43
-}
44
-
45
-func (m *RelayInfo) CloneVT() *RelayInfo {
46
- if m == nil {
47
- return (*RelayInfo)(nil)
48
- }
49
- r := new(RelayInfo)
50
- r.Identity = m.Identity.CloneVT()
51
- if rhs := m.Address; rhs != nil {
52
- tmpContainer := make([]string, len(rhs))
53
- copy(tmpContainer, rhs)
54
- r.Address = tmpContainer
55
- }
56
- if rhs := m.Leases; rhs != nil {
57
- tmpContainer := make([]*Lease, len(rhs))
58
- for k, v := range rhs {
59
- tmpContainer[k] = v.CloneVT()
60
- }
61
- r.Leases = tmpContainer
62
- }
63
- if len(m.unknownFields) > 0 {
64
- r.unknownFields = make([]byte, len(m.unknownFields))
65
- copy(r.unknownFields, m.unknownFields)
66
- }
67
- return r
68
-}
69
-
70
-func (m *RelayInfo) CloneMessageVT() any {
71
- return m.CloneVT()
72
-}
73
-
74
-func (m *RelayInfoRequest) CloneVT() *RelayInfoRequest {
75
- if m == nil {
76
- return (*RelayInfoRequest)(nil)
77
- }
78
- r := new(RelayInfoRequest)
79
- if len(m.unknownFields) > 0 {
80
- r.unknownFields = make([]byte, len(m.unknownFields))
81
- copy(r.unknownFields, m.unknownFields)
82
- }
83
- return r
84
-}
85
-
86
-func (m *RelayInfoRequest) CloneMessageVT() any {
87
- return m.CloneVT()
88
-}
89
-
90
-func (m *RelayInfoResponse) CloneVT() *RelayInfoResponse {
91
- if m == nil {
92
- return (*RelayInfoResponse)(nil)
93
- }
94
- r := new(RelayInfoResponse)
95
- r.RelayInfo = m.RelayInfo.CloneVT()
96
- if len(m.unknownFields) > 0 {
97
- r.unknownFields = make([]byte, len(m.unknownFields))
98
- copy(r.unknownFields, m.unknownFields)
99
- }
100
- return r
101
-}
102
-
103
-func (m *RelayInfoResponse) CloneMessageVT() any {
104
- return m.CloneVT()
105
-}
106
-
107
-func (m *Lease) CloneVT() *Lease {
108
- if m == nil {
109
- return (*Lease)(nil)
110
- }
111
- r := new(Lease)
112
- r.Identity = m.Identity.CloneVT()
113
- r.Expires = m.Expires
114
- r.Name = m.Name
115
- r.Metadata = m.Metadata
116
- if rhs := m.Alpn; rhs != nil {
117
- tmpContainer := make([]string, len(rhs))
118
- copy(tmpContainer, rhs)
119
- r.Alpn = tmpContainer
120
- }
121
- if len(m.unknownFields) > 0 {
122
- r.unknownFields = make([]byte, len(m.unknownFields))
123
- copy(r.unknownFields, m.unknownFields)
124
- }
125
- return r
126
-}
127
-
128
-func (m *Lease) CloneMessageVT() any {
129
- return m.CloneVT()
130
-}
131
-
132
-func (m *LeaseUpdateRequest) CloneVT() *LeaseUpdateRequest {
133
- if m == nil {
134
- return (*LeaseUpdateRequest)(nil)
135
- }
136
- r := new(LeaseUpdateRequest)
137
- r.Lease = m.Lease.CloneVT()
138
- r.Timestamp = m.Timestamp
139
- if rhs := m.Nonce; rhs != nil {
140
- tmpBytes := make([]byte, len(rhs))
141
- copy(tmpBytes, rhs)
142
- r.Nonce = tmpBytes
143
- }
144
- if len(m.unknownFields) > 0 {
145
- r.unknownFields = make([]byte, len(m.unknownFields))
146
- copy(r.unknownFields, m.unknownFields)
147
- }
148
- return r
149
-}
150
-
151
-func (m *LeaseUpdateRequest) CloneMessageVT() any {
152
- return m.CloneVT()
153
-}
154
-
155
-func (m *LeaseUpdateResponse) CloneVT() *LeaseUpdateResponse {
156
- if m == nil {
157
- return (*LeaseUpdateResponse)(nil)
158
- }
159
- r := new(LeaseUpdateResponse)
160
- r.Code = m.Code
161
- if len(m.unknownFields) > 0 {
162
- r.unknownFields = make([]byte, len(m.unknownFields))
163
- copy(r.unknownFields, m.unknownFields)
164
- }
165
- return r
166
-}
167
-
168
-func (m *LeaseUpdateResponse) CloneMessageVT() any {
169
- return m.CloneVT()
170
-}
171
-
172
-func (m *LeaseDeleteRequest) CloneVT() *LeaseDeleteRequest {
173
- if m == nil {
174
- return (*LeaseDeleteRequest)(nil)
175
- }
176
- r := new(LeaseDeleteRequest)
177
- r.Identity = m.Identity.CloneVT()
178
- r.Timestamp = m.Timestamp
179
- if rhs := m.Nonce; rhs != nil {
180
- tmpBytes := make([]byte, len(rhs))
181
- copy(tmpBytes, rhs)
182
- r.Nonce = tmpBytes
183
- }
184
- if len(m.unknownFields) > 0 {
185
- r.unknownFields = make([]byte, len(m.unknownFields))
186
- copy(r.unknownFields, m.unknownFields)
187
- }
188
- return r
189
-}
190
-
191
-func (m *LeaseDeleteRequest) CloneMessageVT() any {
192
- return m.CloneVT()
193
-}
194
-
195
-func (m *LeaseDeleteResponse) CloneVT() *LeaseDeleteResponse {
196
- if m == nil {
197
- return (*LeaseDeleteResponse)(nil)
198
- }
199
- r := new(LeaseDeleteResponse)
200
- r.Code = m.Code
201
- if len(m.unknownFields) > 0 {
202
- r.unknownFields = make([]byte, len(m.unknownFields))
203
- copy(r.unknownFields, m.unknownFields)
204
- }
205
- return r
206
-}
207
-
208
-func (m *LeaseDeleteResponse) CloneMessageVT() any {
209
- return m.CloneVT()
210
-}
211
-
212
-func (m *ConnectionRequest) CloneVT() *ConnectionRequest {
213
- if m == nil {
214
- return (*ConnectionRequest)(nil)
215
- }
216
- r := new(ConnectionRequest)
217
- r.LeaseId = m.LeaseId
218
- r.ClientIdentity = m.ClientIdentity.CloneVT()
219
- if len(m.unknownFields) > 0 {
220
- r.unknownFields = make([]byte, len(m.unknownFields))
221
- copy(r.unknownFields, m.unknownFields)
222
- }
223
- return r
224
-}
225
-
226
-func (m *ConnectionRequest) CloneMessageVT() any {
227
- return m.CloneVT()
228
-}
229
-
230
-func (m *ConnectionResponse) CloneVT() *ConnectionResponse {
231
- if m == nil {
232
- return (*ConnectionResponse)(nil)
233
- }
234
- r := new(ConnectionResponse)
235
- r.Code = m.Code
236
- if len(m.unknownFields) > 0 {
237
- r.unknownFields = make([]byte, len(m.unknownFields))
238
- copy(r.unknownFields, m.unknownFields)
239
- }
240
- return r
241
-}
242
-
243
-func (m *ConnectionResponse) CloneMessageVT() any {
244
- return m.CloneVT()
245
-}
246
-
247
-func (this *Packet) EqualVT(that *Packet) bool {
248
- if this == that {
249
- return true
250
- } else if this == nil || that == nil {
251
- return false
252
- }
253
- if this.Type != that.Type {
254
- return false
255
- }
256
- if string(this.Payload) != string(that.Payload) {
257
- return false
258
- }
259
- return string(this.unknownFields) == string(that.unknownFields)
260
-}
261
-
262
-func (this *Packet) EqualMessageVT(thatMsg any) bool {
263
- that, ok := thatMsg.(*Packet)
264
- if !ok {
265
- return false
266
- }
267
- return this.EqualVT(that)
268
-}
269
-func (this *RelayInfo) EqualVT(that *RelayInfo) bool {
270
- if this == that {
271
- return true
272
- } else if this == nil || that == nil {
273
- return false
274
- }
275
- if !this.Identity.EqualVT(that.Identity) {
276
- return false
277
- }
278
- if len(this.Address) != len(that.Address) {
279
- return false
280
- }
281
- for i, vx := range this.Address {
282
- vy := that.Address[i]
283
- if vx != vy {
284
- return false
285
- }
286
- }
287
- if len(this.Leases) != len(that.Leases) {
288
- return false
289
- }
290
- for i, vx := range this.Leases {
291
- vy := that.Leases[i]
292
- if p, q := vx, vy; p != q {
293
- if p == nil {
294
- p = &Lease{}
295
- }
296
- if q == nil {
297
- q = &Lease{}
298
- }
299
- if !p.EqualVT(q) {
300
- return false
301
- }
302
- }
303
- }
304
- return string(this.unknownFields) == string(that.unknownFields)
305
-}
306
-
307
-func (this *RelayInfo) EqualMessageVT(thatMsg any) bool {
308
- that, ok := thatMsg.(*RelayInfo)
309
- if !ok {
310
- return false
311
- }
312
- return this.EqualVT(that)
313
-}
314
-func (this *RelayInfoRequest) EqualVT(that *RelayInfoRequest) bool {
315
- if this == that {
316
- return true
317
- } else if this == nil || that == nil {
318
- return false
319
- }
320
- return string(this.unknownFields) == string(that.unknownFields)
321
-}
322
-
323
-func (this *RelayInfoRequest) EqualMessageVT(thatMsg any) bool {
324
- that, ok := thatMsg.(*RelayInfoRequest)
325
- if !ok {
326
- return false
327
- }
328
- return this.EqualVT(that)
329
-}
330
-func (this *RelayInfoResponse) EqualVT(that *RelayInfoResponse) bool {
331
- if this == that {
332
- return true
333
- } else if this == nil || that == nil {
334
- return false
335
- }
336
- if !this.RelayInfo.EqualVT(that.RelayInfo) {
337
- return false
338
- }
339
- return string(this.unknownFields) == string(that.unknownFields)
340
-}
341
-
342
-func (this *RelayInfoResponse) EqualMessageVT(thatMsg any) bool {
343
- that, ok := thatMsg.(*RelayInfoResponse)
344
- if !ok {
345
- return false
346
- }
347
- return this.EqualVT(that)
348
-}
349
-func (this *Lease) EqualVT(that *Lease) bool {
350
- if this == that {
351
- return true
352
- } else if this == nil || that == nil {
353
- return false
354
- }
355
- if !this.Identity.EqualVT(that.Identity) {
356
- return false
357
- }
358
- if this.Expires != that.Expires {
359
- return false
360
- }
361
- if this.Name != that.Name {
362
- return false
363
- }
364
- if len(this.Alpn) != len(that.Alpn) {
365
- return false
366
- }
367
- for i, vx := range this.Alpn {
368
- vy := that.Alpn[i]
369
- if vx != vy {
370
- return false
371
- }
372
- }
373
- if this.Metadata != that.Metadata {
374
- return false
375
- }
376
- return string(this.unknownFields) == string(that.unknownFields)
377
-}
378
-
379
-func (this *Lease) EqualMessageVT(thatMsg any) bool {
380
- that, ok := thatMsg.(*Lease)
381
- if !ok {
382
- return false
383
- }
384
- return this.EqualVT(that)
385
-}
386
-func (this *LeaseUpdateRequest) EqualVT(that *LeaseUpdateRequest) bool {
387
- if this == that {
388
- return true
389
- } else if this == nil || that == nil {
390
- return false
391
- }
392
- if !this.Lease.EqualVT(that.Lease) {
393
- return false
394
- }
395
- if string(this.Nonce) != string(that.Nonce) {
396
- return false
397
- }
398
- if this.Timestamp != that.Timestamp {
399
- return false
400
- }
401
- return string(this.unknownFields) == string(that.unknownFields)
402
-}
403
-
404
-func (this *LeaseUpdateRequest) EqualMessageVT(thatMsg any) bool {
405
- that, ok := thatMsg.(*LeaseUpdateRequest)
406
- if !ok {
407
- return false
408
- }
409
- return this.EqualVT(that)
410
-}
411
-func (this *LeaseUpdateResponse) EqualVT(that *LeaseUpdateResponse) bool {
412
- if this == that {
413
- return true
414
- } else if this == nil || that == nil {
415
- return false
416
- }
417
- if this.Code != that.Code {
418
- return false
419
- }
420
- return string(this.unknownFields) == string(that.unknownFields)
421
-}
422
-
423
-func (this *LeaseUpdateResponse) EqualMessageVT(thatMsg any) bool {
424
- that, ok := thatMsg.(*LeaseUpdateResponse)
425
- if !ok {
426
- return false
427
- }
428
- return this.EqualVT(that)
429
-}
430
-func (this *LeaseDeleteRequest) EqualVT(that *LeaseDeleteRequest) bool {
431
- if this == that {
432
- return true
433
- } else if this == nil || that == nil {
434
- return false
435
- }
436
- if !this.Identity.EqualVT(that.Identity) {
437
- return false
438
- }
439
- if string(this.Nonce) != string(that.Nonce) {
440
- return false
441
- }
442
- if this.Timestamp != that.Timestamp {
443
- return false
444
- }
445
- return string(this.unknownFields) == string(that.unknownFields)
446
-}
447
-
448
-func (this *LeaseDeleteRequest) EqualMessageVT(thatMsg any) bool {
449
- that, ok := thatMsg.(*LeaseDeleteRequest)
450
- if !ok {
451
- return false
452
- }
453
- return this.EqualVT(that)
454
-}
455
-func (this *LeaseDeleteResponse) EqualVT(that *LeaseDeleteResponse) bool {
456
- if this == that {
457
- return true
458
- } else if this == nil || that == nil {
459
- return false
460
- }
461
- if this.Code != that.Code {
462
- return false
463
- }
464
- return string(this.unknownFields) == string(that.unknownFields)
465
-}
466
-
467
-func (this *LeaseDeleteResponse) EqualMessageVT(thatMsg any) bool {
468
- that, ok := thatMsg.(*LeaseDeleteResponse)
469
- if !ok {
470
- return false
471
- }
472
- return this.EqualVT(that)
473
-}
474
-func (this *ConnectionRequest) EqualVT(that *ConnectionRequest) bool {
475
- if this == that {
476
- return true
477
- } else if this == nil || that == nil {
478
- return false
479
- }
480
- if this.LeaseId != that.LeaseId {
481
- return false
482
- }
483
- if !this.ClientIdentity.EqualVT(that.ClientIdentity) {
484
- return false
485
- }
486
- return string(this.unknownFields) == string(that.unknownFields)
487
-}
488
-
489
-func (this *ConnectionRequest) EqualMessageVT(thatMsg any) bool {
490
- that, ok := thatMsg.(*ConnectionRequest)
491
- if !ok {
492
- return false
493
- }
494
- return this.EqualVT(that)
495
-}
496
-func (this *ConnectionResponse) EqualVT(that *ConnectionResponse) bool {
497
- if this == that {
498
- return true
499
- } else if this == nil || that == nil {
500
- return false
501
- }
502
- if this.Code != that.Code {
503
- return false
504
- }
505
- return string(this.unknownFields) == string(that.unknownFields)
506
-}
507
-
508
-func (this *ConnectionResponse) EqualMessageVT(thatMsg any) bool {
509
- that, ok := thatMsg.(*ConnectionResponse)
510
- if !ok {
511
- return false
512
- }
513
- return this.EqualVT(that)
514
-}
515
-func (m *Packet) MarshalVT() (dAtA []byte, err error) {
516
- if m == nil {
517
- return nil, nil
518
- }
519
- size := m.SizeVT()
520
- dAtA = make([]byte, size)
521
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
522
- if err != nil {
523
- return nil, err
524
- }
525
- return dAtA[:n], nil
526
-}
527
-
528
-func (m *Packet) MarshalToVT(dAtA []byte) (int, error) {
529
- size := m.SizeVT()
530
- return m.MarshalToSizedBufferVT(dAtA[:size])
531
-}
532
-
533
-func (m *Packet) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
534
- if m == nil {
535
- return 0, nil
536
- }
537
- i := len(dAtA)
538
- _ = i
539
- var l int
540
- _ = l
541
- if m.unknownFields != nil {
542
- i -= len(m.unknownFields)
543
- copy(dAtA[i:], m.unknownFields)
544
- }
545
- if len(m.Payload) > 0 {
546
- i -= len(m.Payload)
547
- copy(dAtA[i:], m.Payload)
548
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Payload)))
549
- i--
550
- dAtA[i] = 0x12
551
- }
552
- if m.Type != 0 {
553
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Type))
554
- i--
555
- dAtA[i] = 0x8
556
- }
557
- return len(dAtA) - i, nil
558
-}
559
-
560
-func (m *RelayInfo) MarshalVT() (dAtA []byte, err error) {
561
- if m == nil {
562
- return nil, nil
563
- }
564
- size := m.SizeVT()
565
- dAtA = make([]byte, size)
566
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
567
- if err != nil {
568
- return nil, err
569
- }
570
- return dAtA[:n], nil
571
-}
572
-
573
-func (m *RelayInfo) MarshalToVT(dAtA []byte) (int, error) {
574
- size := m.SizeVT()
575
- return m.MarshalToSizedBufferVT(dAtA[:size])
576
-}
577
-
578
-func (m *RelayInfo) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
579
- if m == nil {
580
- return 0, nil
581
- }
582
- i := len(dAtA)
583
- _ = i
584
- var l int
585
- _ = l
586
- if m.unknownFields != nil {
587
- i -= len(m.unknownFields)
588
- copy(dAtA[i:], m.unknownFields)
589
- }
590
- if len(m.Leases) > 0 {
591
- for iNdEx := len(m.Leases) - 1; iNdEx >= 0; iNdEx-- {
592
- size, err := m.Leases[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
593
- if err != nil {
594
- return 0, err
595
- }
596
- i -= size
597
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
598
- i--
599
- dAtA[i] = 0x1a
600
- }
601
- }
602
- if len(m.Address) > 0 {
603
- for iNdEx := len(m.Address) - 1; iNdEx >= 0; iNdEx-- {
604
- i -= len(m.Address[iNdEx])
605
- copy(dAtA[i:], m.Address[iNdEx])
606
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Address[iNdEx])))
607
- i--
608
- dAtA[i] = 0x12
609
- }
610
- }
611
- if m.Identity != nil {
612
- size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
613
- if err != nil {
614
- return 0, err
615
- }
616
- i -= size
617
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
618
- i--
619
- dAtA[i] = 0xa
620
- }
621
- return len(dAtA) - i, nil
622
-}
623
-
624
-func (m *RelayInfoRequest) MarshalVT() (dAtA []byte, err error) {
625
- if m == nil {
626
- return nil, nil
627
- }
628
- size := m.SizeVT()
629
- dAtA = make([]byte, size)
630
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
631
- if err != nil {
632
- return nil, err
633
- }
634
- return dAtA[:n], nil
635
-}
636
-
637
-func (m *RelayInfoRequest) MarshalToVT(dAtA []byte) (int, error) {
638
- size := m.SizeVT()
639
- return m.MarshalToSizedBufferVT(dAtA[:size])
640
-}
641
-
642
-func (m *RelayInfoRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
643
- if m == nil {
644
- return 0, nil
645
- }
646
- i := len(dAtA)
647
- _ = i
648
- var l int
649
- _ = l
650
- if m.unknownFields != nil {
651
- i -= len(m.unknownFields)
652
- copy(dAtA[i:], m.unknownFields)
653
- }
654
- return len(dAtA) - i, nil
655
-}
656
-
657
-func (m *RelayInfoResponse) MarshalVT() (dAtA []byte, err error) {
658
- if m == nil {
659
- return nil, nil
660
- }
661
- size := m.SizeVT()
662
- dAtA = make([]byte, size)
663
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
664
- if err != nil {
665
- return nil, err
666
- }
667
- return dAtA[:n], nil
668
-}
669
-
670
-func (m *RelayInfoResponse) MarshalToVT(dAtA []byte) (int, error) {
671
- size := m.SizeVT()
672
- return m.MarshalToSizedBufferVT(dAtA[:size])
673
-}
674
-
675
-func (m *RelayInfoResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
676
- if m == nil {
677
- return 0, nil
678
- }
679
- i := len(dAtA)
680
- _ = i
681
- var l int
682
- _ = l
683
- if m.unknownFields != nil {
684
- i -= len(m.unknownFields)
685
- copy(dAtA[i:], m.unknownFields)
686
- }
687
- if m.RelayInfo != nil {
688
- size, err := m.RelayInfo.MarshalToSizedBufferVT(dAtA[:i])
689
- if err != nil {
690
- return 0, err
691
- }
692
- i -= size
693
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
694
- i--
695
- dAtA[i] = 0xa
696
- }
697
- return len(dAtA) - i, nil
698
-}
699
-
700
-func (m *Lease) MarshalVT() (dAtA []byte, err error) {
701
- if m == nil {
702
- return nil, nil
703
- }
704
- size := m.SizeVT()
705
- dAtA = make([]byte, size)
706
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
707
- if err != nil {
708
- return nil, err
709
- }
710
- return dAtA[:n], nil
711
-}
712
-
713
-func (m *Lease) MarshalToVT(dAtA []byte) (int, error) {
714
- size := m.SizeVT()
715
- return m.MarshalToSizedBufferVT(dAtA[:size])
716
-}
717
-
718
-func (m *Lease) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
719
- if m == nil {
720
- return 0, nil
721
- }
722
- i := len(dAtA)
723
- _ = i
724
- var l int
725
- _ = l
726
- if m.unknownFields != nil {
727
- i -= len(m.unknownFields)
728
- copy(dAtA[i:], m.unknownFields)
729
- }
730
- if len(m.Metadata) > 0 {
731
- i -= len(m.Metadata)
732
- copy(dAtA[i:], m.Metadata)
733
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Metadata)))
734
- i--
735
- dAtA[i] = 0x2a
736
- }
737
- if len(m.Alpn) > 0 {
738
- for iNdEx := len(m.Alpn) - 1; iNdEx >= 0; iNdEx-- {
739
- i -= len(m.Alpn[iNdEx])
740
- copy(dAtA[i:], m.Alpn[iNdEx])
741
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn[iNdEx])))
742
- i--
743
- dAtA[i] = 0x22
744
- }
745
- }
746
- if len(m.Name) > 0 {
747
- i -= len(m.Name)
748
- copy(dAtA[i:], m.Name)
749
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
750
- i--
751
- dAtA[i] = 0x1a
752
- }
753
- if m.Expires != 0 {
754
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Expires))
755
- i--
756
- dAtA[i] = 0x10
757
- }
758
- if m.Identity != nil {
759
- size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
760
- if err != nil {
761
- return 0, err
762
- }
763
- i -= size
764
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
765
- i--
766
- dAtA[i] = 0xa
767
- }
768
- return len(dAtA) - i, nil
769
-}
770
-
771
-func (m *LeaseUpdateRequest) MarshalVT() (dAtA []byte, err error) {
772
- if m == nil {
773
- return nil, nil
774
- }
775
- size := m.SizeVT()
776
- dAtA = make([]byte, size)
777
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
778
- if err != nil {
779
- return nil, err
780
- }
781
- return dAtA[:n], nil
782
-}
783
-
784
-func (m *LeaseUpdateRequest) MarshalToVT(dAtA []byte) (int, error) {
785
- size := m.SizeVT()
786
- return m.MarshalToSizedBufferVT(dAtA[:size])
787
-}
788
-
789
-func (m *LeaseUpdateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
790
- if m == nil {
791
- return 0, nil
792
- }
793
- i := len(dAtA)
794
- _ = i
795
- var l int
796
- _ = l
797
- if m.unknownFields != nil {
798
- i -= len(m.unknownFields)
799
- copy(dAtA[i:], m.unknownFields)
800
- }
801
- if m.Timestamp != 0 {
802
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
803
- i--
804
- dAtA[i] = 0x18
805
- }
806
- if len(m.Nonce) > 0 {
807
- i -= len(m.Nonce)
808
- copy(dAtA[i:], m.Nonce)
809
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
810
- i--
811
- dAtA[i] = 0x12
812
- }
813
- if m.Lease != nil {
814
- size, err := m.Lease.MarshalToSizedBufferVT(dAtA[:i])
815
- if err != nil {
816
- return 0, err
817
- }
818
- i -= size
819
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
820
- i--
821
- dAtA[i] = 0xa
822
- }
823
- return len(dAtA) - i, nil
824
-}
825
-
826
-func (m *LeaseUpdateResponse) MarshalVT() (dAtA []byte, err error) {
827
- if m == nil {
828
- return nil, nil
829
- }
830
- size := m.SizeVT()
831
- dAtA = make([]byte, size)
832
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
833
- if err != nil {
834
- return nil, err
835
- }
836
- return dAtA[:n], nil
837
-}
838
-
839
-func (m *LeaseUpdateResponse) MarshalToVT(dAtA []byte) (int, error) {
840
- size := m.SizeVT()
841
- return m.MarshalToSizedBufferVT(dAtA[:size])
842
-}
843
-
844
-func (m *LeaseUpdateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
845
- if m == nil {
846
- return 0, nil
847
- }
848
- i := len(dAtA)
849
- _ = i
850
- var l int
851
- _ = l
852
- if m.unknownFields != nil {
853
- i -= len(m.unknownFields)
854
- copy(dAtA[i:], m.unknownFields)
855
- }
856
- if m.Code != 0 {
857
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
858
- i--
859
- dAtA[i] = 0x8
860
- }
861
- return len(dAtA) - i, nil
862
-}
863
-
864
-func (m *LeaseDeleteRequest) MarshalVT() (dAtA []byte, err error) {
865
- if m == nil {
866
- return nil, nil
867
- }
868
- size := m.SizeVT()
869
- dAtA = make([]byte, size)
870
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
871
- if err != nil {
872
- return nil, err
873
- }
874
- return dAtA[:n], nil
875
-}
876
-
877
-func (m *LeaseDeleteRequest) MarshalToVT(dAtA []byte) (int, error) {
878
- size := m.SizeVT()
879
- return m.MarshalToSizedBufferVT(dAtA[:size])
880
-}
881
-
882
-func (m *LeaseDeleteRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
883
- if m == nil {
884
- return 0, nil
885
- }
886
- i := len(dAtA)
887
- _ = i
888
- var l int
889
- _ = l
890
- if m.unknownFields != nil {
891
- i -= len(m.unknownFields)
892
- copy(dAtA[i:], m.unknownFields)
893
- }
894
- if m.Timestamp != 0 {
895
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
896
- i--
897
- dAtA[i] = 0x18
898
- }
899
- if len(m.Nonce) > 0 {
900
- i -= len(m.Nonce)
901
- copy(dAtA[i:], m.Nonce)
902
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
903
- i--
904
- dAtA[i] = 0x12
905
- }
906
- if m.Identity != nil {
907
- size, err := m.Identity.MarshalToSizedBufferVT(dAtA[:i])
908
- if err != nil {
909
- return 0, err
910
- }
911
- i -= size
912
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
913
- i--
914
- dAtA[i] = 0xa
915
- }
916
- return len(dAtA) - i, nil
917
-}
918
-
919
-func (m *LeaseDeleteResponse) MarshalVT() (dAtA []byte, err error) {
920
- if m == nil {
921
- return nil, nil
922
- }
923
- size := m.SizeVT()
924
- dAtA = make([]byte, size)
925
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
926
- if err != nil {
927
- return nil, err
928
- }
929
- return dAtA[:n], nil
930
-}
931
-
932
-func (m *LeaseDeleteResponse) MarshalToVT(dAtA []byte) (int, error) {
933
- size := m.SizeVT()
934
- return m.MarshalToSizedBufferVT(dAtA[:size])
935
-}
936
-
937
-func (m *LeaseDeleteResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
938
- if m == nil {
939
- return 0, nil
940
- }
941
- i := len(dAtA)
942
- _ = i
943
- var l int
944
- _ = l
945
- if m.unknownFields != nil {
946
- i -= len(m.unknownFields)
947
- copy(dAtA[i:], m.unknownFields)
948
- }
949
- if m.Code != 0 {
950
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
951
- i--
952
- dAtA[i] = 0x8
953
- }
954
- return len(dAtA) - i, nil
955
-}
956
-
957
-func (m *ConnectionRequest) MarshalVT() (dAtA []byte, err error) {
958
- if m == nil {
959
- return nil, nil
960
- }
961
- size := m.SizeVT()
962
- dAtA = make([]byte, size)
963
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
964
- if err != nil {
965
- return nil, err
966
- }
967
- return dAtA[:n], nil
968
-}
969
-
970
-func (m *ConnectionRequest) MarshalToVT(dAtA []byte) (int, error) {
971
- size := m.SizeVT()
972
- return m.MarshalToSizedBufferVT(dAtA[:size])
973
-}
974
-
975
-func (m *ConnectionRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
976
- if m == nil {
977
- return 0, nil
978
- }
979
- i := len(dAtA)
980
- _ = i
981
- var l int
982
- _ = l
983
- if m.unknownFields != nil {
984
- i -= len(m.unknownFields)
985
- copy(dAtA[i:], m.unknownFields)
986
- }
987
- if m.ClientIdentity != nil {
988
- size, err := m.ClientIdentity.MarshalToSizedBufferVT(dAtA[:i])
989
- if err != nil {
990
- return 0, err
991
- }
992
- i -= size
993
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
994
- i--
995
- dAtA[i] = 0x12
996
- }
997
- if len(m.LeaseId) > 0 {
998
- i -= len(m.LeaseId)
999
- copy(dAtA[i:], m.LeaseId)
1000
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LeaseId)))
1001
- i--
1002
- dAtA[i] = 0xa
1003
- }
1004
- return len(dAtA) - i, nil
1005
-}
1006
-
1007
-func (m *ConnectionResponse) MarshalVT() (dAtA []byte, err error) {
1008
- if m == nil {
1009
- return nil, nil
1010
- }
1011
- size := m.SizeVT()
1012
- dAtA = make([]byte, size)
1013
- n, err := m.MarshalToSizedBufferVT(dAtA[:size])
1014
- if err != nil {
1015
- return nil, err
1016
- }
1017
- return dAtA[:n], nil
1018
-}
1019
-
1020
-func (m *ConnectionResponse) MarshalToVT(dAtA []byte) (int, error) {
1021
- size := m.SizeVT()
1022
- return m.MarshalToSizedBufferVT(dAtA[:size])
1023
-}
1024
-
1025
-func (m *ConnectionResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
1026
- if m == nil {
1027
- return 0, nil
1028
- }
1029
- i := len(dAtA)
1030
- _ = i
1031
- var l int
1032
- _ = l
1033
- if m.unknownFields != nil {
1034
- i -= len(m.unknownFields)
1035
- copy(dAtA[i:], m.unknownFields)
1036
- }
1037
- if m.Code != 0 {
1038
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
1039
- i--
1040
- dAtA[i] = 0x8
1041
- }
1042
- return len(dAtA) - i, nil
1043
-}
1044
-
1045
-func (m *Packet) MarshalVTStrict() (dAtA []byte, err error) {
1046
- if m == nil {
1047
- return nil, nil
1048
- }
1049
- size := m.SizeVT()
1050
- dAtA = make([]byte, size)
1051
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1052
- if err != nil {
1053
- return nil, err
1054
- }
1055
- return dAtA[:n], nil
1056
-}
1057
-
1058
-func (m *Packet) MarshalToVTStrict(dAtA []byte) (int, error) {
1059
- size := m.SizeVT()
1060
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1061
-}
1062
-
1063
-func (m *Packet) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1064
- if m == nil {
1065
- return 0, nil
1066
- }
1067
- i := len(dAtA)
1068
- _ = i
1069
- var l int
1070
- _ = l
1071
- if m.unknownFields != nil {
1072
- i -= len(m.unknownFields)
1073
- copy(dAtA[i:], m.unknownFields)
1074
- }
1075
- if len(m.Payload) > 0 {
1076
- i -= len(m.Payload)
1077
- copy(dAtA[i:], m.Payload)
1078
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Payload)))
1079
- i--
1080
- dAtA[i] = 0x12
1081
- }
1082
- if m.Type != 0 {
1083
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Type))
1084
- i--
1085
- dAtA[i] = 0x8
1086
- }
1087
- return len(dAtA) - i, nil
1088
-}
1089
-
1090
-func (m *RelayInfo) MarshalVTStrict() (dAtA []byte, err error) {
1091
- if m == nil {
1092
- return nil, nil
1093
- }
1094
- size := m.SizeVT()
1095
- dAtA = make([]byte, size)
1096
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1097
- if err != nil {
1098
- return nil, err
1099
- }
1100
- return dAtA[:n], nil
1101
-}
1102
-
1103
-func (m *RelayInfo) MarshalToVTStrict(dAtA []byte) (int, error) {
1104
- size := m.SizeVT()
1105
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1106
-}
1107
-
1108
-func (m *RelayInfo) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1109
- if m == nil {
1110
- return 0, nil
1111
- }
1112
- i := len(dAtA)
1113
- _ = i
1114
- var l int
1115
- _ = l
1116
- if m.unknownFields != nil {
1117
- i -= len(m.unknownFields)
1118
- copy(dAtA[i:], m.unknownFields)
1119
- }
1120
- if len(m.Leases) > 0 {
1121
- for iNdEx := len(m.Leases) - 1; iNdEx >= 0; iNdEx-- {
1122
- size, err := m.Leases[iNdEx].MarshalToSizedBufferVTStrict(dAtA[:i])
1123
- if err != nil {
1124
- return 0, err
1125
- }
1126
- i -= size
1127
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1128
- i--
1129
- dAtA[i] = 0x1a
1130
- }
1131
- }
1132
- if len(m.Address) > 0 {
1133
- for iNdEx := len(m.Address) - 1; iNdEx >= 0; iNdEx-- {
1134
- i -= len(m.Address[iNdEx])
1135
- copy(dAtA[i:], m.Address[iNdEx])
1136
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Address[iNdEx])))
1137
- i--
1138
- dAtA[i] = 0x12
1139
- }
1140
- }
1141
- if m.Identity != nil {
1142
- size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1143
- if err != nil {
1144
- return 0, err
1145
- }
1146
- i -= size
1147
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1148
- i--
1149
- dAtA[i] = 0xa
1150
- }
1151
- return len(dAtA) - i, nil
1152
-}
1153
-
1154
-func (m *RelayInfoRequest) MarshalVTStrict() (dAtA []byte, err error) {
1155
- if m == nil {
1156
- return nil, nil
1157
- }
1158
- size := m.SizeVT()
1159
- dAtA = make([]byte, size)
1160
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1161
- if err != nil {
1162
- return nil, err
1163
- }
1164
- return dAtA[:n], nil
1165
-}
1166
-
1167
-func (m *RelayInfoRequest) MarshalToVTStrict(dAtA []byte) (int, error) {
1168
- size := m.SizeVT()
1169
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1170
-}
1171
-
1172
-func (m *RelayInfoRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1173
- if m == nil {
1174
- return 0, nil
1175
- }
1176
- i := len(dAtA)
1177
- _ = i
1178
- var l int
1179
- _ = l
1180
- if m.unknownFields != nil {
1181
- i -= len(m.unknownFields)
1182
- copy(dAtA[i:], m.unknownFields)
1183
- }
1184
- return len(dAtA) - i, nil
1185
-}
1186
-
1187
-func (m *RelayInfoResponse) MarshalVTStrict() (dAtA []byte, err error) {
1188
- if m == nil {
1189
- return nil, nil
1190
- }
1191
- size := m.SizeVT()
1192
- dAtA = make([]byte, size)
1193
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1194
- if err != nil {
1195
- return nil, err
1196
- }
1197
- return dAtA[:n], nil
1198
-}
1199
-
1200
-func (m *RelayInfoResponse) MarshalToVTStrict(dAtA []byte) (int, error) {
1201
- size := m.SizeVT()
1202
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1203
-}
1204
-
1205
-func (m *RelayInfoResponse) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1206
- if m == nil {
1207
- return 0, nil
1208
- }
1209
- i := len(dAtA)
1210
- _ = i
1211
- var l int
1212
- _ = l
1213
- if m.unknownFields != nil {
1214
- i -= len(m.unknownFields)
1215
- copy(dAtA[i:], m.unknownFields)
1216
- }
1217
- if m.RelayInfo != nil {
1218
- size, err := m.RelayInfo.MarshalToSizedBufferVTStrict(dAtA[:i])
1219
- if err != nil {
1220
- return 0, err
1221
- }
1222
- i -= size
1223
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1224
- i--
1225
- dAtA[i] = 0xa
1226
- }
1227
- return len(dAtA) - i, nil
1228
-}
1229
-
1230
-func (m *Lease) MarshalVTStrict() (dAtA []byte, err error) {
1231
- if m == nil {
1232
- return nil, nil
1233
- }
1234
- size := m.SizeVT()
1235
- dAtA = make([]byte, size)
1236
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1237
- if err != nil {
1238
- return nil, err
1239
- }
1240
- return dAtA[:n], nil
1241
-}
1242
-
1243
-func (m *Lease) MarshalToVTStrict(dAtA []byte) (int, error) {
1244
- size := m.SizeVT()
1245
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1246
-}
1247
-
1248
-func (m *Lease) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1249
- if m == nil {
1250
- return 0, nil
1251
- }
1252
- i := len(dAtA)
1253
- _ = i
1254
- var l int
1255
- _ = l
1256
- if m.unknownFields != nil {
1257
- i -= len(m.unknownFields)
1258
- copy(dAtA[i:], m.unknownFields)
1259
- }
1260
- if len(m.Metadata) > 0 {
1261
- i -= len(m.Metadata)
1262
- copy(dAtA[i:], m.Metadata)
1263
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Metadata)))
1264
- i--
1265
- dAtA[i] = 0x2a
1266
- }
1267
- if len(m.Alpn) > 0 {
1268
- for iNdEx := len(m.Alpn) - 1; iNdEx >= 0; iNdEx-- {
1269
- i -= len(m.Alpn[iNdEx])
1270
- copy(dAtA[i:], m.Alpn[iNdEx])
1271
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Alpn[iNdEx])))
1272
- i--
1273
- dAtA[i] = 0x22
1274
- }
1275
- }
1276
- if len(m.Name) > 0 {
1277
- i -= len(m.Name)
1278
- copy(dAtA[i:], m.Name)
1279
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
1280
- i--
1281
- dAtA[i] = 0x1a
1282
- }
1283
- if m.Expires != 0 {
1284
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Expires))
1285
- i--
1286
- dAtA[i] = 0x10
1287
- }
1288
- if m.Identity != nil {
1289
- size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1290
- if err != nil {
1291
- return 0, err
1292
- }
1293
- i -= size
1294
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1295
- i--
1296
- dAtA[i] = 0xa
1297
- }
1298
- return len(dAtA) - i, nil
1299
-}
1300
-
1301
-func (m *LeaseUpdateRequest) MarshalVTStrict() (dAtA []byte, err error) {
1302
- if m == nil {
1303
- return nil, nil
1304
- }
1305
- size := m.SizeVT()
1306
- dAtA = make([]byte, size)
1307
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1308
- if err != nil {
1309
- return nil, err
1310
- }
1311
- return dAtA[:n], nil
1312
-}
1313
-
1314
-func (m *LeaseUpdateRequest) MarshalToVTStrict(dAtA []byte) (int, error) {
1315
- size := m.SizeVT()
1316
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1317
-}
1318
-
1319
-func (m *LeaseUpdateRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1320
- if m == nil {
1321
- return 0, nil
1322
- }
1323
- i := len(dAtA)
1324
- _ = i
1325
- var l int
1326
- _ = l
1327
- if m.unknownFields != nil {
1328
- i -= len(m.unknownFields)
1329
- copy(dAtA[i:], m.unknownFields)
1330
- }
1331
- if m.Timestamp != 0 {
1332
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
1333
- i--
1334
- dAtA[i] = 0x18
1335
- }
1336
- if len(m.Nonce) > 0 {
1337
- i -= len(m.Nonce)
1338
- copy(dAtA[i:], m.Nonce)
1339
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
1340
- i--
1341
- dAtA[i] = 0x12
1342
- }
1343
- if m.Lease != nil {
1344
- size, err := m.Lease.MarshalToSizedBufferVTStrict(dAtA[:i])
1345
- if err != nil {
1346
- return 0, err
1347
- }
1348
- i -= size
1349
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1350
- i--
1351
- dAtA[i] = 0xa
1352
- }
1353
- return len(dAtA) - i, nil
1354
-}
1355
-
1356
-func (m *LeaseUpdateResponse) MarshalVTStrict() (dAtA []byte, err error) {
1357
- if m == nil {
1358
- return nil, nil
1359
- }
1360
- size := m.SizeVT()
1361
- dAtA = make([]byte, size)
1362
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1363
- if err != nil {
1364
- return nil, err
1365
- }
1366
- return dAtA[:n], nil
1367
-}
1368
-
1369
-func (m *LeaseUpdateResponse) MarshalToVTStrict(dAtA []byte) (int, error) {
1370
- size := m.SizeVT()
1371
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1372
-}
1373
-
1374
-func (m *LeaseUpdateResponse) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1375
- if m == nil {
1376
- return 0, nil
1377
- }
1378
- i := len(dAtA)
1379
- _ = i
1380
- var l int
1381
- _ = l
1382
- if m.unknownFields != nil {
1383
- i -= len(m.unknownFields)
1384
- copy(dAtA[i:], m.unknownFields)
1385
- }
1386
- if m.Code != 0 {
1387
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
1388
- i--
1389
- dAtA[i] = 0x8
1390
- }
1391
- return len(dAtA) - i, nil
1392
-}
1393
-
1394
-func (m *LeaseDeleteRequest) MarshalVTStrict() (dAtA []byte, err error) {
1395
- if m == nil {
1396
- return nil, nil
1397
- }
1398
- size := m.SizeVT()
1399
- dAtA = make([]byte, size)
1400
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1401
- if err != nil {
1402
- return nil, err
1403
- }
1404
- return dAtA[:n], nil
1405
-}
1406
-
1407
-func (m *LeaseDeleteRequest) MarshalToVTStrict(dAtA []byte) (int, error) {
1408
- size := m.SizeVT()
1409
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1410
-}
1411
-
1412
-func (m *LeaseDeleteRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1413
- if m == nil {
1414
- return 0, nil
1415
- }
1416
- i := len(dAtA)
1417
- _ = i
1418
- var l int
1419
- _ = l
1420
- if m.unknownFields != nil {
1421
- i -= len(m.unknownFields)
1422
- copy(dAtA[i:], m.unknownFields)
1423
- }
1424
- if m.Timestamp != 0 {
1425
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Timestamp))
1426
- i--
1427
- dAtA[i] = 0x18
1428
- }
1429
- if len(m.Nonce) > 0 {
1430
- i -= len(m.Nonce)
1431
- copy(dAtA[i:], m.Nonce)
1432
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Nonce)))
1433
- i--
1434
- dAtA[i] = 0x12
1435
- }
1436
- if m.Identity != nil {
1437
- size, err := m.Identity.MarshalToSizedBufferVTStrict(dAtA[:i])
1438
- if err != nil {
1439
- return 0, err
1440
- }
1441
- i -= size
1442
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1443
- i--
1444
- dAtA[i] = 0xa
1445
- }
1446
- return len(dAtA) - i, nil
1447
-}
1448
-
1449
-func (m *LeaseDeleteResponse) MarshalVTStrict() (dAtA []byte, err error) {
1450
- if m == nil {
1451
- return nil, nil
1452
- }
1453
- size := m.SizeVT()
1454
- dAtA = make([]byte, size)
1455
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1456
- if err != nil {
1457
- return nil, err
1458
- }
1459
- return dAtA[:n], nil
1460
-}
1461
-
1462
-func (m *LeaseDeleteResponse) MarshalToVTStrict(dAtA []byte) (int, error) {
1463
- size := m.SizeVT()
1464
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1465
-}
1466
-
1467
-func (m *LeaseDeleteResponse) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1468
- if m == nil {
1469
- return 0, nil
1470
- }
1471
- i := len(dAtA)
1472
- _ = i
1473
- var l int
1474
- _ = l
1475
- if m.unknownFields != nil {
1476
- i -= len(m.unknownFields)
1477
- copy(dAtA[i:], m.unknownFields)
1478
- }
1479
- if m.Code != 0 {
1480
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
1481
- i--
1482
- dAtA[i] = 0x8
1483
- }
1484
- return len(dAtA) - i, nil
1485
-}
1486
-
1487
-func (m *ConnectionRequest) MarshalVTStrict() (dAtA []byte, err error) {
1488
- if m == nil {
1489
- return nil, nil
1490
- }
1491
- size := m.SizeVT()
1492
- dAtA = make([]byte, size)
1493
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1494
- if err != nil {
1495
- return nil, err
1496
- }
1497
- return dAtA[:n], nil
1498
-}
1499
-
1500
-func (m *ConnectionRequest) MarshalToVTStrict(dAtA []byte) (int, error) {
1501
- size := m.SizeVT()
1502
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1503
-}
1504
-
1505
-func (m *ConnectionRequest) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1506
- if m == nil {
1507
- return 0, nil
1508
- }
1509
- i := len(dAtA)
1510
- _ = i
1511
- var l int
1512
- _ = l
1513
- if m.unknownFields != nil {
1514
- i -= len(m.unknownFields)
1515
- copy(dAtA[i:], m.unknownFields)
1516
- }
1517
- if m.ClientIdentity != nil {
1518
- size, err := m.ClientIdentity.MarshalToSizedBufferVTStrict(dAtA[:i])
1519
- if err != nil {
1520
- return 0, err
1521
- }
1522
- i -= size
1523
- i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
1524
- i--
1525
- dAtA[i] = 0x12
1526
- }
1527
- if len(m.LeaseId) > 0 {
1528
- i -= len(m.LeaseId)
1529
- copy(dAtA[i:], m.LeaseId)
1530
- i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LeaseId)))
1531
- i--
1532
- dAtA[i] = 0xa
1533
- }
1534
- return len(dAtA) - i, nil
1535
-}
1536
-
1537
-func (m *ConnectionResponse) MarshalVTStrict() (dAtA []byte, err error) {
1538
- if m == nil {
1539
- return nil, nil
1540
- }
1541
- size := m.SizeVT()
1542
- dAtA = make([]byte, size)
1543
- n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size])
1544
- if err != nil {
1545
- return nil, err
1546
- }
1547
- return dAtA[:n], nil
1548
-}
1549
-
1550
-func (m *ConnectionResponse) MarshalToVTStrict(dAtA []byte) (int, error) {
1551
- size := m.SizeVT()
1552
- return m.MarshalToSizedBufferVTStrict(dAtA[:size])
1553
-}
1554
-
1555
-func (m *ConnectionResponse) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) {
1556
- if m == nil {
1557
- return 0, nil
1558
- }
1559
- i := len(dAtA)
1560
- _ = i
1561
- var l int
1562
- _ = l
1563
- if m.unknownFields != nil {
1564
- i -= len(m.unknownFields)
1565
- copy(dAtA[i:], m.unknownFields)
1566
- }
1567
- if m.Code != 0 {
1568
- i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Code))
1569
- i--
1570
- dAtA[i] = 0x8
1571
- }
1572
- return len(dAtA) - i, nil
1573
-}
1574
-
1575
-func (m *Packet) SizeVT() (n int) {
1576
- if m == nil {
1577
- return 0
1578
- }
1579
- var l int
1580
- _ = l
1581
- if m.Type != 0 {
1582
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Type))
1583
- }
1584
- l = len(m.Payload)
1585
- if l > 0 {
1586
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1587
- }
1588
- n += len(m.unknownFields)
1589
- return n
1590
-}
1591
-
1592
-func (m *RelayInfo) SizeVT() (n int) {
1593
- if m == nil {
1594
- return 0
1595
- }
1596
- var l int
1597
- _ = l
1598
- if m.Identity != nil {
1599
- l = m.Identity.SizeVT()
1600
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1601
- }
1602
- if len(m.Address) > 0 {
1603
- for _, s := range m.Address {
1604
- l = len(s)
1605
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1606
- }
1607
- }
1608
- if len(m.Leases) > 0 {
1609
- for _, e := range m.Leases {
1610
- l = e.SizeVT()
1611
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1612
- }
1613
- }
1614
- n += len(m.unknownFields)
1615
- return n
1616
-}
1617
-
1618
-func (m *RelayInfoRequest) SizeVT() (n int) {
1619
- if m == nil {
1620
- return 0
1621
- }
1622
- var l int
1623
- _ = l
1624
- n += len(m.unknownFields)
1625
- return n
1626
-}
1627
-
1628
-func (m *RelayInfoResponse) SizeVT() (n int) {
1629
- if m == nil {
1630
- return 0
1631
- }
1632
- var l int
1633
- _ = l
1634
- if m.RelayInfo != nil {
1635
- l = m.RelayInfo.SizeVT()
1636
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1637
- }
1638
- n += len(m.unknownFields)
1639
- return n
1640
-}
1641
-
1642
-func (m *Lease) SizeVT() (n int) {
1643
- if m == nil {
1644
- return 0
1645
- }
1646
- var l int
1647
- _ = l
1648
- if m.Identity != nil {
1649
- l = m.Identity.SizeVT()
1650
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1651
- }
1652
- if m.Expires != 0 {
1653
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Expires))
1654
- }
1655
- l = len(m.Name)
1656
- if l > 0 {
1657
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1658
- }
1659
- if len(m.Alpn) > 0 {
1660
- for _, s := range m.Alpn {
1661
- l = len(s)
1662
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1663
- }
1664
- }
1665
- l = len(m.Metadata)
1666
- if l > 0 {
1667
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1668
- }
1669
- n += len(m.unknownFields)
1670
- return n
1671
-}
1672
-
1673
-func (m *LeaseUpdateRequest) SizeVT() (n int) {
1674
- if m == nil {
1675
- return 0
1676
- }
1677
- var l int
1678
- _ = l
1679
- if m.Lease != nil {
1680
- l = m.Lease.SizeVT()
1681
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1682
- }
1683
- l = len(m.Nonce)
1684
- if l > 0 {
1685
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1686
- }
1687
- if m.Timestamp != 0 {
1688
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp))
1689
- }
1690
- n += len(m.unknownFields)
1691
- return n
1692
-}
1693
-
1694
-func (m *LeaseUpdateResponse) SizeVT() (n int) {
1695
- if m == nil {
1696
- return 0
1697
- }
1698
- var l int
1699
- _ = l
1700
- if m.Code != 0 {
1701
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Code))
1702
- }
1703
- n += len(m.unknownFields)
1704
- return n
1705
-}
1706
-
1707
-func (m *LeaseDeleteRequest) SizeVT() (n int) {
1708
- if m == nil {
1709
- return 0
1710
- }
1711
- var l int
1712
- _ = l
1713
- if m.Identity != nil {
1714
- l = m.Identity.SizeVT()
1715
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1716
- }
1717
- l = len(m.Nonce)
1718
- if l > 0 {
1719
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1720
- }
1721
- if m.Timestamp != 0 {
1722
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Timestamp))
1723
- }
1724
- n += len(m.unknownFields)
1725
- return n
1726
-}
1727
-
1728
-func (m *LeaseDeleteResponse) SizeVT() (n int) {
1729
- if m == nil {
1730
- return 0
1731
- }
1732
- var l int
1733
- _ = l
1734
- if m.Code != 0 {
1735
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Code))
1736
- }
1737
- n += len(m.unknownFields)
1738
- return n
1739
-}
1740
-
1741
-func (m *ConnectionRequest) SizeVT() (n int) {
1742
- if m == nil {
1743
- return 0
1744
- }
1745
- var l int
1746
- _ = l
1747
- l = len(m.LeaseId)
1748
- if l > 0 {
1749
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1750
- }
1751
- if m.ClientIdentity != nil {
1752
- l = m.ClientIdentity.SizeVT()
1753
- n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
1754
- }
1755
- n += len(m.unknownFields)
1756
- return n
1757
-}
1758
-
1759
-func (m *ConnectionResponse) SizeVT() (n int) {
1760
- if m == nil {
1761
- return 0
1762
- }
1763
- var l int
1764
- _ = l
1765
- if m.Code != 0 {
1766
- n += 1 + protohelpers.SizeOfVarint(uint64(m.Code))
1767
- }
1768
- n += len(m.unknownFields)
1769
- return n
1770
-}
1771
-
1772
-func (m *Packet) UnmarshalVT(dAtA []byte) error {
1773
- l := len(dAtA)
1774
- iNdEx := 0
1775
- for iNdEx < l {
1776
- preIndex := iNdEx
1777
- var wire uint64
1778
- for shift := uint(0); ; shift += 7 {
1779
- if shift >= 64 {
1780
- return protohelpers.ErrIntOverflow
1781
- }
1782
- if iNdEx >= l {
1783
- return io.ErrUnexpectedEOF
1784
- }
1785
- b := dAtA[iNdEx]
1786
- iNdEx++
1787
- wire |= uint64(b&0x7F) << shift
1788
- if b < 0x80 {
1789
- break
1790
- }
1791
- }
1792
- fieldNum := int32(wire >> 3)
1793
- wireType := int(wire & 0x7)
1794
- if wireType == 4 {
1795
- return fmt.Errorf("proto: Packet: wiretype end group for non-group")
1796
- }
1797
- if fieldNum <= 0 {
1798
- return fmt.Errorf("proto: Packet: illegal tag %d (wire type %d)", fieldNum, wire)
1799
- }
1800
- switch fieldNum {
1801
- case 1:
1802
- if wireType != 0 {
1803
- return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
1804
- }
1805
- m.Type = 0
1806
- for shift := uint(0); ; shift += 7 {
1807
- if shift >= 64 {
1808
- return protohelpers.ErrIntOverflow
1809
- }
1810
- if iNdEx >= l {
1811
- return io.ErrUnexpectedEOF
1812
- }
1813
- b := dAtA[iNdEx]
1814
- iNdEx++
1815
- m.Type |= PacketType(b&0x7F) << shift
1816
- if b < 0x80 {
1817
- break
1818
- }
1819
- }
1820
- case 2:
1821
- if wireType != 2 {
1822
- return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType)
1823
- }
1824
- var byteLen int
1825
- for shift := uint(0); ; shift += 7 {
1826
- if shift >= 64 {
1827
- return protohelpers.ErrIntOverflow
1828
- }
1829
- if iNdEx >= l {
1830
- return io.ErrUnexpectedEOF
1831
- }
1832
- b := dAtA[iNdEx]
1833
- iNdEx++
1834
- byteLen |= int(b&0x7F) << shift
1835
- if b < 0x80 {
1836
- break
1837
- }
1838
- }
1839
- if byteLen < 0 {
1840
- return protohelpers.ErrInvalidLength
1841
- }
1842
- postIndex := iNdEx + byteLen
1843
- if postIndex < 0 {
1844
- return protohelpers.ErrInvalidLength
1845
- }
1846
- if postIndex > l {
1847
- return io.ErrUnexpectedEOF
1848
- }
1849
- m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...)
1850
- if m.Payload == nil {
1851
- m.Payload = []byte{}
1852
- }
1853
- iNdEx = postIndex
1854
- default:
1855
- iNdEx = preIndex
1856
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
1857
- if err != nil {
1858
- return err
1859
- }
1860
- if (skippy < 0) || (iNdEx+skippy) < 0 {
1861
- return protohelpers.ErrInvalidLength
1862
- }
1863
- if (iNdEx + skippy) > l {
1864
- return io.ErrUnexpectedEOF
1865
- }
1866
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
1867
- iNdEx += skippy
1868
- }
1869
- }
1870
-
1871
- if iNdEx > l {
1872
- return io.ErrUnexpectedEOF
1873
- }
1874
- return nil
1875
-}
1876
-func (m *RelayInfo) UnmarshalVT(dAtA []byte) error {
1877
- l := len(dAtA)
1878
- iNdEx := 0
1879
- for iNdEx < l {
1880
- preIndex := iNdEx
1881
- var wire uint64
1882
- for shift := uint(0); ; shift += 7 {
1883
- if shift >= 64 {
1884
- return protohelpers.ErrIntOverflow
1885
- }
1886
- if iNdEx >= l {
1887
- return io.ErrUnexpectedEOF
1888
- }
1889
- b := dAtA[iNdEx]
1890
- iNdEx++
1891
- wire |= uint64(b&0x7F) << shift
1892
- if b < 0x80 {
1893
- break
1894
- }
1895
- }
1896
- fieldNum := int32(wire >> 3)
1897
- wireType := int(wire & 0x7)
1898
- if wireType == 4 {
1899
- return fmt.Errorf("proto: RelayInfo: wiretype end group for non-group")
1900
- }
1901
- if fieldNum <= 0 {
1902
- return fmt.Errorf("proto: RelayInfo: illegal tag %d (wire type %d)", fieldNum, wire)
1903
- }
1904
- switch fieldNum {
1905
- case 1:
1906
- if wireType != 2 {
1907
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
1908
- }
1909
- var msglen int
1910
- for shift := uint(0); ; shift += 7 {
1911
- if shift >= 64 {
1912
- return protohelpers.ErrIntOverflow
1913
- }
1914
- if iNdEx >= l {
1915
- return io.ErrUnexpectedEOF
1916
- }
1917
- b := dAtA[iNdEx]
1918
- iNdEx++
1919
- msglen |= int(b&0x7F) << shift
1920
- if b < 0x80 {
1921
- break
1922
- }
1923
- }
1924
- if msglen < 0 {
1925
- return protohelpers.ErrInvalidLength
1926
- }
1927
- postIndex := iNdEx + msglen
1928
- if postIndex < 0 {
1929
- return protohelpers.ErrInvalidLength
1930
- }
1931
- if postIndex > l {
1932
- return io.ErrUnexpectedEOF
1933
- }
1934
- if m.Identity == nil {
1935
- m.Identity = &rdsec.Identity{}
1936
- }
1937
- if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
1938
- return err
1939
- }
1940
- iNdEx = postIndex
1941
- case 2:
1942
- if wireType != 2 {
1943
- return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
1944
- }
1945
- var stringLen uint64
1946
- for shift := uint(0); ; shift += 7 {
1947
- if shift >= 64 {
1948
- return protohelpers.ErrIntOverflow
1949
- }
1950
- if iNdEx >= l {
1951
- return io.ErrUnexpectedEOF
1952
- }
1953
- b := dAtA[iNdEx]
1954
- iNdEx++
1955
- stringLen |= uint64(b&0x7F) << shift
1956
- if b < 0x80 {
1957
- break
1958
- }
1959
- }
1960
- intStringLen := int(stringLen)
1961
- if intStringLen < 0 {
1962
- return protohelpers.ErrInvalidLength
1963
- }
1964
- postIndex := iNdEx + intStringLen
1965
- if postIndex < 0 {
1966
- return protohelpers.ErrInvalidLength
1967
- }
1968
- if postIndex > l {
1969
- return io.ErrUnexpectedEOF
1970
- }
1971
- m.Address = append(m.Address, string(dAtA[iNdEx:postIndex]))
1972
- iNdEx = postIndex
1973
- case 3:
1974
- if wireType != 2 {
1975
- return fmt.Errorf("proto: wrong wireType = %d for field Leases", wireType)
1976
- }
1977
- var msglen int
1978
- for shift := uint(0); ; shift += 7 {
1979
- if shift >= 64 {
1980
- return protohelpers.ErrIntOverflow
1981
- }
1982
- if iNdEx >= l {
1983
- return io.ErrUnexpectedEOF
1984
- }
1985
- b := dAtA[iNdEx]
1986
- iNdEx++
1987
- msglen |= int(b&0x7F) << shift
1988
- if b < 0x80 {
1989
- break
1990
- }
1991
- }
1992
- if msglen < 0 {
1993
- return protohelpers.ErrInvalidLength
1994
- }
1995
- postIndex := iNdEx + msglen
1996
- if postIndex < 0 {
1997
- return protohelpers.ErrInvalidLength
1998
- }
1999
- if postIndex > l {
2000
- return io.ErrUnexpectedEOF
2001
- }
2002
- m.Leases = append(m.Leases, &Lease{})
2003
- if err := m.Leases[len(m.Leases)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2004
- return err
2005
- }
2006
- iNdEx = postIndex
2007
- default:
2008
- iNdEx = preIndex
2009
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2010
- if err != nil {
2011
- return err
2012
- }
2013
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2014
- return protohelpers.ErrInvalidLength
2015
- }
2016
- if (iNdEx + skippy) > l {
2017
- return io.ErrUnexpectedEOF
2018
- }
2019
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2020
- iNdEx += skippy
2021
- }
2022
- }
2023
-
2024
- if iNdEx > l {
2025
- return io.ErrUnexpectedEOF
2026
- }
2027
- return nil
2028
-}
2029
-func (m *RelayInfoRequest) UnmarshalVT(dAtA []byte) error {
2030
- l := len(dAtA)
2031
- iNdEx := 0
2032
- for iNdEx < l {
2033
- preIndex := iNdEx
2034
- var wire uint64
2035
- for shift := uint(0); ; shift += 7 {
2036
- if shift >= 64 {
2037
- return protohelpers.ErrIntOverflow
2038
- }
2039
- if iNdEx >= l {
2040
- return io.ErrUnexpectedEOF
2041
- }
2042
- b := dAtA[iNdEx]
2043
- iNdEx++
2044
- wire |= uint64(b&0x7F) << shift
2045
- if b < 0x80 {
2046
- break
2047
- }
2048
- }
2049
- fieldNum := int32(wire >> 3)
2050
- wireType := int(wire & 0x7)
2051
- if wireType == 4 {
2052
- return fmt.Errorf("proto: RelayInfoRequest: wiretype end group for non-group")
2053
- }
2054
- if fieldNum <= 0 {
2055
- return fmt.Errorf("proto: RelayInfoRequest: illegal tag %d (wire type %d)", fieldNum, wire)
2056
- }
2057
- switch fieldNum {
2058
- default:
2059
- iNdEx = preIndex
2060
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2061
- if err != nil {
2062
- return err
2063
- }
2064
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2065
- return protohelpers.ErrInvalidLength
2066
- }
2067
- if (iNdEx + skippy) > l {
2068
- return io.ErrUnexpectedEOF
2069
- }
2070
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2071
- iNdEx += skippy
2072
- }
2073
- }
2074
-
2075
- if iNdEx > l {
2076
- return io.ErrUnexpectedEOF
2077
- }
2078
- return nil
2079
-}
2080
-func (m *RelayInfoResponse) UnmarshalVT(dAtA []byte) error {
2081
- l := len(dAtA)
2082
- iNdEx := 0
2083
- for iNdEx < l {
2084
- preIndex := iNdEx
2085
- var wire uint64
2086
- for shift := uint(0); ; shift += 7 {
2087
- if shift >= 64 {
2088
- return protohelpers.ErrIntOverflow
2089
- }
2090
- if iNdEx >= l {
2091
- return io.ErrUnexpectedEOF
2092
- }
2093
- b := dAtA[iNdEx]
2094
- iNdEx++
2095
- wire |= uint64(b&0x7F) << shift
2096
- if b < 0x80 {
2097
- break
2098
- }
2099
- }
2100
- fieldNum := int32(wire >> 3)
2101
- wireType := int(wire & 0x7)
2102
- if wireType == 4 {
2103
- return fmt.Errorf("proto: RelayInfoResponse: wiretype end group for non-group")
2104
- }
2105
- if fieldNum <= 0 {
2106
- return fmt.Errorf("proto: RelayInfoResponse: illegal tag %d (wire type %d)", fieldNum, wire)
2107
- }
2108
- switch fieldNum {
2109
- case 1:
2110
- if wireType != 2 {
2111
- return fmt.Errorf("proto: wrong wireType = %d for field RelayInfo", wireType)
2112
- }
2113
- var msglen int
2114
- for shift := uint(0); ; shift += 7 {
2115
- if shift >= 64 {
2116
- return protohelpers.ErrIntOverflow
2117
- }
2118
- if iNdEx >= l {
2119
- return io.ErrUnexpectedEOF
2120
- }
2121
- b := dAtA[iNdEx]
2122
- iNdEx++
2123
- msglen |= int(b&0x7F) << shift
2124
- if b < 0x80 {
2125
- break
2126
- }
2127
- }
2128
- if msglen < 0 {
2129
- return protohelpers.ErrInvalidLength
2130
- }
2131
- postIndex := iNdEx + msglen
2132
- if postIndex < 0 {
2133
- return protohelpers.ErrInvalidLength
2134
- }
2135
- if postIndex > l {
2136
- return io.ErrUnexpectedEOF
2137
- }
2138
- if m.RelayInfo == nil {
2139
- m.RelayInfo = &RelayInfo{}
2140
- }
2141
- if err := m.RelayInfo.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2142
- return err
2143
- }
2144
- iNdEx = postIndex
2145
- default:
2146
- iNdEx = preIndex
2147
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2148
- if err != nil {
2149
- return err
2150
- }
2151
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2152
- return protohelpers.ErrInvalidLength
2153
- }
2154
- if (iNdEx + skippy) > l {
2155
- return io.ErrUnexpectedEOF
2156
- }
2157
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2158
- iNdEx += skippy
2159
- }
2160
- }
2161
-
2162
- if iNdEx > l {
2163
- return io.ErrUnexpectedEOF
2164
- }
2165
- return nil
2166
-}
2167
-func (m *Lease) UnmarshalVT(dAtA []byte) error {
2168
- l := len(dAtA)
2169
- iNdEx := 0
2170
- for iNdEx < l {
2171
- preIndex := iNdEx
2172
- var wire uint64
2173
- for shift := uint(0); ; shift += 7 {
2174
- if shift >= 64 {
2175
- return protohelpers.ErrIntOverflow
2176
- }
2177
- if iNdEx >= l {
2178
- return io.ErrUnexpectedEOF
2179
- }
2180
- b := dAtA[iNdEx]
2181
- iNdEx++
2182
- wire |= uint64(b&0x7F) << shift
2183
- if b < 0x80 {
2184
- break
2185
- }
2186
- }
2187
- fieldNum := int32(wire >> 3)
2188
- wireType := int(wire & 0x7)
2189
- if wireType == 4 {
2190
- return fmt.Errorf("proto: Lease: wiretype end group for non-group")
2191
- }
2192
- if fieldNum <= 0 {
2193
- return fmt.Errorf("proto: Lease: illegal tag %d (wire type %d)", fieldNum, wire)
2194
- }
2195
- switch fieldNum {
2196
- case 1:
2197
- if wireType != 2 {
2198
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
2199
- }
2200
- var msglen int
2201
- for shift := uint(0); ; shift += 7 {
2202
- if shift >= 64 {
2203
- return protohelpers.ErrIntOverflow
2204
- }
2205
- if iNdEx >= l {
2206
- return io.ErrUnexpectedEOF
2207
- }
2208
- b := dAtA[iNdEx]
2209
- iNdEx++
2210
- msglen |= int(b&0x7F) << shift
2211
- if b < 0x80 {
2212
- break
2213
- }
2214
- }
2215
- if msglen < 0 {
2216
- return protohelpers.ErrInvalidLength
2217
- }
2218
- postIndex := iNdEx + msglen
2219
- if postIndex < 0 {
2220
- return protohelpers.ErrInvalidLength
2221
- }
2222
- if postIndex > l {
2223
- return io.ErrUnexpectedEOF
2224
- }
2225
- if m.Identity == nil {
2226
- m.Identity = &rdsec.Identity{}
2227
- }
2228
- if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2229
- return err
2230
- }
2231
- iNdEx = postIndex
2232
- case 2:
2233
- if wireType != 0 {
2234
- return fmt.Errorf("proto: wrong wireType = %d for field Expires", wireType)
2235
- }
2236
- m.Expires = 0
2237
- for shift := uint(0); ; shift += 7 {
2238
- if shift >= 64 {
2239
- return protohelpers.ErrIntOverflow
2240
- }
2241
- if iNdEx >= l {
2242
- return io.ErrUnexpectedEOF
2243
- }
2244
- b := dAtA[iNdEx]
2245
- iNdEx++
2246
- m.Expires |= int64(b&0x7F) << shift
2247
- if b < 0x80 {
2248
- break
2249
- }
2250
- }
2251
- case 3:
2252
- if wireType != 2 {
2253
- return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
2254
- }
2255
- var stringLen uint64
2256
- for shift := uint(0); ; shift += 7 {
2257
- if shift >= 64 {
2258
- return protohelpers.ErrIntOverflow
2259
- }
2260
- if iNdEx >= l {
2261
- return io.ErrUnexpectedEOF
2262
- }
2263
- b := dAtA[iNdEx]
2264
- iNdEx++
2265
- stringLen |= uint64(b&0x7F) << shift
2266
- if b < 0x80 {
2267
- break
2268
- }
2269
- }
2270
- intStringLen := int(stringLen)
2271
- if intStringLen < 0 {
2272
- return protohelpers.ErrInvalidLength
2273
- }
2274
- postIndex := iNdEx + intStringLen
2275
- if postIndex < 0 {
2276
- return protohelpers.ErrInvalidLength
2277
- }
2278
- if postIndex > l {
2279
- return io.ErrUnexpectedEOF
2280
- }
2281
- m.Name = string(dAtA[iNdEx:postIndex])
2282
- iNdEx = postIndex
2283
- case 4:
2284
- if wireType != 2 {
2285
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
2286
- }
2287
- var stringLen uint64
2288
- for shift := uint(0); ; shift += 7 {
2289
- if shift >= 64 {
2290
- return protohelpers.ErrIntOverflow
2291
- }
2292
- if iNdEx >= l {
2293
- return io.ErrUnexpectedEOF
2294
- }
2295
- b := dAtA[iNdEx]
2296
- iNdEx++
2297
- stringLen |= uint64(b&0x7F) << shift
2298
- if b < 0x80 {
2299
- break
2300
- }
2301
- }
2302
- intStringLen := int(stringLen)
2303
- if intStringLen < 0 {
2304
- return protohelpers.ErrInvalidLength
2305
- }
2306
- postIndex := iNdEx + intStringLen
2307
- if postIndex < 0 {
2308
- return protohelpers.ErrInvalidLength
2309
- }
2310
- if postIndex > l {
2311
- return io.ErrUnexpectedEOF
2312
- }
2313
- m.Alpn = append(m.Alpn, string(dAtA[iNdEx:postIndex]))
2314
- iNdEx = postIndex
2315
- case 5:
2316
- if wireType != 2 {
2317
- return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType)
2318
- }
2319
- var stringLen uint64
2320
- for shift := uint(0); ; shift += 7 {
2321
- if shift >= 64 {
2322
- return protohelpers.ErrIntOverflow
2323
- }
2324
- if iNdEx >= l {
2325
- return io.ErrUnexpectedEOF
2326
- }
2327
- b := dAtA[iNdEx]
2328
- iNdEx++
2329
- stringLen |= uint64(b&0x7F) << shift
2330
- if b < 0x80 {
2331
- break
2332
- }
2333
- }
2334
- intStringLen := int(stringLen)
2335
- if intStringLen < 0 {
2336
- return protohelpers.ErrInvalidLength
2337
- }
2338
- postIndex := iNdEx + intStringLen
2339
- if postIndex < 0 {
2340
- return protohelpers.ErrInvalidLength
2341
- }
2342
- if postIndex > l {
2343
- return io.ErrUnexpectedEOF
2344
- }
2345
- m.Metadata = string(dAtA[iNdEx:postIndex])
2346
- iNdEx = postIndex
2347
- default:
2348
- iNdEx = preIndex
2349
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2350
- if err != nil {
2351
- return err
2352
- }
2353
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2354
- return protohelpers.ErrInvalidLength
2355
- }
2356
- if (iNdEx + skippy) > l {
2357
- return io.ErrUnexpectedEOF
2358
- }
2359
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2360
- iNdEx += skippy
2361
- }
2362
- }
2363
-
2364
- if iNdEx > l {
2365
- return io.ErrUnexpectedEOF
2366
- }
2367
- return nil
2368
-}
2369
-func (m *LeaseUpdateRequest) UnmarshalVT(dAtA []byte) error {
2370
- l := len(dAtA)
2371
- iNdEx := 0
2372
- for iNdEx < l {
2373
- preIndex := iNdEx
2374
- var wire uint64
2375
- for shift := uint(0); ; shift += 7 {
2376
- if shift >= 64 {
2377
- return protohelpers.ErrIntOverflow
2378
- }
2379
- if iNdEx >= l {
2380
- return io.ErrUnexpectedEOF
2381
- }
2382
- b := dAtA[iNdEx]
2383
- iNdEx++
2384
- wire |= uint64(b&0x7F) << shift
2385
- if b < 0x80 {
2386
- break
2387
- }
2388
- }
2389
- fieldNum := int32(wire >> 3)
2390
- wireType := int(wire & 0x7)
2391
- if wireType == 4 {
2392
- return fmt.Errorf("proto: LeaseUpdateRequest: wiretype end group for non-group")
2393
- }
2394
- if fieldNum <= 0 {
2395
- return fmt.Errorf("proto: LeaseUpdateRequest: illegal tag %d (wire type %d)", fieldNum, wire)
2396
- }
2397
- switch fieldNum {
2398
- case 1:
2399
- if wireType != 2 {
2400
- return fmt.Errorf("proto: wrong wireType = %d for field Lease", wireType)
2401
- }
2402
- var msglen int
2403
- for shift := uint(0); ; shift += 7 {
2404
- if shift >= 64 {
2405
- return protohelpers.ErrIntOverflow
2406
- }
2407
- if iNdEx >= l {
2408
- return io.ErrUnexpectedEOF
2409
- }
2410
- b := dAtA[iNdEx]
2411
- iNdEx++
2412
- msglen |= int(b&0x7F) << shift
2413
- if b < 0x80 {
2414
- break
2415
- }
2416
- }
2417
- if msglen < 0 {
2418
- return protohelpers.ErrInvalidLength
2419
- }
2420
- postIndex := iNdEx + msglen
2421
- if postIndex < 0 {
2422
- return protohelpers.ErrInvalidLength
2423
- }
2424
- if postIndex > l {
2425
- return io.ErrUnexpectedEOF
2426
- }
2427
- if m.Lease == nil {
2428
- m.Lease = &Lease{}
2429
- }
2430
- if err := m.Lease.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2431
- return err
2432
- }
2433
- iNdEx = postIndex
2434
- case 2:
2435
- if wireType != 2 {
2436
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
2437
- }
2438
- var byteLen int
2439
- for shift := uint(0); ; shift += 7 {
2440
- if shift >= 64 {
2441
- return protohelpers.ErrIntOverflow
2442
- }
2443
- if iNdEx >= l {
2444
- return io.ErrUnexpectedEOF
2445
- }
2446
- b := dAtA[iNdEx]
2447
- iNdEx++
2448
- byteLen |= int(b&0x7F) << shift
2449
- if b < 0x80 {
2450
- break
2451
- }
2452
- }
2453
- if byteLen < 0 {
2454
- return protohelpers.ErrInvalidLength
2455
- }
2456
- postIndex := iNdEx + byteLen
2457
- if postIndex < 0 {
2458
- return protohelpers.ErrInvalidLength
2459
- }
2460
- if postIndex > l {
2461
- return io.ErrUnexpectedEOF
2462
- }
2463
- m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
2464
- if m.Nonce == nil {
2465
- m.Nonce = []byte{}
2466
- }
2467
- iNdEx = postIndex
2468
- case 3:
2469
- if wireType != 0 {
2470
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
2471
- }
2472
- m.Timestamp = 0
2473
- for shift := uint(0); ; shift += 7 {
2474
- if shift >= 64 {
2475
- return protohelpers.ErrIntOverflow
2476
- }
2477
- if iNdEx >= l {
2478
- return io.ErrUnexpectedEOF
2479
- }
2480
- b := dAtA[iNdEx]
2481
- iNdEx++
2482
- m.Timestamp |= int64(b&0x7F) << shift
2483
- if b < 0x80 {
2484
- break
2485
- }
2486
- }
2487
- default:
2488
- iNdEx = preIndex
2489
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2490
- if err != nil {
2491
- return err
2492
- }
2493
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2494
- return protohelpers.ErrInvalidLength
2495
- }
2496
- if (iNdEx + skippy) > l {
2497
- return io.ErrUnexpectedEOF
2498
- }
2499
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2500
- iNdEx += skippy
2501
- }
2502
- }
2503
-
2504
- if iNdEx > l {
2505
- return io.ErrUnexpectedEOF
2506
- }
2507
- return nil
2508
-}
2509
-func (m *LeaseUpdateResponse) UnmarshalVT(dAtA []byte) error {
2510
- l := len(dAtA)
2511
- iNdEx := 0
2512
- for iNdEx < l {
2513
- preIndex := iNdEx
2514
- var wire uint64
2515
- for shift := uint(0); ; shift += 7 {
2516
- if shift >= 64 {
2517
- return protohelpers.ErrIntOverflow
2518
- }
2519
- if iNdEx >= l {
2520
- return io.ErrUnexpectedEOF
2521
- }
2522
- b := dAtA[iNdEx]
2523
- iNdEx++
2524
- wire |= uint64(b&0x7F) << shift
2525
- if b < 0x80 {
2526
- break
2527
- }
2528
- }
2529
- fieldNum := int32(wire >> 3)
2530
- wireType := int(wire & 0x7)
2531
- if wireType == 4 {
2532
- return fmt.Errorf("proto: LeaseUpdateResponse: wiretype end group for non-group")
2533
- }
2534
- if fieldNum <= 0 {
2535
- return fmt.Errorf("proto: LeaseUpdateResponse: illegal tag %d (wire type %d)", fieldNum, wire)
2536
- }
2537
- switch fieldNum {
2538
- case 1:
2539
- if wireType != 0 {
2540
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
2541
- }
2542
- m.Code = 0
2543
- for shift := uint(0); ; shift += 7 {
2544
- if shift >= 64 {
2545
- return protohelpers.ErrIntOverflow
2546
- }
2547
- if iNdEx >= l {
2548
- return io.ErrUnexpectedEOF
2549
- }
2550
- b := dAtA[iNdEx]
2551
- iNdEx++
2552
- m.Code |= ResponseCode(b&0x7F) << shift
2553
- if b < 0x80 {
2554
- break
2555
- }
2556
- }
2557
- default:
2558
- iNdEx = preIndex
2559
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2560
- if err != nil {
2561
- return err
2562
- }
2563
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2564
- return protohelpers.ErrInvalidLength
2565
- }
2566
- if (iNdEx + skippy) > l {
2567
- return io.ErrUnexpectedEOF
2568
- }
2569
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2570
- iNdEx += skippy
2571
- }
2572
- }
2573
-
2574
- if iNdEx > l {
2575
- return io.ErrUnexpectedEOF
2576
- }
2577
- return nil
2578
-}
2579
-func (m *LeaseDeleteRequest) UnmarshalVT(dAtA []byte) error {
2580
- l := len(dAtA)
2581
- iNdEx := 0
2582
- for iNdEx < l {
2583
- preIndex := iNdEx
2584
- var wire uint64
2585
- for shift := uint(0); ; shift += 7 {
2586
- if shift >= 64 {
2587
- return protohelpers.ErrIntOverflow
2588
- }
2589
- if iNdEx >= l {
2590
- return io.ErrUnexpectedEOF
2591
- }
2592
- b := dAtA[iNdEx]
2593
- iNdEx++
2594
- wire |= uint64(b&0x7F) << shift
2595
- if b < 0x80 {
2596
- break
2597
- }
2598
- }
2599
- fieldNum := int32(wire >> 3)
2600
- wireType := int(wire & 0x7)
2601
- if wireType == 4 {
2602
- return fmt.Errorf("proto: LeaseDeleteRequest: wiretype end group for non-group")
2603
- }
2604
- if fieldNum <= 0 {
2605
- return fmt.Errorf("proto: LeaseDeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire)
2606
- }
2607
- switch fieldNum {
2608
- case 1:
2609
- if wireType != 2 {
2610
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
2611
- }
2612
- var msglen int
2613
- for shift := uint(0); ; shift += 7 {
2614
- if shift >= 64 {
2615
- return protohelpers.ErrIntOverflow
2616
- }
2617
- if iNdEx >= l {
2618
- return io.ErrUnexpectedEOF
2619
- }
2620
- b := dAtA[iNdEx]
2621
- iNdEx++
2622
- msglen |= int(b&0x7F) << shift
2623
- if b < 0x80 {
2624
- break
2625
- }
2626
- }
2627
- if msglen < 0 {
2628
- return protohelpers.ErrInvalidLength
2629
- }
2630
- postIndex := iNdEx + msglen
2631
- if postIndex < 0 {
2632
- return protohelpers.ErrInvalidLength
2633
- }
2634
- if postIndex > l {
2635
- return io.ErrUnexpectedEOF
2636
- }
2637
- if m.Identity == nil {
2638
- m.Identity = &rdsec.Identity{}
2639
- }
2640
- if err := m.Identity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2641
- return err
2642
- }
2643
- iNdEx = postIndex
2644
- case 2:
2645
- if wireType != 2 {
2646
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
2647
- }
2648
- var byteLen int
2649
- for shift := uint(0); ; shift += 7 {
2650
- if shift >= 64 {
2651
- return protohelpers.ErrIntOverflow
2652
- }
2653
- if iNdEx >= l {
2654
- return io.ErrUnexpectedEOF
2655
- }
2656
- b := dAtA[iNdEx]
2657
- iNdEx++
2658
- byteLen |= int(b&0x7F) << shift
2659
- if b < 0x80 {
2660
- break
2661
- }
2662
- }
2663
- if byteLen < 0 {
2664
- return protohelpers.ErrInvalidLength
2665
- }
2666
- postIndex := iNdEx + byteLen
2667
- if postIndex < 0 {
2668
- return protohelpers.ErrInvalidLength
2669
- }
2670
- if postIndex > l {
2671
- return io.ErrUnexpectedEOF
2672
- }
2673
- m.Nonce = append(m.Nonce[:0], dAtA[iNdEx:postIndex]...)
2674
- if m.Nonce == nil {
2675
- m.Nonce = []byte{}
2676
- }
2677
- iNdEx = postIndex
2678
- case 3:
2679
- if wireType != 0 {
2680
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
2681
- }
2682
- m.Timestamp = 0
2683
- for shift := uint(0); ; shift += 7 {
2684
- if shift >= 64 {
2685
- return protohelpers.ErrIntOverflow
2686
- }
2687
- if iNdEx >= l {
2688
- return io.ErrUnexpectedEOF
2689
- }
2690
- b := dAtA[iNdEx]
2691
- iNdEx++
2692
- m.Timestamp |= int64(b&0x7F) << shift
2693
- if b < 0x80 {
2694
- break
2695
- }
2696
- }
2697
- default:
2698
- iNdEx = preIndex
2699
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2700
- if err != nil {
2701
- return err
2702
- }
2703
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2704
- return protohelpers.ErrInvalidLength
2705
- }
2706
- if (iNdEx + skippy) > l {
2707
- return io.ErrUnexpectedEOF
2708
- }
2709
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2710
- iNdEx += skippy
2711
- }
2712
- }
2713
-
2714
- if iNdEx > l {
2715
- return io.ErrUnexpectedEOF
2716
- }
2717
- return nil
2718
-}
2719
-func (m *LeaseDeleteResponse) UnmarshalVT(dAtA []byte) error {
2720
- l := len(dAtA)
2721
- iNdEx := 0
2722
- for iNdEx < l {
2723
- preIndex := iNdEx
2724
- var wire uint64
2725
- for shift := uint(0); ; shift += 7 {
2726
- if shift >= 64 {
2727
- return protohelpers.ErrIntOverflow
2728
- }
2729
- if iNdEx >= l {
2730
- return io.ErrUnexpectedEOF
2731
- }
2732
- b := dAtA[iNdEx]
2733
- iNdEx++
2734
- wire |= uint64(b&0x7F) << shift
2735
- if b < 0x80 {
2736
- break
2737
- }
2738
- }
2739
- fieldNum := int32(wire >> 3)
2740
- wireType := int(wire & 0x7)
2741
- if wireType == 4 {
2742
- return fmt.Errorf("proto: LeaseDeleteResponse: wiretype end group for non-group")
2743
- }
2744
- if fieldNum <= 0 {
2745
- return fmt.Errorf("proto: LeaseDeleteResponse: illegal tag %d (wire type %d)", fieldNum, wire)
2746
- }
2747
- switch fieldNum {
2748
- case 1:
2749
- if wireType != 0 {
2750
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
2751
- }
2752
- m.Code = 0
2753
- for shift := uint(0); ; shift += 7 {
2754
- if shift >= 64 {
2755
- return protohelpers.ErrIntOverflow
2756
- }
2757
- if iNdEx >= l {
2758
- return io.ErrUnexpectedEOF
2759
- }
2760
- b := dAtA[iNdEx]
2761
- iNdEx++
2762
- m.Code |= ResponseCode(b&0x7F) << shift
2763
- if b < 0x80 {
2764
- break
2765
- }
2766
- }
2767
- default:
2768
- iNdEx = preIndex
2769
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2770
- if err != nil {
2771
- return err
2772
- }
2773
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2774
- return protohelpers.ErrInvalidLength
2775
- }
2776
- if (iNdEx + skippy) > l {
2777
- return io.ErrUnexpectedEOF
2778
- }
2779
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2780
- iNdEx += skippy
2781
- }
2782
- }
2783
-
2784
- if iNdEx > l {
2785
- return io.ErrUnexpectedEOF
2786
- }
2787
- return nil
2788
-}
2789
-func (m *ConnectionRequest) UnmarshalVT(dAtA []byte) error {
2790
- l := len(dAtA)
2791
- iNdEx := 0
2792
- for iNdEx < l {
2793
- preIndex := iNdEx
2794
- var wire uint64
2795
- for shift := uint(0); ; shift += 7 {
2796
- if shift >= 64 {
2797
- return protohelpers.ErrIntOverflow
2798
- }
2799
- if iNdEx >= l {
2800
- return io.ErrUnexpectedEOF
2801
- }
2802
- b := dAtA[iNdEx]
2803
- iNdEx++
2804
- wire |= uint64(b&0x7F) << shift
2805
- if b < 0x80 {
2806
- break
2807
- }
2808
- }
2809
- fieldNum := int32(wire >> 3)
2810
- wireType := int(wire & 0x7)
2811
- if wireType == 4 {
2812
- return fmt.Errorf("proto: ConnectionRequest: wiretype end group for non-group")
2813
- }
2814
- if fieldNum <= 0 {
2815
- return fmt.Errorf("proto: ConnectionRequest: illegal tag %d (wire type %d)", fieldNum, wire)
2816
- }
2817
- switch fieldNum {
2818
- case 1:
2819
- if wireType != 2 {
2820
- return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
2821
- }
2822
- var stringLen uint64
2823
- for shift := uint(0); ; shift += 7 {
2824
- if shift >= 64 {
2825
- return protohelpers.ErrIntOverflow
2826
- }
2827
- if iNdEx >= l {
2828
- return io.ErrUnexpectedEOF
2829
- }
2830
- b := dAtA[iNdEx]
2831
- iNdEx++
2832
- stringLen |= uint64(b&0x7F) << shift
2833
- if b < 0x80 {
2834
- break
2835
- }
2836
- }
2837
- intStringLen := int(stringLen)
2838
- if intStringLen < 0 {
2839
- return protohelpers.ErrInvalidLength
2840
- }
2841
- postIndex := iNdEx + intStringLen
2842
- if postIndex < 0 {
2843
- return protohelpers.ErrInvalidLength
2844
- }
2845
- if postIndex > l {
2846
- return io.ErrUnexpectedEOF
2847
- }
2848
- m.LeaseId = string(dAtA[iNdEx:postIndex])
2849
- iNdEx = postIndex
2850
- case 2:
2851
- if wireType != 2 {
2852
- return fmt.Errorf("proto: wrong wireType = %d for field ClientIdentity", wireType)
2853
- }
2854
- var msglen int
2855
- for shift := uint(0); ; shift += 7 {
2856
- if shift >= 64 {
2857
- return protohelpers.ErrIntOverflow
2858
- }
2859
- if iNdEx >= l {
2860
- return io.ErrUnexpectedEOF
2861
- }
2862
- b := dAtA[iNdEx]
2863
- iNdEx++
2864
- msglen |= int(b&0x7F) << shift
2865
- if b < 0x80 {
2866
- break
2867
- }
2868
- }
2869
- if msglen < 0 {
2870
- return protohelpers.ErrInvalidLength
2871
- }
2872
- postIndex := iNdEx + msglen
2873
- if postIndex < 0 {
2874
- return protohelpers.ErrInvalidLength
2875
- }
2876
- if postIndex > l {
2877
- return io.ErrUnexpectedEOF
2878
- }
2879
- if m.ClientIdentity == nil {
2880
- m.ClientIdentity = &rdsec.Identity{}
2881
- }
2882
- if err := m.ClientIdentity.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
2883
- return err
2884
- }
2885
- iNdEx = postIndex
2886
- default:
2887
- iNdEx = preIndex
2888
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2889
- if err != nil {
2890
- return err
2891
- }
2892
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2893
- return protohelpers.ErrInvalidLength
2894
- }
2895
- if (iNdEx + skippy) > l {
2896
- return io.ErrUnexpectedEOF
2897
- }
2898
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2899
- iNdEx += skippy
2900
- }
2901
- }
2902
-
2903
- if iNdEx > l {
2904
- return io.ErrUnexpectedEOF
2905
- }
2906
- return nil
2907
-}
2908
-func (m *ConnectionResponse) UnmarshalVT(dAtA []byte) error {
2909
- l := len(dAtA)
2910
- iNdEx := 0
2911
- for iNdEx < l {
2912
- preIndex := iNdEx
2913
- var wire uint64
2914
- for shift := uint(0); ; shift += 7 {
2915
- if shift >= 64 {
2916
- return protohelpers.ErrIntOverflow
2917
- }
2918
- if iNdEx >= l {
2919
- return io.ErrUnexpectedEOF
2920
- }
2921
- b := dAtA[iNdEx]
2922
- iNdEx++
2923
- wire |= uint64(b&0x7F) << shift
2924
- if b < 0x80 {
2925
- break
2926
- }
2927
- }
2928
- fieldNum := int32(wire >> 3)
2929
- wireType := int(wire & 0x7)
2930
- if wireType == 4 {
2931
- return fmt.Errorf("proto: ConnectionResponse: wiretype end group for non-group")
2932
- }
2933
- if fieldNum <= 0 {
2934
- return fmt.Errorf("proto: ConnectionResponse: illegal tag %d (wire type %d)", fieldNum, wire)
2935
- }
2936
- switch fieldNum {
2937
- case 1:
2938
- if wireType != 0 {
2939
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
2940
- }
2941
- m.Code = 0
2942
- for shift := uint(0); ; shift += 7 {
2943
- if shift >= 64 {
2944
- return protohelpers.ErrIntOverflow
2945
- }
2946
- if iNdEx >= l {
2947
- return io.ErrUnexpectedEOF
2948
- }
2949
- b := dAtA[iNdEx]
2950
- iNdEx++
2951
- m.Code |= ResponseCode(b&0x7F) << shift
2952
- if b < 0x80 {
2953
- break
2954
- }
2955
- }
2956
- default:
2957
- iNdEx = preIndex
2958
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
2959
- if err != nil {
2960
- return err
2961
- }
2962
- if (skippy < 0) || (iNdEx+skippy) < 0 {
2963
- return protohelpers.ErrInvalidLength
2964
- }
2965
- if (iNdEx + skippy) > l {
2966
- return io.ErrUnexpectedEOF
2967
- }
2968
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
2969
- iNdEx += skippy
2970
- }
2971
- }
2972
-
2973
- if iNdEx > l {
2974
- return io.ErrUnexpectedEOF
2975
- }
2976
- return nil
2977
-}
2978
-func (m *Packet) UnmarshalVTUnsafe(dAtA []byte) error {
2979
- l := len(dAtA)
2980
- iNdEx := 0
2981
- for iNdEx < l {
2982
- preIndex := iNdEx
2983
- var wire uint64
2984
- for shift := uint(0); ; shift += 7 {
2985
- if shift >= 64 {
2986
- return protohelpers.ErrIntOverflow
2987
- }
2988
- if iNdEx >= l {
2989
- return io.ErrUnexpectedEOF
2990
- }
2991
- b := dAtA[iNdEx]
2992
- iNdEx++
2993
- wire |= uint64(b&0x7F) << shift
2994
- if b < 0x80 {
2995
- break
2996
- }
2997
- }
2998
- fieldNum := int32(wire >> 3)
2999
- wireType := int(wire & 0x7)
3000
- if wireType == 4 {
3001
- return fmt.Errorf("proto: Packet: wiretype end group for non-group")
3002
- }
3003
- if fieldNum <= 0 {
3004
- return fmt.Errorf("proto: Packet: illegal tag %d (wire type %d)", fieldNum, wire)
3005
- }
3006
- switch fieldNum {
3007
- case 1:
3008
- if wireType != 0 {
3009
- return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
3010
- }
3011
- m.Type = 0
3012
- for shift := uint(0); ; shift += 7 {
3013
- if shift >= 64 {
3014
- return protohelpers.ErrIntOverflow
3015
- }
3016
- if iNdEx >= l {
3017
- return io.ErrUnexpectedEOF
3018
- }
3019
- b := dAtA[iNdEx]
3020
- iNdEx++
3021
- m.Type |= PacketType(b&0x7F) << shift
3022
- if b < 0x80 {
3023
- break
3024
- }
3025
- }
3026
- case 2:
3027
- if wireType != 2 {
3028
- return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType)
3029
- }
3030
- var byteLen int
3031
- for shift := uint(0); ; shift += 7 {
3032
- if shift >= 64 {
3033
- return protohelpers.ErrIntOverflow
3034
- }
3035
- if iNdEx >= l {
3036
- return io.ErrUnexpectedEOF
3037
- }
3038
- b := dAtA[iNdEx]
3039
- iNdEx++
3040
- byteLen |= int(b&0x7F) << shift
3041
- if b < 0x80 {
3042
- break
3043
- }
3044
- }
3045
- if byteLen < 0 {
3046
- return protohelpers.ErrInvalidLength
3047
- }
3048
- postIndex := iNdEx + byteLen
3049
- if postIndex < 0 {
3050
- return protohelpers.ErrInvalidLength
3051
- }
3052
- if postIndex > l {
3053
- return io.ErrUnexpectedEOF
3054
- }
3055
- m.Payload = dAtA[iNdEx:postIndex]
3056
- iNdEx = postIndex
3057
- default:
3058
- iNdEx = preIndex
3059
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3060
- if err != nil {
3061
- return err
3062
- }
3063
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3064
- return protohelpers.ErrInvalidLength
3065
- }
3066
- if (iNdEx + skippy) > l {
3067
- return io.ErrUnexpectedEOF
3068
- }
3069
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3070
- iNdEx += skippy
3071
- }
3072
- }
3073
-
3074
- if iNdEx > l {
3075
- return io.ErrUnexpectedEOF
3076
- }
3077
- return nil
3078
-}
3079
-func (m *RelayInfo) UnmarshalVTUnsafe(dAtA []byte) error {
3080
- l := len(dAtA)
3081
- iNdEx := 0
3082
- for iNdEx < l {
3083
- preIndex := iNdEx
3084
- var wire uint64
3085
- for shift := uint(0); ; shift += 7 {
3086
- if shift >= 64 {
3087
- return protohelpers.ErrIntOverflow
3088
- }
3089
- if iNdEx >= l {
3090
- return io.ErrUnexpectedEOF
3091
- }
3092
- b := dAtA[iNdEx]
3093
- iNdEx++
3094
- wire |= uint64(b&0x7F) << shift
3095
- if b < 0x80 {
3096
- break
3097
- }
3098
- }
3099
- fieldNum := int32(wire >> 3)
3100
- wireType := int(wire & 0x7)
3101
- if wireType == 4 {
3102
- return fmt.Errorf("proto: RelayInfo: wiretype end group for non-group")
3103
- }
3104
- if fieldNum <= 0 {
3105
- return fmt.Errorf("proto: RelayInfo: illegal tag %d (wire type %d)", fieldNum, wire)
3106
- }
3107
- switch fieldNum {
3108
- case 1:
3109
- if wireType != 2 {
3110
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
3111
- }
3112
- var msglen int
3113
- for shift := uint(0); ; shift += 7 {
3114
- if shift >= 64 {
3115
- return protohelpers.ErrIntOverflow
3116
- }
3117
- if iNdEx >= l {
3118
- return io.ErrUnexpectedEOF
3119
- }
3120
- b := dAtA[iNdEx]
3121
- iNdEx++
3122
- msglen |= int(b&0x7F) << shift
3123
- if b < 0x80 {
3124
- break
3125
- }
3126
- }
3127
- if msglen < 0 {
3128
- return protohelpers.ErrInvalidLength
3129
- }
3130
- postIndex := iNdEx + msglen
3131
- if postIndex < 0 {
3132
- return protohelpers.ErrInvalidLength
3133
- }
3134
- if postIndex > l {
3135
- return io.ErrUnexpectedEOF
3136
- }
3137
- if m.Identity == nil {
3138
- m.Identity = &rdsec.Identity{}
3139
- }
3140
- if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3141
- return err
3142
- }
3143
- iNdEx = postIndex
3144
- case 2:
3145
- if wireType != 2 {
3146
- return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
3147
- }
3148
- var stringLen uint64
3149
- for shift := uint(0); ; shift += 7 {
3150
- if shift >= 64 {
3151
- return protohelpers.ErrIntOverflow
3152
- }
3153
- if iNdEx >= l {
3154
- return io.ErrUnexpectedEOF
3155
- }
3156
- b := dAtA[iNdEx]
3157
- iNdEx++
3158
- stringLen |= uint64(b&0x7F) << shift
3159
- if b < 0x80 {
3160
- break
3161
- }
3162
- }
3163
- intStringLen := int(stringLen)
3164
- if intStringLen < 0 {
3165
- return protohelpers.ErrInvalidLength
3166
- }
3167
- postIndex := iNdEx + intStringLen
3168
- if postIndex < 0 {
3169
- return protohelpers.ErrInvalidLength
3170
- }
3171
- if postIndex > l {
3172
- return io.ErrUnexpectedEOF
3173
- }
3174
- var stringValue string
3175
- if intStringLen > 0 {
3176
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
3177
- }
3178
- m.Address = append(m.Address, stringValue)
3179
- iNdEx = postIndex
3180
- case 3:
3181
- if wireType != 2 {
3182
- return fmt.Errorf("proto: wrong wireType = %d for field Leases", wireType)
3183
- }
3184
- var msglen int
3185
- for shift := uint(0); ; shift += 7 {
3186
- if shift >= 64 {
3187
- return protohelpers.ErrIntOverflow
3188
- }
3189
- if iNdEx >= l {
3190
- return io.ErrUnexpectedEOF
3191
- }
3192
- b := dAtA[iNdEx]
3193
- iNdEx++
3194
- msglen |= int(b&0x7F) << shift
3195
- if b < 0x80 {
3196
- break
3197
- }
3198
- }
3199
- if msglen < 0 {
3200
- return protohelpers.ErrInvalidLength
3201
- }
3202
- postIndex := iNdEx + msglen
3203
- if postIndex < 0 {
3204
- return protohelpers.ErrInvalidLength
3205
- }
3206
- if postIndex > l {
3207
- return io.ErrUnexpectedEOF
3208
- }
3209
- m.Leases = append(m.Leases, &Lease{})
3210
- if err := m.Leases[len(m.Leases)-1].UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3211
- return err
3212
- }
3213
- iNdEx = postIndex
3214
- default:
3215
- iNdEx = preIndex
3216
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3217
- if err != nil {
3218
- return err
3219
- }
3220
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3221
- return protohelpers.ErrInvalidLength
3222
- }
3223
- if (iNdEx + skippy) > l {
3224
- return io.ErrUnexpectedEOF
3225
- }
3226
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3227
- iNdEx += skippy
3228
- }
3229
- }
3230
-
3231
- if iNdEx > l {
3232
- return io.ErrUnexpectedEOF
3233
- }
3234
- return nil
3235
-}
3236
-func (m *RelayInfoRequest) UnmarshalVTUnsafe(dAtA []byte) error {
3237
- l := len(dAtA)
3238
- iNdEx := 0
3239
- for iNdEx < l {
3240
- preIndex := iNdEx
3241
- var wire uint64
3242
- for shift := uint(0); ; shift += 7 {
3243
- if shift >= 64 {
3244
- return protohelpers.ErrIntOverflow
3245
- }
3246
- if iNdEx >= l {
3247
- return io.ErrUnexpectedEOF
3248
- }
3249
- b := dAtA[iNdEx]
3250
- iNdEx++
3251
- wire |= uint64(b&0x7F) << shift
3252
- if b < 0x80 {
3253
- break
3254
- }
3255
- }
3256
- fieldNum := int32(wire >> 3)
3257
- wireType := int(wire & 0x7)
3258
- if wireType == 4 {
3259
- return fmt.Errorf("proto: RelayInfoRequest: wiretype end group for non-group")
3260
- }
3261
- if fieldNum <= 0 {
3262
- return fmt.Errorf("proto: RelayInfoRequest: illegal tag %d (wire type %d)", fieldNum, wire)
3263
- }
3264
- switch fieldNum {
3265
- default:
3266
- iNdEx = preIndex
3267
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3268
- if err != nil {
3269
- return err
3270
- }
3271
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3272
- return protohelpers.ErrInvalidLength
3273
- }
3274
- if (iNdEx + skippy) > l {
3275
- return io.ErrUnexpectedEOF
3276
- }
3277
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3278
- iNdEx += skippy
3279
- }
3280
- }
3281
-
3282
- if iNdEx > l {
3283
- return io.ErrUnexpectedEOF
3284
- }
3285
- return nil
3286
-}
3287
-func (m *RelayInfoResponse) UnmarshalVTUnsafe(dAtA []byte) error {
3288
- l := len(dAtA)
3289
- iNdEx := 0
3290
- for iNdEx < l {
3291
- preIndex := iNdEx
3292
- var wire uint64
3293
- for shift := uint(0); ; shift += 7 {
3294
- if shift >= 64 {
3295
- return protohelpers.ErrIntOverflow
3296
- }
3297
- if iNdEx >= l {
3298
- return io.ErrUnexpectedEOF
3299
- }
3300
- b := dAtA[iNdEx]
3301
- iNdEx++
3302
- wire |= uint64(b&0x7F) << shift
3303
- if b < 0x80 {
3304
- break
3305
- }
3306
- }
3307
- fieldNum := int32(wire >> 3)
3308
- wireType := int(wire & 0x7)
3309
- if wireType == 4 {
3310
- return fmt.Errorf("proto: RelayInfoResponse: wiretype end group for non-group")
3311
- }
3312
- if fieldNum <= 0 {
3313
- return fmt.Errorf("proto: RelayInfoResponse: illegal tag %d (wire type %d)", fieldNum, wire)
3314
- }
3315
- switch fieldNum {
3316
- case 1:
3317
- if wireType != 2 {
3318
- return fmt.Errorf("proto: wrong wireType = %d for field RelayInfo", wireType)
3319
- }
3320
- var msglen int
3321
- for shift := uint(0); ; shift += 7 {
3322
- if shift >= 64 {
3323
- return protohelpers.ErrIntOverflow
3324
- }
3325
- if iNdEx >= l {
3326
- return io.ErrUnexpectedEOF
3327
- }
3328
- b := dAtA[iNdEx]
3329
- iNdEx++
3330
- msglen |= int(b&0x7F) << shift
3331
- if b < 0x80 {
3332
- break
3333
- }
3334
- }
3335
- if msglen < 0 {
3336
- return protohelpers.ErrInvalidLength
3337
- }
3338
- postIndex := iNdEx + msglen
3339
- if postIndex < 0 {
3340
- return protohelpers.ErrInvalidLength
3341
- }
3342
- if postIndex > l {
3343
- return io.ErrUnexpectedEOF
3344
- }
3345
- if m.RelayInfo == nil {
3346
- m.RelayInfo = &RelayInfo{}
3347
- }
3348
- if err := m.RelayInfo.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3349
- return err
3350
- }
3351
- iNdEx = postIndex
3352
- default:
3353
- iNdEx = preIndex
3354
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3355
- if err != nil {
3356
- return err
3357
- }
3358
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3359
- return protohelpers.ErrInvalidLength
3360
- }
3361
- if (iNdEx + skippy) > l {
3362
- return io.ErrUnexpectedEOF
3363
- }
3364
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3365
- iNdEx += skippy
3366
- }
3367
- }
3368
-
3369
- if iNdEx > l {
3370
- return io.ErrUnexpectedEOF
3371
- }
3372
- return nil
3373
-}
3374
-func (m *Lease) UnmarshalVTUnsafe(dAtA []byte) error {
3375
- l := len(dAtA)
3376
- iNdEx := 0
3377
- for iNdEx < l {
3378
- preIndex := iNdEx
3379
- var wire uint64
3380
- for shift := uint(0); ; shift += 7 {
3381
- if shift >= 64 {
3382
- return protohelpers.ErrIntOverflow
3383
- }
3384
- if iNdEx >= l {
3385
- return io.ErrUnexpectedEOF
3386
- }
3387
- b := dAtA[iNdEx]
3388
- iNdEx++
3389
- wire |= uint64(b&0x7F) << shift
3390
- if b < 0x80 {
3391
- break
3392
- }
3393
- }
3394
- fieldNum := int32(wire >> 3)
3395
- wireType := int(wire & 0x7)
3396
- if wireType == 4 {
3397
- return fmt.Errorf("proto: Lease: wiretype end group for non-group")
3398
- }
3399
- if fieldNum <= 0 {
3400
- return fmt.Errorf("proto: Lease: illegal tag %d (wire type %d)", fieldNum, wire)
3401
- }
3402
- switch fieldNum {
3403
- case 1:
3404
- if wireType != 2 {
3405
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
3406
- }
3407
- var msglen int
3408
- for shift := uint(0); ; shift += 7 {
3409
- if shift >= 64 {
3410
- return protohelpers.ErrIntOverflow
3411
- }
3412
- if iNdEx >= l {
3413
- return io.ErrUnexpectedEOF
3414
- }
3415
- b := dAtA[iNdEx]
3416
- iNdEx++
3417
- msglen |= int(b&0x7F) << shift
3418
- if b < 0x80 {
3419
- break
3420
- }
3421
- }
3422
- if msglen < 0 {
3423
- return protohelpers.ErrInvalidLength
3424
- }
3425
- postIndex := iNdEx + msglen
3426
- if postIndex < 0 {
3427
- return protohelpers.ErrInvalidLength
3428
- }
3429
- if postIndex > l {
3430
- return io.ErrUnexpectedEOF
3431
- }
3432
- if m.Identity == nil {
3433
- m.Identity = &rdsec.Identity{}
3434
- }
3435
- if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3436
- return err
3437
- }
3438
- iNdEx = postIndex
3439
- case 2:
3440
- if wireType != 0 {
3441
- return fmt.Errorf("proto: wrong wireType = %d for field Expires", wireType)
3442
- }
3443
- m.Expires = 0
3444
- for shift := uint(0); ; shift += 7 {
3445
- if shift >= 64 {
3446
- return protohelpers.ErrIntOverflow
3447
- }
3448
- if iNdEx >= l {
3449
- return io.ErrUnexpectedEOF
3450
- }
3451
- b := dAtA[iNdEx]
3452
- iNdEx++
3453
- m.Expires |= int64(b&0x7F) << shift
3454
- if b < 0x80 {
3455
- break
3456
- }
3457
- }
3458
- case 3:
3459
- if wireType != 2 {
3460
- return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
3461
- }
3462
- var stringLen uint64
3463
- for shift := uint(0); ; shift += 7 {
3464
- if shift >= 64 {
3465
- return protohelpers.ErrIntOverflow
3466
- }
3467
- if iNdEx >= l {
3468
- return io.ErrUnexpectedEOF
3469
- }
3470
- b := dAtA[iNdEx]
3471
- iNdEx++
3472
- stringLen |= uint64(b&0x7F) << shift
3473
- if b < 0x80 {
3474
- break
3475
- }
3476
- }
3477
- intStringLen := int(stringLen)
3478
- if intStringLen < 0 {
3479
- return protohelpers.ErrInvalidLength
3480
- }
3481
- postIndex := iNdEx + intStringLen
3482
- if postIndex < 0 {
3483
- return protohelpers.ErrInvalidLength
3484
- }
3485
- if postIndex > l {
3486
- return io.ErrUnexpectedEOF
3487
- }
3488
- var stringValue string
3489
- if intStringLen > 0 {
3490
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
3491
- }
3492
- m.Name = stringValue
3493
- iNdEx = postIndex
3494
- case 4:
3495
- if wireType != 2 {
3496
- return fmt.Errorf("proto: wrong wireType = %d for field Alpn", wireType)
3497
- }
3498
- var stringLen uint64
3499
- for shift := uint(0); ; shift += 7 {
3500
- if shift >= 64 {
3501
- return protohelpers.ErrIntOverflow
3502
- }
3503
- if iNdEx >= l {
3504
- return io.ErrUnexpectedEOF
3505
- }
3506
- b := dAtA[iNdEx]
3507
- iNdEx++
3508
- stringLen |= uint64(b&0x7F) << shift
3509
- if b < 0x80 {
3510
- break
3511
- }
3512
- }
3513
- intStringLen := int(stringLen)
3514
- if intStringLen < 0 {
3515
- return protohelpers.ErrInvalidLength
3516
- }
3517
- postIndex := iNdEx + intStringLen
3518
- if postIndex < 0 {
3519
- return protohelpers.ErrInvalidLength
3520
- }
3521
- if postIndex > l {
3522
- return io.ErrUnexpectedEOF
3523
- }
3524
- var stringValue string
3525
- if intStringLen > 0 {
3526
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
3527
- }
3528
- m.Alpn = append(m.Alpn, stringValue)
3529
- iNdEx = postIndex
3530
- case 5:
3531
- if wireType != 2 {
3532
- return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType)
3533
- }
3534
- var stringLen uint64
3535
- for shift := uint(0); ; shift += 7 {
3536
- if shift >= 64 {
3537
- return protohelpers.ErrIntOverflow
3538
- }
3539
- if iNdEx >= l {
3540
- return io.ErrUnexpectedEOF
3541
- }
3542
- b := dAtA[iNdEx]
3543
- iNdEx++
3544
- stringLen |= uint64(b&0x7F) << shift
3545
- if b < 0x80 {
3546
- break
3547
- }
3548
- }
3549
- intStringLen := int(stringLen)
3550
- if intStringLen < 0 {
3551
- return protohelpers.ErrInvalidLength
3552
- }
3553
- postIndex := iNdEx + intStringLen
3554
- if postIndex < 0 {
3555
- return protohelpers.ErrInvalidLength
3556
- }
3557
- if postIndex > l {
3558
- return io.ErrUnexpectedEOF
3559
- }
3560
- var stringValue string
3561
- if intStringLen > 0 {
3562
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
3563
- }
3564
- m.Metadata = stringValue
3565
- iNdEx = postIndex
3566
- default:
3567
- iNdEx = preIndex
3568
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3569
- if err != nil {
3570
- return err
3571
- }
3572
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3573
- return protohelpers.ErrInvalidLength
3574
- }
3575
- if (iNdEx + skippy) > l {
3576
- return io.ErrUnexpectedEOF
3577
- }
3578
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3579
- iNdEx += skippy
3580
- }
3581
- }
3582
-
3583
- if iNdEx > l {
3584
- return io.ErrUnexpectedEOF
3585
- }
3586
- return nil
3587
-}
3588
-func (m *LeaseUpdateRequest) UnmarshalVTUnsafe(dAtA []byte) error {
3589
- l := len(dAtA)
3590
- iNdEx := 0
3591
- for iNdEx < l {
3592
- preIndex := iNdEx
3593
- var wire uint64
3594
- for shift := uint(0); ; shift += 7 {
3595
- if shift >= 64 {
3596
- return protohelpers.ErrIntOverflow
3597
- }
3598
- if iNdEx >= l {
3599
- return io.ErrUnexpectedEOF
3600
- }
3601
- b := dAtA[iNdEx]
3602
- iNdEx++
3603
- wire |= uint64(b&0x7F) << shift
3604
- if b < 0x80 {
3605
- break
3606
- }
3607
- }
3608
- fieldNum := int32(wire >> 3)
3609
- wireType := int(wire & 0x7)
3610
- if wireType == 4 {
3611
- return fmt.Errorf("proto: LeaseUpdateRequest: wiretype end group for non-group")
3612
- }
3613
- if fieldNum <= 0 {
3614
- return fmt.Errorf("proto: LeaseUpdateRequest: illegal tag %d (wire type %d)", fieldNum, wire)
3615
- }
3616
- switch fieldNum {
3617
- case 1:
3618
- if wireType != 2 {
3619
- return fmt.Errorf("proto: wrong wireType = %d for field Lease", wireType)
3620
- }
3621
- var msglen int
3622
- for shift := uint(0); ; shift += 7 {
3623
- if shift >= 64 {
3624
- return protohelpers.ErrIntOverflow
3625
- }
3626
- if iNdEx >= l {
3627
- return io.ErrUnexpectedEOF
3628
- }
3629
- b := dAtA[iNdEx]
3630
- iNdEx++
3631
- msglen |= int(b&0x7F) << shift
3632
- if b < 0x80 {
3633
- break
3634
- }
3635
- }
3636
- if msglen < 0 {
3637
- return protohelpers.ErrInvalidLength
3638
- }
3639
- postIndex := iNdEx + msglen
3640
- if postIndex < 0 {
3641
- return protohelpers.ErrInvalidLength
3642
- }
3643
- if postIndex > l {
3644
- return io.ErrUnexpectedEOF
3645
- }
3646
- if m.Lease == nil {
3647
- m.Lease = &Lease{}
3648
- }
3649
- if err := m.Lease.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3650
- return err
3651
- }
3652
- iNdEx = postIndex
3653
- case 2:
3654
- if wireType != 2 {
3655
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
3656
- }
3657
- var byteLen int
3658
- for shift := uint(0); ; shift += 7 {
3659
- if shift >= 64 {
3660
- return protohelpers.ErrIntOverflow
3661
- }
3662
- if iNdEx >= l {
3663
- return io.ErrUnexpectedEOF
3664
- }
3665
- b := dAtA[iNdEx]
3666
- iNdEx++
3667
- byteLen |= int(b&0x7F) << shift
3668
- if b < 0x80 {
3669
- break
3670
- }
3671
- }
3672
- if byteLen < 0 {
3673
- return protohelpers.ErrInvalidLength
3674
- }
3675
- postIndex := iNdEx + byteLen
3676
- if postIndex < 0 {
3677
- return protohelpers.ErrInvalidLength
3678
- }
3679
- if postIndex > l {
3680
- return io.ErrUnexpectedEOF
3681
- }
3682
- m.Nonce = dAtA[iNdEx:postIndex]
3683
- iNdEx = postIndex
3684
- case 3:
3685
- if wireType != 0 {
3686
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
3687
- }
3688
- m.Timestamp = 0
3689
- for shift := uint(0); ; shift += 7 {
3690
- if shift >= 64 {
3691
- return protohelpers.ErrIntOverflow
3692
- }
3693
- if iNdEx >= l {
3694
- return io.ErrUnexpectedEOF
3695
- }
3696
- b := dAtA[iNdEx]
3697
- iNdEx++
3698
- m.Timestamp |= int64(b&0x7F) << shift
3699
- if b < 0x80 {
3700
- break
3701
- }
3702
- }
3703
- default:
3704
- iNdEx = preIndex
3705
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3706
- if err != nil {
3707
- return err
3708
- }
3709
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3710
- return protohelpers.ErrInvalidLength
3711
- }
3712
- if (iNdEx + skippy) > l {
3713
- return io.ErrUnexpectedEOF
3714
- }
3715
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3716
- iNdEx += skippy
3717
- }
3718
- }
3719
-
3720
- if iNdEx > l {
3721
- return io.ErrUnexpectedEOF
3722
- }
3723
- return nil
3724
-}
3725
-func (m *LeaseUpdateResponse) UnmarshalVTUnsafe(dAtA []byte) error {
3726
- l := len(dAtA)
3727
- iNdEx := 0
3728
- for iNdEx < l {
3729
- preIndex := iNdEx
3730
- var wire uint64
3731
- for shift := uint(0); ; shift += 7 {
3732
- if shift >= 64 {
3733
- return protohelpers.ErrIntOverflow
3734
- }
3735
- if iNdEx >= l {
3736
- return io.ErrUnexpectedEOF
3737
- }
3738
- b := dAtA[iNdEx]
3739
- iNdEx++
3740
- wire |= uint64(b&0x7F) << shift
3741
- if b < 0x80 {
3742
- break
3743
- }
3744
- }
3745
- fieldNum := int32(wire >> 3)
3746
- wireType := int(wire & 0x7)
3747
- if wireType == 4 {
3748
- return fmt.Errorf("proto: LeaseUpdateResponse: wiretype end group for non-group")
3749
- }
3750
- if fieldNum <= 0 {
3751
- return fmt.Errorf("proto: LeaseUpdateResponse: illegal tag %d (wire type %d)", fieldNum, wire)
3752
- }
3753
- switch fieldNum {
3754
- case 1:
3755
- if wireType != 0 {
3756
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
3757
- }
3758
- m.Code = 0
3759
- for shift := uint(0); ; shift += 7 {
3760
- if shift >= 64 {
3761
- return protohelpers.ErrIntOverflow
3762
- }
3763
- if iNdEx >= l {
3764
- return io.ErrUnexpectedEOF
3765
- }
3766
- b := dAtA[iNdEx]
3767
- iNdEx++
3768
- m.Code |= ResponseCode(b&0x7F) << shift
3769
- if b < 0x80 {
3770
- break
3771
- }
3772
- }
3773
- default:
3774
- iNdEx = preIndex
3775
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3776
- if err != nil {
3777
- return err
3778
- }
3779
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3780
- return protohelpers.ErrInvalidLength
3781
- }
3782
- if (iNdEx + skippy) > l {
3783
- return io.ErrUnexpectedEOF
3784
- }
3785
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3786
- iNdEx += skippy
3787
- }
3788
- }
3789
-
3790
- if iNdEx > l {
3791
- return io.ErrUnexpectedEOF
3792
- }
3793
- return nil
3794
-}
3795
-func (m *LeaseDeleteRequest) UnmarshalVTUnsafe(dAtA []byte) error {
3796
- l := len(dAtA)
3797
- iNdEx := 0
3798
- for iNdEx < l {
3799
- preIndex := iNdEx
3800
- var wire uint64
3801
- for shift := uint(0); ; shift += 7 {
3802
- if shift >= 64 {
3803
- return protohelpers.ErrIntOverflow
3804
- }
3805
- if iNdEx >= l {
3806
- return io.ErrUnexpectedEOF
3807
- }
3808
- b := dAtA[iNdEx]
3809
- iNdEx++
3810
- wire |= uint64(b&0x7F) << shift
3811
- if b < 0x80 {
3812
- break
3813
- }
3814
- }
3815
- fieldNum := int32(wire >> 3)
3816
- wireType := int(wire & 0x7)
3817
- if wireType == 4 {
3818
- return fmt.Errorf("proto: LeaseDeleteRequest: wiretype end group for non-group")
3819
- }
3820
- if fieldNum <= 0 {
3821
- return fmt.Errorf("proto: LeaseDeleteRequest: illegal tag %d (wire type %d)", fieldNum, wire)
3822
- }
3823
- switch fieldNum {
3824
- case 1:
3825
- if wireType != 2 {
3826
- return fmt.Errorf("proto: wrong wireType = %d for field Identity", wireType)
3827
- }
3828
- var msglen int
3829
- for shift := uint(0); ; shift += 7 {
3830
- if shift >= 64 {
3831
- return protohelpers.ErrIntOverflow
3832
- }
3833
- if iNdEx >= l {
3834
- return io.ErrUnexpectedEOF
3835
- }
3836
- b := dAtA[iNdEx]
3837
- iNdEx++
3838
- msglen |= int(b&0x7F) << shift
3839
- if b < 0x80 {
3840
- break
3841
- }
3842
- }
3843
- if msglen < 0 {
3844
- return protohelpers.ErrInvalidLength
3845
- }
3846
- postIndex := iNdEx + msglen
3847
- if postIndex < 0 {
3848
- return protohelpers.ErrInvalidLength
3849
- }
3850
- if postIndex > l {
3851
- return io.ErrUnexpectedEOF
3852
- }
3853
- if m.Identity == nil {
3854
- m.Identity = &rdsec.Identity{}
3855
- }
3856
- if err := m.Identity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
3857
- return err
3858
- }
3859
- iNdEx = postIndex
3860
- case 2:
3861
- if wireType != 2 {
3862
- return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType)
3863
- }
3864
- var byteLen int
3865
- for shift := uint(0); ; shift += 7 {
3866
- if shift >= 64 {
3867
- return protohelpers.ErrIntOverflow
3868
- }
3869
- if iNdEx >= l {
3870
- return io.ErrUnexpectedEOF
3871
- }
3872
- b := dAtA[iNdEx]
3873
- iNdEx++
3874
- byteLen |= int(b&0x7F) << shift
3875
- if b < 0x80 {
3876
- break
3877
- }
3878
- }
3879
- if byteLen < 0 {
3880
- return protohelpers.ErrInvalidLength
3881
- }
3882
- postIndex := iNdEx + byteLen
3883
- if postIndex < 0 {
3884
- return protohelpers.ErrInvalidLength
3885
- }
3886
- if postIndex > l {
3887
- return io.ErrUnexpectedEOF
3888
- }
3889
- m.Nonce = dAtA[iNdEx:postIndex]
3890
- iNdEx = postIndex
3891
- case 3:
3892
- if wireType != 0 {
3893
- return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
3894
- }
3895
- m.Timestamp = 0
3896
- for shift := uint(0); ; shift += 7 {
3897
- if shift >= 64 {
3898
- return protohelpers.ErrIntOverflow
3899
- }
3900
- if iNdEx >= l {
3901
- return io.ErrUnexpectedEOF
3902
- }
3903
- b := dAtA[iNdEx]
3904
- iNdEx++
3905
- m.Timestamp |= int64(b&0x7F) << shift
3906
- if b < 0x80 {
3907
- break
3908
- }
3909
- }
3910
- default:
3911
- iNdEx = preIndex
3912
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3913
- if err != nil {
3914
- return err
3915
- }
3916
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3917
- return protohelpers.ErrInvalidLength
3918
- }
3919
- if (iNdEx + skippy) > l {
3920
- return io.ErrUnexpectedEOF
3921
- }
3922
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3923
- iNdEx += skippy
3924
- }
3925
- }
3926
-
3927
- if iNdEx > l {
3928
- return io.ErrUnexpectedEOF
3929
- }
3930
- return nil
3931
-}
3932
-func (m *LeaseDeleteResponse) UnmarshalVTUnsafe(dAtA []byte) error {
3933
- l := len(dAtA)
3934
- iNdEx := 0
3935
- for iNdEx < l {
3936
- preIndex := iNdEx
3937
- var wire uint64
3938
- for shift := uint(0); ; shift += 7 {
3939
- if shift >= 64 {
3940
- return protohelpers.ErrIntOverflow
3941
- }
3942
- if iNdEx >= l {
3943
- return io.ErrUnexpectedEOF
3944
- }
3945
- b := dAtA[iNdEx]
3946
- iNdEx++
3947
- wire |= uint64(b&0x7F) << shift
3948
- if b < 0x80 {
3949
- break
3950
- }
3951
- }
3952
- fieldNum := int32(wire >> 3)
3953
- wireType := int(wire & 0x7)
3954
- if wireType == 4 {
3955
- return fmt.Errorf("proto: LeaseDeleteResponse: wiretype end group for non-group")
3956
- }
3957
- if fieldNum <= 0 {
3958
- return fmt.Errorf("proto: LeaseDeleteResponse: illegal tag %d (wire type %d)", fieldNum, wire)
3959
- }
3960
- switch fieldNum {
3961
- case 1:
3962
- if wireType != 0 {
3963
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
3964
- }
3965
- m.Code = 0
3966
- for shift := uint(0); ; shift += 7 {
3967
- if shift >= 64 {
3968
- return protohelpers.ErrIntOverflow
3969
- }
3970
- if iNdEx >= l {
3971
- return io.ErrUnexpectedEOF
3972
- }
3973
- b := dAtA[iNdEx]
3974
- iNdEx++
3975
- m.Code |= ResponseCode(b&0x7F) << shift
3976
- if b < 0x80 {
3977
- break
3978
- }
3979
- }
3980
- default:
3981
- iNdEx = preIndex
3982
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
3983
- if err != nil {
3984
- return err
3985
- }
3986
- if (skippy < 0) || (iNdEx+skippy) < 0 {
3987
- return protohelpers.ErrInvalidLength
3988
- }
3989
- if (iNdEx + skippy) > l {
3990
- return io.ErrUnexpectedEOF
3991
- }
3992
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
3993
- iNdEx += skippy
3994
- }
3995
- }
3996
-
3997
- if iNdEx > l {
3998
- return io.ErrUnexpectedEOF
3999
- }
4000
- return nil
4001
-}
4002
-func (m *ConnectionRequest) UnmarshalVTUnsafe(dAtA []byte) error {
4003
- l := len(dAtA)
4004
- iNdEx := 0
4005
- for iNdEx < l {
4006
- preIndex := iNdEx
4007
- var wire uint64
4008
- for shift := uint(0); ; shift += 7 {
4009
- if shift >= 64 {
4010
- return protohelpers.ErrIntOverflow
4011
- }
4012
- if iNdEx >= l {
4013
- return io.ErrUnexpectedEOF
4014
- }
4015
- b := dAtA[iNdEx]
4016
- iNdEx++
4017
- wire |= uint64(b&0x7F) << shift
4018
- if b < 0x80 {
4019
- break
4020
- }
4021
- }
4022
- fieldNum := int32(wire >> 3)
4023
- wireType := int(wire & 0x7)
4024
- if wireType == 4 {
4025
- return fmt.Errorf("proto: ConnectionRequest: wiretype end group for non-group")
4026
- }
4027
- if fieldNum <= 0 {
4028
- return fmt.Errorf("proto: ConnectionRequest: illegal tag %d (wire type %d)", fieldNum, wire)
4029
- }
4030
- switch fieldNum {
4031
- case 1:
4032
- if wireType != 2 {
4033
- return fmt.Errorf("proto: wrong wireType = %d for field LeaseId", wireType)
4034
- }
4035
- var stringLen uint64
4036
- for shift := uint(0); ; shift += 7 {
4037
- if shift >= 64 {
4038
- return protohelpers.ErrIntOverflow
4039
- }
4040
- if iNdEx >= l {
4041
- return io.ErrUnexpectedEOF
4042
- }
4043
- b := dAtA[iNdEx]
4044
- iNdEx++
4045
- stringLen |= uint64(b&0x7F) << shift
4046
- if b < 0x80 {
4047
- break
4048
- }
4049
- }
4050
- intStringLen := int(stringLen)
4051
- if intStringLen < 0 {
4052
- return protohelpers.ErrInvalidLength
4053
- }
4054
- postIndex := iNdEx + intStringLen
4055
- if postIndex < 0 {
4056
- return protohelpers.ErrInvalidLength
4057
- }
4058
- if postIndex > l {
4059
- return io.ErrUnexpectedEOF
4060
- }
4061
- var stringValue string
4062
- if intStringLen > 0 {
4063
- stringValue = unsafe.String(&dAtA[iNdEx], intStringLen)
4064
- }
4065
- m.LeaseId = stringValue
4066
- iNdEx = postIndex
4067
- case 2:
4068
- if wireType != 2 {
4069
- return fmt.Errorf("proto: wrong wireType = %d for field ClientIdentity", wireType)
4070
- }
4071
- var msglen int
4072
- for shift := uint(0); ; shift += 7 {
4073
- if shift >= 64 {
4074
- return protohelpers.ErrIntOverflow
4075
- }
4076
- if iNdEx >= l {
4077
- return io.ErrUnexpectedEOF
4078
- }
4079
- b := dAtA[iNdEx]
4080
- iNdEx++
4081
- msglen |= int(b&0x7F) << shift
4082
- if b < 0x80 {
4083
- break
4084
- }
4085
- }
4086
- if msglen < 0 {
4087
- return protohelpers.ErrInvalidLength
4088
- }
4089
- postIndex := iNdEx + msglen
4090
- if postIndex < 0 {
4091
- return protohelpers.ErrInvalidLength
4092
- }
4093
- if postIndex > l {
4094
- return io.ErrUnexpectedEOF
4095
- }
4096
- if m.ClientIdentity == nil {
4097
- m.ClientIdentity = &rdsec.Identity{}
4098
- }
4099
- if err := m.ClientIdentity.UnmarshalVTUnsafe(dAtA[iNdEx:postIndex]); err != nil {
4100
- return err
4101
- }
4102
- iNdEx = postIndex
4103
- default:
4104
- iNdEx = preIndex
4105
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
4106
- if err != nil {
4107
- return err
4108
- }
4109
- if (skippy < 0) || (iNdEx+skippy) < 0 {
4110
- return protohelpers.ErrInvalidLength
4111
- }
4112
- if (iNdEx + skippy) > l {
4113
- return io.ErrUnexpectedEOF
4114
- }
4115
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
4116
- iNdEx += skippy
4117
- }
4118
- }
4119
-
4120
- if iNdEx > l {
4121
- return io.ErrUnexpectedEOF
4122
- }
4123
- return nil
4124
-}
4125
-func (m *ConnectionResponse) UnmarshalVTUnsafe(dAtA []byte) error {
4126
- l := len(dAtA)
4127
- iNdEx := 0
4128
- for iNdEx < l {
4129
- preIndex := iNdEx
4130
- var wire uint64
4131
- for shift := uint(0); ; shift += 7 {
4132
- if shift >= 64 {
4133
- return protohelpers.ErrIntOverflow
4134
- }
4135
- if iNdEx >= l {
4136
- return io.ErrUnexpectedEOF
4137
- }
4138
- b := dAtA[iNdEx]
4139
- iNdEx++
4140
- wire |= uint64(b&0x7F) << shift
4141
- if b < 0x80 {
4142
- break
4143
- }
4144
- }
4145
- fieldNum := int32(wire >> 3)
4146
- wireType := int(wire & 0x7)
4147
- if wireType == 4 {
4148
- return fmt.Errorf("proto: ConnectionResponse: wiretype end group for non-group")
4149
- }
4150
- if fieldNum <= 0 {
4151
- return fmt.Errorf("proto: ConnectionResponse: illegal tag %d (wire type %d)", fieldNum, wire)
4152
- }
4153
- switch fieldNum {
4154
- case 1:
4155
- if wireType != 0 {
4156
- return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType)
4157
- }
4158
- m.Code = 0
4159
- for shift := uint(0); ; shift += 7 {
4160
- if shift >= 64 {
4161
- return protohelpers.ErrIntOverflow
4162
- }
4163
- if iNdEx >= l {
4164
- return io.ErrUnexpectedEOF
4165
- }
4166
- b := dAtA[iNdEx]
4167
- iNdEx++
4168
- m.Code |= ResponseCode(b&0x7F) << shift
4169
- if b < 0x80 {
4170
- break
4171
- }
4172
- }
4173
- default:
4174
- iNdEx = preIndex
4175
- skippy, err := protohelpers.Skip(dAtA[iNdEx:])
4176
- if err != nil {
4177
- return err
4178
- }
4179
- if (skippy < 0) || (iNdEx+skippy) < 0 {
4180
- return protohelpers.ErrInvalidLength
4181
- }
4182
- if (iNdEx + skippy) > l {
4183
- return io.ErrUnexpectedEOF
4184
- }
4185
- m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
4186
- iNdEx += skippy
4187
- }
4188
- }
4189
-
4190
- if iNdEx > l {
4191
- return io.ErrUnexpectedEOF
4192
- }
4193
- return nil
4194
-}
portal/handlers.go
deleted
-350
@@ -1,350 +0,0 @@
1
-package portal
2
-
3
-import (
4
- "encoding/binary"
5
- "io"
6
-
7
- "github.com/hashicorp/yamux"
8
- "github.com/rs/zerolog/log"
9
- "github.com/valyala/bytebufferpool"
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 StreamContext struct {
16
- Server *RelayServer
17
- Stream *yamux.Stream
18
- Connection *Connection
19
- ConnectionID int64
20
- Hijacked *bool
21
-}
22
-
23
-func (ctx *StreamContext) Hijack() {
24
- *ctx.Hijacked = true
25
-}
26
-
27
-func (g *RelayServer) handleRelayInfoRequest(ctx *StreamContext, packet *rdverb.Packet) error {
28
- _, err := decodeProtobuf[*rdverb.RelayInfoRequest](packet.Payload)
29
- if err != nil {
30
- return err
31
- }
32
-
33
- var resp rdverb.RelayInfoResponse
34
- resp.RelayInfo = g.relayInfo()
35
- response, err := resp.MarshalVT()
36
- if err != nil {
37
- return err
38
- }
39
-
40
- return writePacket(ctx.Stream, &rdverb.Packet{
41
- Type: rdverb.PacketType_PACKET_TYPE_RELAY_INFO_RESPONSE,
42
- Payload: response,
43
- })
44
-}
45
-
46
-func (g *RelayServer) handleLeaseUpdateRequest(ctx *StreamContext, packet *rdverb.Packet) error {
47
- var signedPayload rdsec.SignedPayload
48
- err := signedPayload.UnmarshalVT(packet.Payload)
49
- if err != nil {
50
- return err
51
- }
52
-
53
- var req rdverb.LeaseUpdateRequest
54
- err = req.UnmarshalVT(signedPayload.Data)
55
- if err != nil {
56
- return err
57
- }
58
-
59
- if !cryptoops.VerifySignedPayload(&signedPayload, req.Lease.Identity) {
60
- return err
61
- }
62
-
63
- var resp rdverb.LeaseUpdateResponse
64
-
65
- // Update lease in lease manager
66
- if g.leaseManager.UpdateLease(req.Lease, ctx.ConnectionID) {
67
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED
68
-
69
- // Register lease connection
70
- leaseID := string(req.Lease.Identity.Id)
71
- g.leaseConnectionsLock.Lock()
72
- g.leaseConnections[leaseID] = ctx.Connection
73
- g.leaseConnectionsLock.Unlock()
74
-
75
- // Log lease update completion
76
- log.Debug().
77
- Str("lease_id", leaseID).
78
- Str("lease_name", req.Lease.Name).
79
- RawJSON("metadata", []byte(req.Lease.Metadata)).
80
- Int64("connection_id", ctx.ConnectionID).
81
- Msg("[RelayServer] Lease update completed successfully")
82
- } else {
83
- // Lease update failed (could be expired or name conflict)
84
- leaseID := string(req.Lease.Identity.Id)
85
- log.Warn().
86
- Str("lease_id", leaseID).
87
- Str("lease_name", req.Lease.Name).
88
- Msg("[RelayServer] Lease update rejected (expired or name conflict)")
89
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_REJECTED
90
- }
91
-
92
- response, err := resp.MarshalVT()
93
- if err != nil {
94
- return err
95
- }
96
-
97
- return writePacket(ctx.Stream, &rdverb.Packet{
98
- Type: rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_RESPONSE,
99
- Payload: response,
100
- })
101
-}
102
-
103
-func (g *RelayServer) handleLeaseDeleteRequest(ctx *StreamContext, packet *rdverb.Packet) error {
104
- var signedPayload rdsec.SignedPayload
105
- err := signedPayload.UnmarshalVT(packet.Payload)
106
- if err != nil {
107
- return err
108
- }
109
-
110
- var req rdverb.LeaseDeleteRequest
111
- err = req.UnmarshalVT(signedPayload.Data)
112
- if err != nil {
113
- return err
114
- }
115
-
116
- if !cryptoops.VerifySignedPayload(&signedPayload, req.Identity) {
117
- return err
118
- }
119
-
120
- var resp rdverb.LeaseDeleteResponse
121
-
122
- // Delete lease from lease manager
123
- if g.leaseManager.DeleteLease(req.Identity) {
124
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED
125
-
126
- // Remove lease connection
127
- leaseID := string(req.Identity.Id)
128
- g.leaseConnectionsLock.Lock()
129
- delete(g.leaseConnections, leaseID)
130
- g.leaseConnectionsLock.Unlock()
131
-
132
- // Log lease deletion completion
133
- log.Debug().
134
- Str("lease_id", leaseID).
135
- Msg("[RelayServer] Lease deletion completed successfully")
136
- } else {
137
- resp.Code = rdverb.ResponseCode_RESPONSE_CODE_INVALID_IDENTITY
138
- }
139
-
140
- response, err := resp.MarshalVT()
141
- if err != nil {
142
- return err
143
- }
144
-
145
- return writePacket(ctx.Stream, &rdverb.Packet{
146
- Type: rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_RESPONSE,
147
- Payload: response,
148
- })
149
-}
150
-
151
-func (g *RelayServer) handleConnectionRequest(ctx *StreamContext, packet *rdverb.Packet) error {
152
- var req rdverb.ConnectionRequest
153
- err := req.UnmarshalVT(packet.Payload)
154
- if err != nil {
155
- log.Error().Err(err).Msg("[RelayServer] Failed to unmarshal connection request")
156
- return err
157
- }
158
-
159
- log.Debug().
160
- Str("lease_id", req.LeaseId).
161
- Str("client_id", req.ClientIdentity.Id).
162
- Int64("conn_id", ctx.ConnectionID).
163
- Msg("[RelayServer] Handling connection request")
164
-
165
- // Check if lease exists and get lease connection
166
- leaseEntry, exists := g.leaseManager.GetLeaseByID(req.LeaseId)
167
- if !exists {
168
- return g.sendConnectionResponse(ctx.Stream, rdverb.ResponseCode_RESPONSE_CODE_INVALID_IDENTITY)
169
- }
170
-
171
- // Get the lease connection
172
- g.connectionsLock.RLock()
173
- leaseConn, leaseExists := g.connections[leaseEntry.ConnectionID]
174
- g.connectionsLock.RUnlock()
175
-
176
- if !leaseExists {
177
- return g.sendConnectionResponse(ctx.Stream, rdverb.ResponseCode_RESPONSE_CODE_INVALID_IDENTITY)
178
- }
179
-
180
- // Forward request to lease holder
181
- leaseStream, respCode, err := g.forwardConnectionRequest(leaseConn, &req)
182
- if err != nil {
183
- // If forwarding failed, we might need to close the stream if it was opened
184
- if leaseStream != nil {
185
- leaseStream.Close()
186
- }
187
- return g.sendConnectionResponse(ctx.Stream, respCode)
188
- }
189
-
190
- // Enforce relayed connection limits
191
- if respCode == rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
192
- leaseID := string(leaseEntry.Lease.Identity.Id)
193
- g.limitsLock.Lock()
194
- overPerLease := g.maxRelayedPerLease > 0 && g.relayedPerLeaseCount[leaseID] >= g.maxRelayedPerLease
195
- g.limitsLock.Unlock()
196
-
197
- if overPerLease {
198
- log.Warn().Str("lease_id", leaseID).Msg("[RelayServer] Relayed connection per-lease limit reached")
199
- respCode = rdverb.ResponseCode_RESPONSE_CODE_REJECTED
200
- leaseStream.Close()
201
- }
202
- }
203
-
204
- // Send response to client
205
- if err := g.sendConnectionResponse(ctx.Stream, respCode); err != nil {
206
- leaseStream.Close()
207
- return err
208
- }
209
-
210
- // If accepted, set up bidirectional forwarding
211
- if respCode == rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
212
- ctx.Hijack()
213
- go g.establishRelayedConnection(ctx.Stream, leaseStream, string(leaseEntry.Lease.Identity.Id))
214
- } else {
215
- leaseStream.Close()
216
- }
217
-
218
- return nil
219
-}
220
-
221
-// forwardConnectionRequest opens a stream to the lease holder and forwards the request
222
-func (g *RelayServer) forwardConnectionRequest(leaseConn *Connection, req *rdverb.ConnectionRequest) (*yamux.Stream, rdverb.ResponseCode, error) {
223
- leaseStream, err := leaseConn.sess.OpenStream()
224
- if err != nil {
225
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, err
226
- }
227
-
228
- reqPayload, err := req.MarshalVT()
229
- if err != nil {
230
- leaseStream.Close()
231
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, err
232
- }
233
-
234
- err = writePacket(leaseStream, &rdverb.Packet{
235
- Type: rdverb.PacketType_PACKET_TYPE_CONNECTION_REQUEST,
236
- Payload: reqPayload,
237
- })
238
- if err != nil {
239
- leaseStream.Close()
240
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, err
241
- }
242
-
243
- respPacket, err := readPacket(leaseStream)
244
- if err != nil {
245
- leaseStream.Close()
246
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, err
247
- }
248
-
249
- if respPacket.Type != rdverb.PacketType_PACKET_TYPE_CONNECTION_RESPONSE {
250
- leaseStream.Close()
251
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, nil
252
- }
253
-
254
- var resp rdverb.ConnectionResponse
255
- if err := resp.UnmarshalVT(respPacket.Payload); err != nil {
256
- leaseStream.Close()
257
- return nil, rdverb.ResponseCode_RESPONSE_CODE_REJECTED, err
258
- }
259
-
260
- return leaseStream, resp.Code, nil
261
-}
262
-
263
-func (g *RelayServer) sendConnectionResponse(stream *yamux.Stream, code rdverb.ResponseCode) error {
264
- resp := rdverb.ConnectionResponse{Code: code}
265
- payload, err := resp.MarshalVT()
266
- if err != nil {
267
- return err
268
- }
269
- return writePacket(stream, &rdverb.Packet{
270
- Type: rdverb.PacketType_PACKET_TYPE_CONNECTION_RESPONSE,
271
- Payload: payload,
272
- })
273
-}
274
-
275
-func (g *RelayServer) establishRelayedConnection(clientStream, leaseStream *yamux.Stream, leaseID string) {
276
- // Register connection for tracking
277
- g.limitsLock.Lock()
278
- g.relayedPerLeaseCount[leaseID]++
279
- g.limitsLock.Unlock()
280
-
281
- g.relayedConnectionsLock.Lock()
282
- g.relayedConnections[leaseID] = append(g.relayedConnections[leaseID], clientStream)
283
- g.relayedConnectionsLock.Unlock()
284
-
285
- // Cleanup function
286
- defer func() {
287
- g.limitsLock.Lock()
288
- if g.relayedPerLeaseCount[leaseID] > 0 {
289
- g.relayedPerLeaseCount[leaseID]--
290
- }
291
- g.limitsLock.Unlock()
292
-
293
- g.relayedConnectionsLock.Lock()
294
- if streams, ok := g.relayedConnections[leaseID]; ok {
295
- for i, s := range streams {
296
- if s == clientStream {
297
- g.relayedConnections[leaseID] = append(streams[:i], streams[i+1:]...)
298
- break
299
- }
300
- }
301
- }
302
- g.relayedConnectionsLock.Unlock()
303
- }()
304
-
305
- // Use callback for actual relay (handles BPS limiting in relay-server)
306
- if g.onEstablishRelay != nil {
307
- g.onEstablishRelay(clientStream, leaseStream, leaseID)
308
- } else {
309
- // Fallback: simple copy without rate limiting
310
- go func() {
311
- io.Copy(leaseStream, clientStream)
312
- leaseStream.Close()
313
- }()
314
- io.Copy(clientStream, leaseStream)
315
- clientStream.Close()
316
- }
317
-}
318
-
319
-// Helper function to read packet from stream
320
-func readPacket(stream io.Reader) (*rdverb.Packet, error) {
321
- var size [4]byte
322
-
323
- _, err := io.ReadFull(stream, size[:])
324
- if err != nil {
325
- return nil, err
326
- }
327
-
328
- n := int(binary.BigEndian.Uint32(size[:]))
329
- if n > _MAX_RAW_PACKET_SIZE {
330
- return nil, err
331
- }
332
-
333
- buffer := bytebufferpool.Get()
334
- defer bytebufferpool.Put(buffer)
335
-
336
- bufferGrow(buffer, n)
337
-
338
- _, err = io.ReadFull(stream, buffer.B[:n])
339
- if err != nil {
340
- return nil, err
341
- }
342
-
343
- var packet rdverb.Packet
344
- err = packet.UnmarshalVT(buffer.B[:n])
345
- if err != nil {
346
- return nil, err
347
- }
348
-
349
- return &packet, nil
350
-}
portal/helper.go
deleted
-52
@@ -1,52 +0,0 @@
1
-package portal
2
-
3
-import (
4
- "encoding/binary"
5
- "io"
6
-
7
- "github.com/valyala/bytebufferpool"
8
- "gosuda.org/portal/portal/core/proto/rdverb"
9
-)
10
-
11
-func bufferGrow(buffer *bytebufferpool.ByteBuffer, n int) {
12
- if n > cap(buffer.B) {
13
- if n > _MAX_RAW_PACKET_SIZE {
14
- n = _MAX_RAW_PACKET_SIZE
15
- }
16
- newSize := ((n + (1 << 14) - 1) / (1 << 14)) * (1 << 14)
17
- buffer.B = make([]byte, newSize)
18
- }
19
-}
20
-
21
-func decodeProtobuf[T interface {
22
- UnmarshalVT(data []byte) error
23
-}](
24
- data []byte,
25
-) (
26
- *T,
27
- error,
28
-) {
29
- var t T
30
- err := t.UnmarshalVT(data)
31
- if err != nil {
32
- return nil, err
33
- }
34
- return &t, nil
35
-}
36
-
37
-func writePacket(w io.Writer, packet *rdverb.Packet) error {
38
- payload, err := packet.MarshalVT()
39
- if err != nil {
40
- return err
41
- }
42
-
43
- buffer := bytebufferpool.Get()
44
- defer bytebufferpool.Put(buffer)
45
-
46
- var size [4]byte
47
- binary.BigEndian.PutUint32(size[:], uint32(len(payload)))
48
- buffer.Write(size[:])
49
- buffer.Write(payload)
50
- _, err = w.Write(buffer.B)
51
- return err
52
-}
portal/integration_test.go
deleted
-110
@@ -1,110 +0,0 @@
1
-package portal
2
-
3
-import (
4
- "io"
5
- "net"
6
- "testing"
7
- "time"
8
-
9
- "github.com/stretchr/testify/assert"
10
- "github.com/stretchr/testify/require"
11
- "gosuda.org/portal/portal/core/cryptoops"
12
- "gosuda.org/portal/portal/core/proto/rdverb"
13
-)
14
-
15
-// generateTestCredential creates a new credential for testing
16
-func generateTestCredential(t *testing.T) *cryptoops.Credential {
17
- cred, err := cryptoops.NewCredential()
18
- require.NoError(t, err)
19
- return cred
20
-}
21
-
22
-func TestIntegration_FullFlow(t *testing.T) {
23
- // 1. Setup Relay Server
24
- serverCred := generateTestCredential(t)
25
- server := NewRelayServer(serverCred, []string{"localhost:8080"})
26
- server.Start()
27
- defer server.Stop()
28
-
29
- // Create a listener for the server
30
- listener, err := net.Listen("tcp", "127.0.0.1:0")
31
- require.NoError(t, err)
32
- defer listener.Close()
33
-
34
- go func() {
35
- for {
36
- conn, err := listener.Accept()
37
- if err != nil {
38
- return
39
- }
40
- go server.HandleConnection(conn)
41
- }
42
- }()
43
-
44
- serverAddr := listener.Addr().String()
45
-
46
- // 2. Setup Host Client (Service Provider)
47
- hostCred := generateTestCredential(t)
48
- hostConn, err := net.Dial("tcp", serverAddr)
49
- require.NoError(t, err)
50
-
51
- hostClient := NewRelayClient(hostConn)
52
- require.NotNil(t, hostClient)
53
- defer hostClient.Close()
54
-
55
- // Register Lease
56
- lease := &rdverb.Lease{
57
- Name: "test-service",
58
- Alpn: []string{"test-proto"},
59
- }
60
- err = hostClient.RegisterLease(hostCred, lease)
61
- require.NoError(t, err)
62
-
63
- // Handle incoming connections on Host
64
- go func() {
65
- for conn := range hostClient.IncomingConnection() {
66
- go func(c *IncomingConn) {
67
- defer c.Close()
68
- // Echo server
69
- io.Copy(c, c)
70
- }(conn)
71
- }
72
- }()
73
-
74
- // 3. Setup Peer Client (Consumer)
75
- peerCred := generateTestCredential(t)
76
- peerConn, err := net.Dial("tcp", serverAddr)
77
- require.NoError(t, err)
78
-
79
- peerClient := NewRelayClient(peerConn)
80
- require.NotNil(t, peerClient)
81
- defer peerClient.Close()
82
-
83
- // 4. Peer connects to Host
84
- code, conn, err := peerClient.RequestConnection(hostCred.ID(), "test-proto", peerCred)
85
- require.NoError(t, err)
86
- assert.Equal(t, rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED, code)
87
- require.NotNil(t, conn)
88
- defer conn.Close()
89
-
90
- // 5. Verify Data Transfer
91
- message := []byte("Hello, Portal!")
92
- _, err = conn.Write(message)
93
- require.NoError(t, err)
94
-
95
- buffer := make([]byte, len(message))
96
- _, err = io.ReadFull(conn, buffer)
97
- require.NoError(t, err)
98
- assert.Equal(t, message, buffer)
99
-
100
- // 6. Verify Lease Cleanup
101
- err = hostClient.DeregisterLease(hostCred)
102
- require.NoError(t, err)
103
-
104
- // Wait a bit for propagation
105
- time.Sleep(100 * time.Millisecond)
106
-
107
- // Connection should fail now
108
- code, _, err = peerClient.RequestConnection(hostCred.ID(), "test-proto", peerCred)
109
- assert.Equal(t, rdverb.ResponseCode_RESPONSE_CODE_INVALID_IDENTITY, code)
110
-}
portal/lease.go
+85
-82
@@ -6,9 +6,6 @@ import (
6
"strings"
7
"sync"
8
"time"
9
-
10
- "gosuda.org/portal/portal/core/proto/rdsec"
11
- "gosuda.org/portal/portal/core/proto/rdverb"
9
)
10
11
// ParsedMetadata holds struct-parsed metadata for better access
@@ -20,27 +17,47 @@ type ParsedMetadata struct {
17
Hide bool `json:"hide"`
18
}
19
20
+// Lease represents a registered service.
21
+type Lease struct {
22
+ ID string `json:"id"`
23
+ Name string `json:"name"`
24
+ Address string `json:"address"` // Tunnel client address for TCP connection
25
+ Metadata Metadata `json:"metadata"`
26
+ Expires time.Time `json:"expires"`
27
+ TLSEnabled bool `json:"tls_enabled"` // Whether the tunnel client handles TLS termination
28
+ ReverseToken string `json:"-"` // shared secret for reverse connect authentication
29
+}
30
+
31
+// Metadata holds service metadata
32
+type Metadata struct {
33
+ Description string `json:"description,omitempty"`
34
+ Tags []string `json:"tags,omitempty"`
35
+ Thumbnail string `json:"thumbnail,omitempty"`
36
+ Owner string `json:"owner,omitempty"`
37
+ Hide bool `json:"hide,omitempty"`
38
+}
39
+
40
// LeaseEntry represents a registered lease with expiration tracking.
41
type LeaseEntry struct {
25
- Lease *rdverb.Lease
42
+ Lease *Lease
43
Expires time.Time
44
LastSeen time.Time
45
FirstSeen time.Time
29
- ConnectionID int64
46
ParsedMetadata *ParsedMetadata // Cached parsed metadata
47
}
48
49
type LeaseManager struct {
34
- leases map[string]*LeaseEntry // Key: identity ID
50
+ leases map[string]*LeaseEntry // Key: lease ID
51
leasesLock sync.RWMutex
52
stopCh chan struct{}
53
ttlInterval time.Duration
54
55
// policy controls
40
- bannedLeases map[string]struct{}
41
- namePattern *regexp.Regexp
42
- minTTL time.Duration // 0 = no bound
43
- maxTTL time.Duration // 0 = no bound
56
+ bannedLeases map[string]struct{}
57
+ namePattern *regexp.Regexp
58
+ minTTL time.Duration // 0 = no bound
59
+ maxTTL time.Duration // 0 = no bound
60
+ onLeaseDeleted func(string)
61
}
62
63
func NewLeaseManager(ttlInterval time.Duration) *LeaseManager {
@@ -76,25 +93,34 @@ func (lm *LeaseManager) ttlWorker() {
93
94
func (lm *LeaseManager) cleanupExpiredLeases() {
95
lm.leasesLock.Lock()
79
- defer lm.leasesLock.Unlock()
96
97
now := time.Now()
98
+ expired := make([]string, 0)
99
for id, lease := range lm.leases {
100
if now.After(lease.Expires) {
101
delete(lm.leases, id)
102
+ expired = append(expired, id)
103
}
104
}
105
+ callback := lm.onLeaseDeleted
106
+ lm.leasesLock.Unlock()
107
+
108
+ if callback == nil {
109
+ return
110
+ }
111
+ for _, id := range expired {
112
+ callback(id)
113
+ }
114
}
115
89
-func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64) bool {
116
+func (lm *LeaseManager) UpdateLease(lease *Lease) bool {
117
lm.leasesLock.Lock()
118
defer lm.leasesLock.Unlock()
119
93
- identityID := string(lease.Identity.Id)
94
- expires := time.Unix(lease.Expires, 0)
120
+ identityID := lease.ID
121
122
// Check if lease is already expired
97
- if time.Now().After(expires) {
123
+ if time.Now().After(lease.Expires) {
124
return false
125
}
126
@@ -105,9 +131,8 @@ func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64) boo
131
if lm.namePattern != nil && lease.Name != "" && !lm.namePattern.MatchString(lease.Name) {
132
return false
133
}
108
- // reserved prefix check removed
134
if lm.minTTL > 0 || lm.maxTTL > 0 {
110
- ttl := time.Until(expires)
135
+ ttl := time.Until(lease.Expires)
136
if lm.minTTL > 0 && ttl < lm.minTTL {
137
return false
138
}
@@ -133,22 +158,11 @@ func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64) boo
158
159
// Parse metadata once for cached access
160
var parsedMeta *ParsedMetadata
136
- if lease.Metadata != "" {
137
- var meta struct {
138
- Description string `json:"description"`
139
- Tags []string `json:"tags"`
140
- Thumbnail string `json:"thumbnail"`
141
- Owner string `json:"owner"`
142
- Hide bool `json:"hide"`
143
- }
144
- if err := json.Unmarshal([]byte(lease.Metadata), &meta); err == nil {
145
- parsedMeta = &ParsedMetadata{
146
- Description: meta.Description,
147
- Tags: meta.Tags,
148
- Thumbnail: meta.Thumbnail,
149
- Owner: meta.Owner,
150
- Hide: meta.Hide,
151
- }
161
+ metadataJSON, _ := json.Marshal(lease.Metadata)
162
+ if len(metadataJSON) > 0 {
163
+ var meta ParsedMetadata
164
+ if err := json.Unmarshal(metadataJSON, &meta); err == nil {
165
+ parsedMeta = &meta
166
}
167
}
168
@@ -162,45 +176,34 @@ func (lm *LeaseManager) UpdateLease(lease *rdverb.Lease, connectionID int64) boo
176
177
lm.leases[identityID] = &LeaseEntry{
178
Lease: lease,
165
- Expires: expires,
179
+ Expires: lease.Expires,
180
LastSeen: time.Now(),
181
FirstSeen: firstSeen,
168
- ConnectionID: connectionID,
182
ParsedMetadata: parsedMeta,
183
}
184
185
return true
186
}
187
175
-func (lm *LeaseManager) DeleteLease(identity *rdsec.Identity) bool {
188
+func (lm *LeaseManager) DeleteLease(leaseID string) bool {
189
lm.leasesLock.Lock()
177
- defer lm.leasesLock.Unlock()
178
-
179
- identityID := string(identity.Id)
180
- if _, exists := lm.leases[identityID]; exists {
181
- delete(lm.leases, identityID)
190
+ if _, exists := lm.leases[leaseID]; exists {
191
+ delete(lm.leases, leaseID)
192
+ callback := lm.onLeaseDeleted
193
+ lm.leasesLock.Unlock()
194
+ if callback != nil {
195
+ callback(leaseID)
196
+ }
197
return true
198
}
199
+ lm.leasesLock.Unlock()
200
return false
201
}
202
187
-func (lm *LeaseManager) GetLease(identity *rdsec.Identity) (*LeaseEntry, bool) {
188
- lm.leasesLock.RLock()
189
- defer lm.leasesLock.RUnlock()
190
-
191
- identityID := string(identity.Id)
192
-
193
- lease, exists := lm.leases[identityID]
194
- if !exists {
195
- return nil, false
196
- }
197
-
198
- // Check if lease is expired
199
- if time.Now().After(lease.Expires) {
200
- return nil, false
201
- }
202
-
203
- return lease, true
203
+func (lm *LeaseManager) SetOnLeaseDeleted(callback func(string)) {
204
+ lm.leasesLock.Lock()
205
+ defer lm.leasesLock.Unlock()
206
+ lm.onLeaseDeleted = callback
207
}
208
209
func (lm *LeaseManager) GetLeaseByID(leaseID string) (*LeaseEntry, bool) {
@@ -237,7 +240,7 @@ func (lm *LeaseManager) GetLeaseByName(name string) (*LeaseEntry, bool) {
240
for _, lease := range lm.leases {
241
if lease.Lease.Name == name {
242
// Check if banned
240
- if _, banned := lm.bannedLeases[string(lease.Lease.Identity.Id)]; banned {
243
+ if _, banned := lm.bannedLeases[lease.Lease.ID]; banned {
244
continue
245
}
246
// Check if expired
@@ -263,7 +266,7 @@ func (lm *LeaseManager) GetLeaseByNameFold(name string) (*LeaseEntry, bool) {
266
now := time.Now()
267
for _, lease := range lm.leases {
268
if strings.EqualFold(lease.Lease.Name, name) {
266
- if _, banned := lm.bannedLeases[string(lease.Lease.Identity.Id)]; banned {
269
+ if _, banned := lm.bannedLeases[lease.Lease.ID]; banned {
270
continue
271
}
272
if now.After(lease.Expires) {
@@ -275,22 +278,39 @@ func (lm *LeaseManager) GetLeaseByNameFold(name string) (*LeaseEntry, bool) {
278
return nil, false
279
}
280
278
-func (lm *LeaseManager) GetAllLeases() []*rdverb.Lease {
281
+func (lm *LeaseManager) GetAllLeases() []*Lease {
282
lm.leasesLock.RLock()
283
defer lm.leasesLock.RUnlock()
284
285
now := time.Now()
283
- var validLeases []*rdverb.Lease
286
+ var validLeases []*Lease
287
285
- for _, lease := range lm.leases {
286
- if now.Before(lease.Expires) {
287
- validLeases = append(validLeases, lease.Lease)
288
+ for _, entry := range lm.leases {
289
+ if now.Before(entry.Expires) {
290
+ validLeases = append(validLeases, entry.Lease)
291
}
292
}
293
294
return validLeases
295
}
296
297
+// GetAllLeaseEntries returns all lease entries from the lease manager
298
+func (lm *LeaseManager) GetAllLeaseEntries() []*LeaseEntry {
299
+ lm.leasesLock.RLock()
300
+ defer lm.leasesLock.RUnlock()
301
+
302
+ now := time.Now()
303
+ var entries []*LeaseEntry
304
+
305
+ for _, entry := range lm.leases {
306
+ if now.Before(entry.Expires) {
307
+ entries = append(entries, entry)
308
+ }
309
+ }
310
+
311
+ return entries
312
+}
313
+
314
// Lease policy configuration helpers
315
func (lm *LeaseManager) BanLease(leaseID string) {
316
lm.leasesLock.Lock()
@@ -329,26 +349,9 @@ func (lm *LeaseManager) SetNamePattern(pattern string) error {
349
return nil
350
}
351
332
-// SetReservedPrefixes removed: reserved prefix policy no longer supported
333
-
352
func (lm *LeaseManager) SetTTLBounds(min, max time.Duration) {
353
lm.leasesLock.Lock()
354
lm.minTTL = min
355
lm.maxTTL = max
356
lm.leasesLock.Unlock()
357
}
340
-
341
-func (lm *LeaseManager) CleanupLeasesByConnectionID(connectionID int64) []string {
342
- lm.leasesLock.Lock()
343
- defer lm.leasesLock.Unlock()
344
-
345
- var cleanedLeaseIDs []string
346
- for leaseID, lease := range lm.leases {
347
- if lease.ConnectionID == connectionID {
348
- delete(lm.leases, leaseID)
349
- cleanedLeaseIDs = append(cleanedLeaseIDs, leaseID)
350
- }
351
- }
352
-
353
- return cleanedLeaseIDs
354
-}
portal/lease_test.go
+47
-199
@@ -1,225 +1,73 @@
1
package portal
2
3
import (
4
+ "slices"
5
"testing"
6
"time"
6
-
7
- "gosuda.org/portal/portal/core/proto/rdsec"
8
- "gosuda.org/portal/portal/core/proto/rdverb"
7
)
8
11
-func TestLeaseManager_NameConflict(t *testing.T) {
12
- lm := NewLeaseManager(30 * time.Second)
13
- defer lm.Stop()
14
-
15
- // Create two different identities
16
- identity1 := &rdsec.Identity{
17
- Id: "identity-1",
18
- PublicKey: []byte("public-key-1"),
19
- }
20
-
21
- identity2 := &rdsec.Identity{
22
- Id: "identity-2",
23
- PublicKey: []byte("public-key-2"),
24
- }
9
+func TestLeaseManagerDeleteLeaseInvokesCallback(t *testing.T) {
10
+ lm := NewLeaseManager(time.Second)
11
26
- // Lease 1 with name "my-service"
27
- lease1 := &rdverb.Lease{
28
- Identity: identity1,
29
- Name: "my-service",
30
- Alpn: []string{"http/1.1"},
31
- Expires: time.Now().Add(10 * time.Minute).Unix(),
32
- }
12
+ var deleted []string
13
+ lm.SetOnLeaseDeleted(func(id string) {
14
+ deleted = append(deleted, id)
15
+ })
16
34
- // Lease 2 with the same name "my-service" but different identity
35
- lease2 := &rdverb.Lease{
36
- Identity: identity2,
37
- Name: "my-service",
38
- Alpn: []string{"http/1.1"},
39
- Expires: time.Now().Add(10 * time.Minute).Unix(),
17
+ lease := &Lease{
18
+ ID: "lease-1",
19
+ Name: "app-1",
20
+ Address: "127.0.0.1:10001",
21
+ Expires: time.Now().Add(30 * time.Second),
22
}
41
-
42
- // First lease should succeed
43
- if !lm.UpdateLease(lease1, 1) {
44
- t.Fatal("First lease registration should succeed")
23
+ if !lm.UpdateLease(lease) {
24
+ t.Fatalf("expected lease update success")
25
}
26
47
- // Second lease with same name should fail (name conflict)
48
- if lm.UpdateLease(lease2, 2) {
49
- t.Fatal("Second lease registration should fail due to name conflict")
27
+ if !lm.DeleteLease("lease-1") {
28
+ t.Fatalf("expected lease deletion success")
29
}
51
-
52
- // Verify only first lease exists
53
- entry, exists := lm.GetLeaseByID(string(identity1.Id))
54
- if !exists {
55
- t.Fatal("First lease should exist")
56
- }
57
- if entry.Lease.Name != "my-service" {
58
- t.Errorf("Expected lease name 'my-service', got '%s'", entry.Lease.Name)
59
- }
60
-
61
- // Verify second lease was not added
62
- _, exists = lm.GetLeaseByID(string(identity2.Id))
63
- if exists {
64
- t.Fatal("Second lease should not exist due to name conflict")
30
+ if !slices.Contains(deleted, "lease-1") {
31
+ t.Fatalf("expected callback with lease-1, got %v", deleted)
32
}
33
}
34
68
-func TestLeaseManager_SameIdentityUpdate(t *testing.T) {
69
- lm := NewLeaseManager(30 * time.Second)
70
- defer lm.Stop()
35
+func TestLeaseManagerCleanupExpiredLeasesInvokesCallback(t *testing.T) {
36
+ lm := NewLeaseManager(time.Second)
37
72
- identity := &rdsec.Identity{
73
- Id: "identity-1",
74
- PublicKey: []byte("public-key-1"),
75
- }
38
+ var deleted []string
39
+ lm.SetOnLeaseDeleted(func(id string) {
40
+ deleted = append(deleted, id)
41
+ })
42
77
- // Initial lease with name "my-service"
78
- lease1 := &rdverb.Lease{
79
- Identity: identity,
80
- Name: "my-service",
81
- Alpn: []string{"http/1.1"},
82
- Expires: time.Now().Add(10 * time.Minute).Unix(),
43
+ lm.leases["expired-1"] = &LeaseEntry{
44
+ Lease: &Lease{
45
+ ID: "expired-1",
46
+ Name: "expired",
47
+ Address: "127.0.0.1:10002",
48
+ Expires: time.Now().Add(-1 * time.Second),
49
+ },
50
+ Expires: time.Now().Add(-1 * time.Second),
51
}
84
-
85
- // Updated lease with same identity and same name
86
- lease2 := &rdverb.Lease{
87
- Identity: identity,
88
- Name: "my-service",
89
- Alpn: []string{"http/1.1", "h2"},
90
- Expires: time.Now().Add(15 * time.Minute).Unix(),
52
+ lm.leases["active-1"] = &LeaseEntry{
53
+ Lease: &Lease{
54
+ ID: "active-1",
55
+ Name: "active",
56
+ Address: "127.0.0.1:10003",
57
+ Expires: time.Now().Add(30 * time.Second),
58
+ },
59
+ Expires: time.Now().Add(30 * time.Second),
60
}
61
93
- // First registration
94
- if !lm.UpdateLease(lease1, 1) {
95
- t.Fatal("First lease registration should succeed")
96
- }
62
+ lm.cleanupExpiredLeases()
63
98
- // Update with same identity should succeed (no conflict)
99
- if !lm.UpdateLease(lease2, 1) {
100
- t.Fatal("Updating own lease should succeed")
64
+ if !slices.Contains(deleted, "expired-1") {
65
+ t.Fatalf("expected callback with expired-1, got %v", deleted)
66
}
102
-
103
- // Verify lease was updated
104
- entry, exists := lm.GetLeaseByID(string(identity.Id))
105
- if !exists {
106
- t.Fatal("Lease should exist")
67
+ if _, ok := lm.leases["expired-1"]; ok {
68
+ t.Fatal("expected expired-1 removed")
69
}
108
- if len(entry.Lease.Alpn) != 2 {
109
- t.Errorf("Expected 2 ALPNs, got %d", len(entry.Lease.Alpn))
110
- }
111
-}
112
-
113
-func TestLeaseManager_EmptyNameAllowed(t *testing.T) {
114
- lm := NewLeaseManager(30 * time.Second)
115
- defer lm.Stop()
116
-
117
- identity1 := &rdsec.Identity{
118
- Id: "identity-1",
119
- PublicKey: []byte("public-key-1"),
120
- }
121
-
122
- identity2 := &rdsec.Identity{
123
- Id: "identity-2",
124
- PublicKey: []byte("public-key-2"),
125
- }
126
-
127
- // Both leases with empty names should succeed
128
- lease1 := &rdverb.Lease{
129
- Identity: identity1,
130
- Name: "",
131
- Alpn: []string{"http/1.1"},
132
- Expires: time.Now().Add(10 * time.Minute).Unix(),
133
- }
134
-
135
- lease2 := &rdverb.Lease{
136
- Identity: identity2,
137
- Name: "",
138
- Alpn: []string{"http/1.1"},
139
- Expires: time.Now().Add(10 * time.Minute).Unix(),
140
- }
141
-
142
- if !lm.UpdateLease(lease1, 1) {
143
- t.Fatal("First lease with empty name should succeed")
144
- }
145
-
146
- if !lm.UpdateLease(lease2, 2) {
147
- t.Fatal("Second lease with empty name should succeed (empty names don't conflict)")
148
- }
149
-}
150
-
151
-func TestLeaseManager_UnnamedAllowed(t *testing.T) {
152
- lm := NewLeaseManager(30 * time.Second)
153
- defer lm.Stop()
154
-
155
- identity1 := &rdsec.Identity{
156
- Id: "identity-1",
157
- PublicKey: []byte("public-key-1"),
158
- }
159
-
160
- identity2 := &rdsec.Identity{
161
- Id: "identity-2",
162
- PublicKey: []byte("public-key-2"),
163
- }
164
-
165
- // Both leases with "(unnamed)" should succeed
166
- lease1 := &rdverb.Lease{
167
- Identity: identity1,
168
- Name: "(unnamed)",
169
- Alpn: []string{"http/1.1"},
170
- Expires: time.Now().Add(10 * time.Minute).Unix(),
171
- }
172
-
173
- lease2 := &rdverb.Lease{
174
- Identity: identity2,
175
- Name: "(unnamed)",
176
- Alpn: []string{"http/1.1"},
177
- Expires: time.Now().Add(10 * time.Minute).Unix(),
178
- }
179
-
180
- if !lm.UpdateLease(lease1, 1) {
181
- t.Fatal("First lease with '(unnamed)' should succeed")
182
- }
183
-
184
- if !lm.UpdateLease(lease2, 2) {
185
- t.Fatal("Second lease with '(unnamed)' should succeed (unnamed don't conflict)")
186
- }
187
-}
188
-
189
-func TestLeaseManager_UnicodeNameConflict(t *testing.T) {
190
- lm := NewLeaseManager(30 * time.Second)
191
- defer lm.Stop()
192
-
193
- identity1 := &rdsec.Identity{
194
- Id: "identity-1",
195
- PublicKey: []byte("public-key-1"),
196
- }
197
-
198
- identity2 := &rdsec.Identity{
199
- Id: "identity-2",
200
- PublicKey: []byte("public-key-2"),
201
- }
202
-
203
- // Lease with Korean name
204
- lease1 := &rdverb.Lease{
205
- Identity: identity1,
206
- Name: "한글서비스",
207
- Alpn: []string{"http/1.1"},
208
- Expires: time.Now().Add(10 * time.Minute).Unix(),
209
- }
210
-
211
- lease2 := &rdverb.Lease{
212
- Identity: identity2,
213
- Name: "한글서비스", // Same Korean name
214
- Alpn: []string{"http/1.1"},
215
- Expires: time.Now().Add(10 * time.Minute).Unix(),
216
- }
217
-
218
- if !lm.UpdateLease(lease1, 1) {
219
- t.Fatal("First lease with Korean name should succeed")
220
- }
221
-
222
- if lm.UpdateLease(lease2, 2) {
223
- t.Fatal("Second lease with same Korean name should fail")
70
+ if _, ok := lm.leases["active-1"]; !ok {
71
+ t.Fatal("expected active-1 to remain")
72
}
73
}
portal/relay.go
+33
-370
@@ -1,299 +1,53 @@
1
package portal
2
3
import (
4
- "errors"
5
- "fmt"
6
- "io"
7
- "net"
4
+ "crypto/subtle"
5
+ "strings"
6
"sync"
7
"time"
8
11
- "github.com/hashicorp/yamux"
9
"github.com/rs/zerolog/log"
13
- "gosuda.org/portal/portal/core/cryptoops"
14
- "gosuda.org/portal/portal/core/proto/rdsec"
15
- "gosuda.org/portal/portal/core/proto/rdverb"
10
)
11
18
-var (
19
- // ErrLeaseNotFound is returned when the requested lease does not exist or has expired
20
- ErrLeaseNotFound = errors.New("lease not found")
21
- // ErrConnectionNotAvailable is returned when the tunnel connection for a lease is not available
22
- ErrConnectionNotAvailable = errors.New("connection not available")
23
-)
24
-
25
-// portalAddr implements net.Addr for portal tunnel connections.
26
-type portalAddr string
27
-
28
-func (a portalAddr) Network() string { return "portal" }
29
-func (a portalAddr) String() string { return string(a) }
30
-
31
-// leaseNetConn wraps a SecureConnection as net.Conn for use with http.Transport.
32
-type leaseNetConn struct {
33
- *cryptoops.SecureConnection
34
-}
35
-
36
-func (c *leaseNetConn) LocalAddr() net.Addr { return portalAddr(c.SecureConnection.LocalID()) }
37
-func (c *leaseNetConn) RemoteAddr() net.Addr { return portalAddr(c.SecureConnection.RemoteID()) }
38
-
39
-type Connection struct {
40
- conn io.ReadWriteCloser
41
- sess *yamux.Session
42
-
43
- streams map[uint32]*yamux.Stream
44
- streamsLock sync.Mutex
45
-}
46
-
12
type RelayServer struct {
48
- credential *cryptoops.Credential
49
- identity *rdsec.Identity
50
- address []string
51
-
52
- connidCounter int64
53
- connections map[int64]*Connection
54
- connectionsLock sync.RWMutex
55
-
56
- leaseConnections map[string]*Connection // Key: lease ID, Value: Connection
57
- leaseConnectionsLock sync.RWMutex
58
-
59
- relayedConnections map[string][]*yamux.Stream // Key: lease ID, Value: slice of relayed streams
60
- relayedConnectionsLock sync.RWMutex
13
+ address []string
14
15
leaseManager *LeaseManager
16
+ reverseHub *ReverseHub
17
18
stopch chan struct{}
19
waitgroup sync.WaitGroup
66
-
67
- // Traffic control limits and counters
68
- maxRelayedPerLease int
69
- relayedPerLeaseCount map[string]int
70
- limitsLock sync.Mutex
71
-
72
- // Callback for relay connection establishment (set by relay-server for BPS handling)
73
- onEstablishRelay func(clientStream, leaseStream *yamux.Stream, leaseID string)
20
}
21
76
-func NewRelayServer(credential *cryptoops.Credential, address []string) *RelayServer {
77
- return &RelayServer{
78
- credential: credential,
79
- identity: &rdsec.Identity{
80
- Id: credential.ID(),
81
- PublicKey: credential.PublicKey(),
82
- },
83
- address: address,
84
- connidCounter: 0,
85
- connections: make(map[int64]*Connection),
86
- leaseConnections: make(map[string]*Connection),
87
- relayedConnections: make(map[string][]*yamux.Stream),
88
- leaseManager: NewLeaseManager(30 * time.Second), // TTL check every 30 seconds
89
- stopch: make(chan struct{}),
90
- relayedPerLeaseCount: make(map[string]int),
22
+// NewRelayServer creates a new relay server.
23
+func NewRelayServer(address []string) *RelayServer {
24
+ server := &RelayServer{
25
+ address: address,
26
+ leaseManager: NewLeaseManager(30 * time.Second),
27
+ reverseHub: NewReverseHub(),
28
+ stopch: make(chan struct{}),
29
}
92
-}
93
-
94
-var _yamux_config = func() *yamux.Config {
95
- cfg := yamux.DefaultConfig()
96
- cfg.MaxStreamWindowSize = 16 * 1024 * 1024 // 16MB for high-BDP scenarios
97
- cfg.StreamOpenTimeout = 75 * time.Second
98
- cfg.StreamCloseTimeout = 5 * time.Minute
99
- return cfg
100
-}()
101
-
102
-func (g *RelayServer) handleConn(id int64, connection *Connection) {
103
- log.Debug().Int64("conn_id", id).Msg("[RelayServer] Handling new connection")
104
-
105
- defer func() {
106
- log.Debug().Int64("conn_id", id).Msg("[RelayServer] Connection closing, cleaning up")
107
-
108
- // Clean up leases associated with this connection when it closes
109
- cleanedLeaseIDs := g.leaseManager.CleanupLeasesByConnectionID(id)
110
-
111
- if len(cleanedLeaseIDs) > 0 {
112
- log.Debug().
113
- Int64("conn_id", id).
114
- Strs("lease_ids", cleanedLeaseIDs).
115
- Msg("[RelayServer] Cleaned up leases for connection")
30
+ server.leaseManager.SetOnLeaseDeleted(server.reverseHub.DropLease)
31
+ server.reverseHub.SetAuthorizer(func(leaseID, token string) bool {
32
+ entry, ok := server.leaseManager.GetLeaseByID(strings.TrimSpace(leaseID))
33
+ if !ok || entry == nil || entry.Lease == nil {
34
+ return false
35
}
117
-
118
- // Also clean up lease connections mapping
119
- g.leaseConnectionsLock.Lock()
120
- for _, leaseID := range cleanedLeaseIDs {
121
- delete(g.leaseConnections, leaseID)
36
+ expected := strings.TrimSpace(entry.Lease.ReverseToken)
37
+ if expected == "" {
38
+ return false
39
}
123
- g.leaseConnectionsLock.Unlock()
124
-
125
- // Clean up relayed connections for these leases
126
- g.relayedConnectionsLock.Lock()
127
- for _, leaseID := range cleanedLeaseIDs {
128
- if streams, exists := g.relayedConnections[leaseID]; exists {
129
- // Close all relayed streams
130
- for _, stream := range streams {
131
- stream.Close()
132
- }
133
- delete(g.relayedConnections, leaseID)
134
- }
135
- }
136
- g.relayedConnectionsLock.Unlock()
137
-
138
- // Remove the connection itself
139
- g.connectionsLock.Lock()
140
- delete(g.connections, id)
141
- g.connectionsLock.Unlock()
142
-
143
- // Close the underlying connection
144
- connection.conn.Close()
145
-
146
- log.Debug().Int64("conn_id", id).Msg("[RelayServer] Connection cleanup complete")
147
- }()
148
-
149
- for {
150
- stream, err := connection.sess.AcceptStream()
151
- if err != nil {
152
- log.Debug().Err(err).Int64("conn_id", id).Msg("[RelayServer] Error accepting stream, connection closing")
153
- return
154
- }
155
- log.Debug().
156
- Int64("conn_id", id).
157
- Uint32("stream_id", stream.StreamID()).
158
- Msg("[RelayServer] Accepted new stream")
159
-
160
- connection.streamsLock.Lock()
161
- connection.streams[stream.StreamID()] = stream
162
- connection.streamsLock.Unlock()
163
- go g.handleStream(stream, id, connection)
164
- }
40
+ return subtle.ConstantTimeCompare([]byte(expected), []byte(strings.TrimSpace(token))) == 1
41
+ })
42
+ return server
43
}
44
167
-const _MAX_RAW_PACKET_SIZE = 1 << 26 // 64MB
168
-
169
-func (g *RelayServer) handleStream(stream *yamux.Stream, id int64, connection *Connection) {
170
- log.Debug().
171
- Int64("conn_id", id).
172
- Uint32("stream_id", stream.StreamID()).
173
- Msg("[RelayServer] Handling stream")
174
-
175
- var hijacked bool
176
- defer func() {
177
- stream_id := stream.StreamID()
178
- if !hijacked {
179
- log.Debug().
180
- Int64("conn_id", id).
181
- Uint32("stream_id", stream_id).
182
- Msg("[RelayServer] Closing stream")
183
- connection.streamsLock.Lock()
184
- stream.Close()
185
- delete(connection.streams, stream_id)
186
- connection.streamsLock.Unlock()
187
- } else {
188
- log.Debug().
189
- Int64("conn_id", id).
190
- Uint32("stream_id", stream_id).
191
- Msg("[RelayServer] Stream was hijacked, not closing")
192
- }
193
- }()
194
-
195
- ctx := &StreamContext{
196
- Server: g,
197
- Stream: stream,
198
- Connection: connection,
199
- ConnectionID: id,
200
- Hijacked: &hijacked,
201
- }
202
-
203
- for {
204
- packet, err := readPacket(stream)
205
- if err != nil {
206
- if err != io.EOF {
207
- log.Debug().
208
- Err(err).
209
- Int64("conn_id", id).
210
- Uint32("stream_id", stream.StreamID()).
211
- Msg("[RelayServer] Error reading packet")
212
- }
213
- return
214
- }
215
-
216
- log.Debug().
217
- Int64("conn_id", id).
218
- Uint32("stream_id", stream.StreamID()).
219
- Str("packet_type", packet.Type.String()).
220
- Msg("[RelayServer] Received packet")
221
-
222
- switch packet.Type {
223
- case rdverb.PacketType_PACKET_TYPE_RELAY_INFO_REQUEST:
224
- err = g.handleRelayInfoRequest(ctx, packet)
225
- case rdverb.PacketType_PACKET_TYPE_LEASE_UPDATE_REQUEST:
226
- err = g.handleLeaseUpdateRequest(ctx, packet)
227
- case rdverb.PacketType_PACKET_TYPE_LEASE_DELETE_REQUEST:
228
- err = g.handleLeaseDeleteRequest(ctx, packet)
229
- case rdverb.PacketType_PACKET_TYPE_CONNECTION_REQUEST:
230
- err = g.handleConnectionRequest(ctx, packet)
231
- default:
232
- log.Warn().
233
- Int64("conn_id", id).
234
- Str("packet_type", packet.Type.String()).
235
- Msg("[RelayServer] Unknown packet type")
236
- // Unknown packet type, return to close the stream
237
- return
238
- }
239
-
240
- if err != nil {
241
- log.Error().
242
- Err(err).
243
- Int64("conn_id", id).
244
- Str("packet_type", packet.Type.String()).
245
- Msg("[RelayServer] Error handling packet")
246
- return
247
- }
248
-
249
- // If the stream was hijacked, exit the loop
250
- if hijacked {
251
- log.Debug().Int64("conn_id", id).Msg("[RelayServer] Stream hijacked, exiting handler")
252
- return
253
- }
254
- }
255
-}
256
-
257
-func (g *RelayServer) HandleConnection(conn io.ReadWriteCloser) error {
258
- log.Debug().Msg("[RelayServer] New connection received")
259
-
260
- sess, err := yamux.Server(conn, _yamux_config)
261
- if err != nil {
262
- log.Error().Err(err).Msg("[RelayServer] Failed to create yamux server session")
263
- return err
264
- }
265
-
266
- g.connectionsLock.Lock()
267
- g.connidCounter++
268
- connID := g.connidCounter
269
- connection := &Connection{
270
- conn: conn,
271
- sess: sess,
272
- streams: make(map[uint32]*yamux.Stream),
273
- }
274
- g.connections[connID] = connection
275
- g.connectionsLock.Unlock()
276
-
277
- log.Debug().Int64("conn_id", connID).Msg("[RelayServer] Connection registered, starting handler")
278
- go g.handleConn(connID, connection)
279
-
280
- return nil
281
-}
282
-
283
-func (g *RelayServer) relayInfo() *rdverb.RelayInfo {
284
- return &rdverb.RelayInfo{
285
- Identity: g.identity,
286
- Address: g.address,
287
- Leases: g.leaseManager.GetAllLeases(),
288
- }
289
-}
290
-
291
-// GetLeaseManager returns the lease manager instance
45
+// GetLeaseManager returns the lease manager instance.
46
func (g *RelayServer) GetLeaseManager() *LeaseManager {
47
return g.leaseManager
48
}
49
296
-// GetLeaseByName returns a lease entry by its name
50
+// GetLeaseByName returns a lease entry by its name.
51
func (g *RelayServer) GetLeaseByName(name string) (*LeaseEntry, bool) {
52
return g.leaseManager.GetLeaseByName(name)
53
}
@@ -303,117 +57,26 @@ func (g *RelayServer) GetLeaseByNameFold(name string) (*LeaseEntry, bool) {
57
return g.leaseManager.GetLeaseByNameFold(name)
58
}
59
306
-// IsConnectionActive checks if a connection with the given ID is still active
307
-func (g *RelayServer) IsConnectionActive(connectionID int64) bool {
308
- g.connectionsLock.RLock()
309
- defer g.connectionsLock.RUnlock()
310
-
311
- _, exists := g.connections[connectionID]
312
- return exists
313
-}
314
-
315
-// GetAllLeaseEntries returns all lease entries from the lease manager
60
+// GetAllLeaseEntries returns all lease entries from the lease manager.
61
func (g *RelayServer) GetAllLeaseEntries() []*LeaseEntry {
317
- g.leaseManager.leasesLock.RLock()
318
- defer g.leaseManager.leasesLock.RUnlock()
319
-
320
- var entries []*LeaseEntry
321
- now := time.Now()
322
-
323
- for _, entry := range g.leaseManager.leases {
324
- if now.Before(entry.Expires) {
325
- entries = append(entries, entry)
326
- }
327
- }
328
-
329
- return entries
62
+ return g.leaseManager.GetAllLeaseEntries()
63
}
64
332
-// GetLeaseALPNs returns the ALPN identifiers for a given lease ID
333
-func (g *RelayServer) GetLeaseALPNs(leaseID string) []string {
334
- g.leaseManager.leasesLock.RLock()
335
- defer g.leaseManager.leasesLock.RUnlock()
336
-
337
- entry, exists := g.leaseManager.leases[leaseID]
338
- if !exists {
339
- return nil
340
- }
341
-
342
- now := time.Now()
343
- if now.After(entry.Expires) {
344
- return nil
345
- }
346
-
347
- return entry.Lease.Alpn
65
+// GetReverseHub returns the reverse hub instance.
66
+func (g *RelayServer) GetReverseHub() *ReverseHub {
67
+ return g.reverseHub
68
}
69
70
+// Start starts the relay server.
71
func (g *RelayServer) Start() {
72
g.leaseManager.Start()
73
+ log.Info().Msg("[RelayServer] Started")
74
}
75
76
+// Stop stops the relay server.
77
func (g *RelayServer) Stop() {
78
close(g.stopch)
79
g.leaseManager.Stop()
80
g.waitgroup.Wait()
358
-}
359
-
360
-// Traffic control setters
361
-func (g *RelayServer) SetMaxRelayedPerLease(n int) {
362
- g.limitsLock.Lock()
363
- g.maxRelayedPerLease = n
364
- g.limitsLock.Unlock()
365
-}
366
-
367
-// SetEstablishRelayCallback sets the callback for relay connection establishment
368
-// This allows external code (e.g., relay-server) to handle BPS limiting
369
-func (g *RelayServer) SetEstablishRelayCallback(
370
- callback func(clientStream, leaseStream *yamux.Stream, leaseID string),
371
-) {
372
- g.onEstablishRelay = callback
373
-}
374
-
375
-// DialLease establishes a direct connection to a tunnel client's lease,
376
-// using the relay server's own credential for the RDSEC handshake.
377
-// Returns a net.Conn that transparently encrypts/decrypts through the tunnel.
378
-func (g *RelayServer) DialLease(leaseID, alpn string) (net.Conn, error) {
379
- // 1. Look up lease entry
380
- leaseEntry, exists := g.leaseManager.GetLeaseByID(leaseID)
381
- if !exists {
382
- return nil, ErrLeaseNotFound
383
- }
384
-
385
- // 2. Get the tunnel client's yamux Connection
386
- g.connectionsLock.RLock()
387
- conn, connExists := g.connections[leaseEntry.ConnectionID]
388
- g.connectionsLock.RUnlock()
389
- if !connExists {
390
- return nil, ErrConnectionNotAvailable
391
- }
392
-
393
- // 3. Forward CONNECTION_REQUEST to tunnel client and get acceptance
394
- req := &rdverb.ConnectionRequest{
395
- LeaseId: leaseID,
396
- ClientIdentity: g.identity,
397
- }
398
- leaseStream, respCode, err := g.forwardConnectionRequest(conn, req)
399
- if err != nil {
400
- if leaseStream != nil {
401
- leaseStream.Close()
402
- }
403
- return nil, fmt.Errorf("forward connection request: %w", err)
404
- }
405
- if respCode != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
406
- leaseStream.Close()
407
- return nil, ErrConnectionRejected
408
- }
409
-
410
- // 4. Perform RDSEC client handshake (relay acts as "client" to the tunnel)
411
- handshaker := cryptoops.NewHandshaker(g.credential)
412
- secConn, err := handshaker.ClientHandshake(leaseStream, alpn)
413
- if err != nil {
414
- leaseStream.Close()
415
- return nil, fmt.Errorf("client handshake: %w", err)
416
- }
417
-
418
- return &leaseNetConn{SecureConnection: secConn}, nil
81
+ log.Info().Msg("[RelayServer] Stopped")
82
}
portal/reverse_hub.go
new
+213
@@ -0,0 +1,213 @@
1
+package portal
2
+
3
+import (
4
+ "fmt"
5
+ "net"
6
+ "strings"
7
+ "sync"
8
+ "time"
9
+
10
+ "github.com/rs/zerolog/log"
11
+ "golang.org/x/net/websocket"
12
+)
13
+
14
+const (
15
+ ReverseStartMarker = byte(0x01)
16
+ ReverseQueueSize = 64
17
+ ReverseAcquireWait = 2 * time.Second
18
+ ReverseHTTPWait = 1500 * time.Millisecond
19
+ ReverseSNIAcquireWait = 2 * time.Second
20
+)
21
+
22
+type ReverseConn struct {
23
+ Conn net.Conn
24
+ done chan struct{}
25
+ once sync.Once
26
+}
27
+
28
+func NewReverseConn(conn net.Conn) *ReverseConn {
29
+ return &ReverseConn{
30
+ Conn: conn,
31
+ done: make(chan struct{}),
32
+ }
33
+}
34
+
35
+func (c *ReverseConn) Close() {
36
+ c.Conn.Close()
37
+ c.once.Do(func() {
38
+ close(c.done)
39
+ })
40
+}
41
+
42
+func (c *ReverseConn) Wait() {
43
+ <-c.done
44
+}
45
+
46
+type ReverseHub struct {
47
+ mu sync.RWMutex
48
+ pending map[string]chan *ReverseConn
49
+ authorizer func(string, string) bool
50
+}
51
+
52
+func NewReverseHub() *ReverseHub {
53
+ return &ReverseHub{
54
+ pending: make(map[string]chan *ReverseConn),
55
+ }
56
+}
57
+
58
+func (h *ReverseHub) getOrCreate(leaseID string) chan *ReverseConn {
59
+ h.mu.Lock()
60
+ defer h.mu.Unlock()
61
+
62
+ ch, ok := h.pending[leaseID]
63
+ if ok {
64
+ return ch
65
+ }
66
+ ch = make(chan *ReverseConn, ReverseQueueSize)
67
+ h.pending[leaseID] = ch
68
+ return ch
69
+}
70
+
71
+func (h *ReverseHub) get(leaseID string) (chan *ReverseConn, bool) {
72
+ h.mu.RLock()
73
+ defer h.mu.RUnlock()
74
+ ch, ok := h.pending[leaseID]
75
+ return ch, ok
76
+}
77
+
78
+func (h *ReverseHub) SetAuthorizer(authorizer func(string, string) bool) {
79
+ h.mu.Lock()
80
+ defer h.mu.Unlock()
81
+ h.authorizer = authorizer
82
+}
83
+
84
+func (h *ReverseHub) isAuthorized(leaseID, token string) bool {
85
+ h.mu.RLock()
86
+ authorizer := h.authorizer
87
+ h.mu.RUnlock()
88
+ if authorizer == nil {
89
+ return false
90
+ }
91
+ return authorizer(leaseID, token)
92
+}
93
+
94
+func (h *ReverseHub) Offer(leaseID string, conn *ReverseConn) bool {
95
+ ch := h.getOrCreate(leaseID)
96
+ select {
97
+ case ch <- conn:
98
+ return true
99
+ default:
100
+ return false
101
+ }
102
+}
103
+
104
+func (h *ReverseHub) Acquire(leaseID string, timeout time.Duration) (*ReverseConn, error) {
105
+ ch, ok := h.get(leaseID)
106
+ if !ok {
107
+ return nil, fmt.Errorf("no reverse tunnel for lease %s", leaseID)
108
+ }
109
+
110
+ if timeout <= 0 {
111
+ timeout = ReverseAcquireWait
112
+ }
113
+
114
+ timer := time.NewTimer(timeout)
115
+ defer timer.Stop()
116
+
117
+ select {
118
+ case conn := <-ch:
119
+ if conn == nil {
120
+ return nil, fmt.Errorf("reverse tunnel unavailable for lease %s", leaseID)
121
+ }
122
+ return conn, nil
123
+ case <-timer.C:
124
+ return nil, fmt.Errorf("reverse tunnel timeout for lease %s", leaseID)
125
+ }
126
+}
127
+
128
+func (h *ReverseHub) AcquireStarted(leaseID string, timeout time.Duration) (*ReverseConn, error) {
129
+ if timeout <= 0 {
130
+ timeout = ReverseAcquireWait
131
+ }
132
+
133
+ deadline := time.Now().Add(timeout)
134
+ for {
135
+ remaining := time.Until(deadline)
136
+ if remaining <= 0 {
137
+ return nil, fmt.Errorf("reverse tunnel timeout for lease %s", leaseID)
138
+ }
139
+
140
+ conn, err := h.Acquire(leaseID, remaining)
141
+ if err != nil {
142
+ return nil, err
143
+ }
144
+
145
+ _ = conn.Conn.SetWriteDeadline(time.Now().Add(2 * time.Second))
146
+ _, err = conn.Conn.Write([]byte{ReverseStartMarker})
147
+ _ = conn.Conn.SetWriteDeadline(time.Time{})
148
+ if err == nil {
149
+ return conn, nil
150
+ }
151
+
152
+ log.Warn().
153
+ Err(err).
154
+ Str("lease_id", leaseID).
155
+ Msg("[ReverseHub] Failed to start reverse stream; retrying")
156
+ conn.Close()
157
+ }
158
+}
159
+
160
+func (h *ReverseHub) DropLease(leaseID string) {
161
+ h.mu.Lock()
162
+ ch, ok := h.pending[leaseID]
163
+ if ok {
164
+ delete(h.pending, leaseID)
165
+ }
166
+ h.mu.Unlock()
167
+
168
+ if !ok {
169
+ return
170
+ }
171
+
172
+ for {
173
+ select {
174
+ case conn := <-ch:
175
+ if conn != nil {
176
+ conn.Close()
177
+ }
178
+ default:
179
+ return
180
+ }
181
+ }
182
+}
183
+
184
+func (h *ReverseHub) HandleConnect(ws *websocket.Conn) {
185
+ ws.PayloadType = websocket.BinaryFrame
186
+
187
+ req := ws.Request()
188
+ leaseID := ""
189
+ token := ""
190
+ if req != nil {
191
+ leaseID = strings.TrimSpace(req.URL.Query().Get("lease_id"))
192
+ token = strings.TrimSpace(req.URL.Query().Get("token"))
193
+ }
194
+ if leaseID == "" {
195
+ log.Warn().Msg("[ReverseHub] Missing lease_id on reverse connect")
196
+ ws.Close()
197
+ return
198
+ }
199
+ if !h.isAuthorized(leaseID, token) {
200
+ log.Warn().Str("lease_id", leaseID).Msg("[ReverseHub] Unauthorized reverse connect")
201
+ ws.Close()
202
+ return
203
+ }
204
+
205
+ conn := NewReverseConn(ws)
206
+ if !h.Offer(leaseID, conn) {
207
+ log.Warn().Str("lease_id", leaseID).Msg("[ReverseHub] Reverse queue full")
208
+ conn.Close()
209
+ return
210
+ }
211
+
212
+ conn.Wait()
213
+}
portal/reverse_hub_test.go
new
+22
@@ -0,0 +1,22 @@
1
+package portal
2
+
3
+import "testing"
4
+
5
+func TestReverseHubAuthorization(t *testing.T) {
6
+ hub := NewReverseHub()
7
+
8
+ if hub.isAuthorized("lease-1", "token-1") {
9
+ t.Fatal("expected unauthorized when authorizer is not configured")
10
+ }
11
+
12
+ hub.SetAuthorizer(func(leaseID, token string) bool {
13
+ return leaseID == "lease-1" && token == "token-1"
14
+ })
15
+
16
+ if !hub.isAuthorized("lease-1", "token-1") {
17
+ t.Fatal("expected authorized")
18
+ }
19
+ if hub.isAuthorized("lease-1", "wrong-token") {
20
+ t.Fatal("expected unauthorized for wrong token")
21
+ }
22
+}
portal/utils/randpool/randpool.go
deleted
-74
@@ -1,74 +0,0 @@
1
-package randpool
2
-
3
-import (
4
- "crypto/rand"
5
- "io"
6
- "log"
7
- "sync"
8
-
9
- "golang.org/x/crypto/chacha20"
10
-)
11
-
12
-var (
13
- _csprng_fallback_mu sync.Mutex
14
- _csprng_fallback = func() *chacha20.Cipher {
15
- var initdata [12 + 32]byte // 12 byte nonce, 32 byte key
16
- _, err := io.ReadFull(rand.Reader, initdata[:])
17
- if err != nil {
18
- panic(err)
19
- }
20
- c, err := chacha20.NewUnauthenticatedCipher(initdata[12:], initdata[:12])
21
- if err != nil {
22
- panic(err)
23
- }
24
- return c
25
- }()
26
-)
27
-
28
-type chacha20rng struct {
29
- c *chacha20.Cipher
30
- used uint64
31
-}
32
-
33
-var _chacha20rngPool = sync.Pool{
34
- New: func() interface{} {
35
- var initdata [12 + 32]byte // 12 byte nonce, 32 byte key
36
- _, err := rand.Read(initdata[:])
37
- if err != nil {
38
- // if system rand fails, use fallback and print log
39
- log.Println("randpool: chacha20rng init failed to read from system rand, using fallback")
40
- _csprng_fallback_mu.Lock()
41
- _csprng_fallback.XORKeyStream(initdata[:], initdata[:])
42
- _csprng_fallback_mu.Unlock()
43
- }
44
- c, err := chacha20.NewUnauthenticatedCipher(initdata[12:], initdata[:12])
45
- if err != nil {
46
- panic(err) // should never happen
47
- }
48
- return &chacha20rng{
49
- c: c,
50
- }
51
- },
52
-}
53
-
54
-func _chacha20rng() *chacha20rng {
55
- return _chacha20rngPool.Get().(*chacha20rng)
56
-}
57
-
58
-func chacha20rand(dst []byte) {
59
- c := _chacha20rng()
60
- c.used += uint64(len(dst))
61
- // Zero out the destination buffer to ensure we overwrite instead of XOR
62
- for i := range dst {
63
- dst[i] = 0
64
- }
65
- c.c.XORKeyStream(dst, dst)
66
- if c.used < 50*1<<30 {
67
- // Return to pool only if we haven't used more than 50GiB
68
- _chacha20rngPool.Put(c)
69
- }
70
-}
71
-
72
-func Rand(dst []byte) {
73
- chacha20rand(dst)
74
-}
portal/utils/randpool/randpool_test.go
deleted
-51
@@ -1,51 +0,0 @@
1
-package randpool
2
-
3
-import (
4
- "bytes"
5
- "testing"
6
-)
7
-
8
-func TestRandOverwrite(t *testing.T) {
9
- // Create a buffer with known data
10
- buf := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
11
- original := make([]byte, len(buf))
12
- copy(original, buf)
13
-
14
- // Call Rand
15
- Rand(buf)
16
-
17
- // Verify that the buffer has changed
18
- if bytes.Equal(buf, original) {
19
- t.Error("Buffer should have changed after Rand")
20
- }
21
-
22
- // Verify that it's not just XORed (though hard to prove deterministically without mocking,
23
- // the fact that we zeroed it in code gives us confidence.
24
- // If it was XORed with 0xFF, the result would be (stream ^ 0xFF).
25
- // Since we zeroed it, the result is (stream ^ 0x00) = stream.
26
- // We can't easily distinguish stream vs stream^0xFF without knowing stream.
27
- // But we can check that running it twice produces different results.
28
-
29
- buf2 := make([]byte, 5) // Zeros
30
- Rand(buf2)
31
-
32
- if bytes.Equal(buf, buf2) {
33
- t.Error("Two random calls produced same output")
34
- }
35
-}
36
-
37
-func TestRandConcurrency(t *testing.T) {
38
- // Just run a bunch of goroutines to trigger the pool and potential race conditions
39
- // (though the fallback race is hard to trigger without fault injection)
40
- done := make(chan bool)
41
- for range 100 {
42
- go func() {
43
- buf := make([]byte, 32)
44
- Rand(buf)
45
- done <- true
46
- }()
47
- }
48
- for range 100 {
49
- <-done
50
- }
51
-}
portal/utils/ratelimit/bucket.go
deleted
-101
@@ -1,101 +0,0 @@
1
-package ratelimit
2
-
3
-import (
4
- "io"
5
- "sync"
6
- "time"
7
-)
8
-
9
-// Bucket is a very simple thread-safe rate limiter.
10
-// It uses a shared timeline (allowAt) with a fixed per-byte duration and
11
-// a maximum slack to model burst capacity.
12
-type Bucket struct {
13
- mu sync.Mutex
14
- perByte time.Duration // time per byte
15
- maxSlack time.Duration // maximum credit time (burst)
16
- allowAt time.Time // next allowed time on the timeline
17
-}
18
-
19
-// NewBucket creates a limiter for rateBps with burst bytes.
20
-// burst is translated to time slack = burst * perByte.
21
-func NewBucket(rateBps int64, burst int64) *Bucket {
22
- if rateBps <= 0 {
23
- return nil
24
- }
25
- if burst <= 0 {
26
- burst = rateBps
27
- }
28
- perByte := time.Second / time.Duration(rateBps)
29
- if perByte <= 0 {
30
- perByte = time.Nanosecond
31
- }
32
- maxSlack := perByte * time.Duration(burst)
33
- now := time.Now()
34
- // Start with full burst credit available
35
- allowAt := now.Add(-maxSlack)
36
- return &Bucket{perByte: perByte, maxSlack: maxSlack, allowAt: allowAt}
37
-}
38
-
39
-// Take blocks long enough to account for n bytes at the configured rate.
40
-// It is safe for concurrent use and coordinates consumers by a shared timeline.
41
-func (b *Bucket) Take(n int64) {
42
- if b == nil || n <= 0 {
43
- return
44
- }
45
- b.mu.Lock()
46
- now := time.Now()
47
- // Refill slack over time up to maxSlack
48
- if now.Sub(b.allowAt) > b.maxSlack {
49
- b.allowAt = now.Add(-b.maxSlack)
50
- }
51
- start := b.allowAt
52
- finish := start.Add(b.perByte * time.Duration(n))
53
- b.allowAt = finish
54
- b.mu.Unlock()
55
-
56
- if sleep := finish.Sub(now); sleep > 0 {
57
- time.Sleep(sleep)
58
- }
59
-}
60
-
61
-// internal buffer pool for Copy
62
-// Using *[]byte to avoid interface boxing allocation in sync.Pool.
63
-var bufPool = sync.Pool{New: func() any {
64
- b := make([]byte, 64*1024)
65
- return &b
66
-}}
67
-
68
-// Copy copies from src to dst, enforcing the provided byte-rate bucket if not nil.
69
-// Returns bytes written and any copy error encountered.
70
-func Copy(dst io.Writer, src io.Reader, b *Bucket) (int64, error) {
71
- if b == nil {
72
- return io.Copy(dst, src)
73
- }
74
- buf := *bufPool.Get().(*[]byte)
75
- defer bufPool.Put(&buf)
76
-
77
- var total int64
78
- for {
79
- nr, er := src.Read(buf)
80
- if nr > 0 {
81
- b.Take(int64(nr))
82
- nw, ew := dst.Write(buf[:nr])
83
- if nw > 0 {
84
- total += int64(nw)
85
- }
86
- if ew != nil {
87
- return total, ew
88
- }
89
- if nr != nw {
90
- return total, io.ErrShortWrite
91
- }
92
- }
93
- if er != nil {
94
- if er == io.EOF {
95
- break
96
- }
97
- return total, er
98
- }
99
- }
100
- return total, nil
101
-}
portal/utils/ratelimit/bucket_test.go
deleted
-424
@@ -1,424 +0,0 @@
1
-package ratelimit
2
-
3
-import (
4
- "bytes"
5
- "errors"
6
- "io"
7
- "strings"
8
- "sync"
9
- "testing"
10
- "time"
11
-)
12
-
13
-// This test keeps expectations deliberately loose to avoid flakiness
14
-// while still catching gross misbehavior.
15
-func TestSimpleRateAndBurst(t *testing.T) {
16
- rate := int64(1 * 1024 * 1024) // 1 MiB/s
17
- burst := rate // allow ~1s worth of burst
18
- b := NewBucket(rate, burst)
19
- if b == nil {
20
- t.Fatalf("bucket should not be nil for positive rate")
21
- }
22
-
23
- // First half-burst should complete quickly (use a generous threshold)
24
- start := time.Now()
25
- b.Take(burst / 2)
26
- fast := time.Since(start)
27
- if fast > 200*time.Millisecond {
28
- t.Fatalf("half-burst took too long: %v", fast)
29
- }
30
-
31
- // Taking 2*rate bytes should take roughly ~1s given 1s burst credit.
32
- start = time.Now()
33
- b.Take(2 * rate)
34
- elapsed := time.Since(start)
35
- if elapsed < 700*time.Millisecond { // be tolerant to scheduling variance
36
- t.Fatalf("expected at least ~0.7s throttling, got %v", elapsed)
37
- }
38
-}
39
-
40
-// TestNewBucketInvalidRate tests that NewBucket returns nil for non-positive rates
41
-func TestNewBucketInvalidRate(t *testing.T) {
42
- tests := []struct {
43
- name string
44
- rate int64
45
- burst int64
46
- }{
47
- {"zero rate", 0, 100},
48
- {"negative rate", -100, 100},
49
- {"negative rate with positive burst", -1, 1},
50
- }
51
-
52
- for _, tt := range tests {
53
- t.Run(tt.name, func(t *testing.T) {
54
- b := NewBucket(tt.rate, tt.burst)
55
- if b != nil {
56
- t.Errorf("NewBucket(%d, %d) should return nil for invalid rate", tt.rate, tt.burst)
57
- }
58
- })
59
- }
60
-}
61
-
62
-// TestNewBucketDefaultBurst tests that burst defaults to rate when burst <= 0
63
-func TestNewBucketDefaultBurst(t *testing.T) {
64
- rate := int64(1000)
65
- b := NewBucket(rate, 0) // zero burst
66
- if b == nil {
67
- t.Fatal("NewBucket should not return nil for positive rate with zero burst")
68
- }
69
- if b.maxSlack <= 0 {
70
- t.Errorf("expected positive maxSlack, got %v", b.maxSlack)
71
- }
72
-
73
- // burst should default to rate, so maxSlack should equal perByte * rate
74
- expectedSlack := b.perByte * time.Duration(rate)
75
- if b.maxSlack != expectedSlack {
76
- t.Errorf("maxSlack = %v, want %v", b.maxSlack, expectedSlack)
77
- }
78
-
79
- // Test with negative burst
80
- b2 := NewBucket(rate, -100)
81
- if b2 == nil {
82
- t.Fatal("NewBucket should not return nil for positive rate with negative burst")
83
- }
84
- if b2.maxSlack != expectedSlack {
85
- t.Errorf("maxSlack with negative burst = %v, want %v", b2.maxSlack, expectedSlack)
86
- }
87
-}
88
-
89
-// TestNewBucketHighRate tests edge case where perByte could be 0 for very high rates
90
-func TestNewBucketHighRate(t *testing.T) {
91
- // Use a very high rate that could cause perByte to be 0
92
- rate := int64(1e18) // Extremely high rate
93
- burst := int64(100)
94
- b := NewBucket(rate, burst)
95
- if b == nil {
96
- t.Fatal("NewBucket should not return nil for very high rate")
97
- }
98
- // perByte should be at least 1 nanosecond
99
- if b.perByte < time.Nanosecond {
100
- t.Errorf("perByte = %v, want >= %v", b.perByte, time.Nanosecond)
101
- }
102
-}
103
-
104
-// TestTakeNilBucket tests that Take handles nil bucket gracefully
105
-func TestTakeNilBucket(t *testing.T) {
106
- // Should not panic
107
- var b *Bucket = nil
108
- b.Take(100) // Should just return without panicking
109
-}
110
-
111
-// TestTakeNonPositiveBytes tests that Take handles non-positive byte counts
112
-func TestTakeNonPositiveBytes(t *testing.T) {
113
- rate := int64(1000)
114
- b := NewBucket(rate, rate)
115
- if b == nil {
116
- t.Fatal("NewBucket failed")
117
- }
118
-
119
- // Should not block or panic for non-positive values
120
- b.Take(0)
121
- b.Take(-1)
122
- b.Take(-100)
123
-}
124
-
125
-// TestTakeSlackRefill tests the slack refill logic over time
126
-func TestTakeSlackRefill(t *testing.T) {
127
- rate := int64(1000) // 1000 bytes/sec
128
- burst := int64(500) // 0.5 sec burst
129
- b := NewBucket(rate, burst)
130
- if b == nil {
131
- t.Fatal("NewBucket failed")
132
- }
133
-
134
- // Exhaust the burst
135
- b.Take(burst)
136
- initialAllowAt := b.allowAt
137
-
138
- // Wait for slack to refill (more than maxSlack duration)
139
- time.Sleep(time.Duration(2*burst*int64(time.Second)/rate) + 100*time.Millisecond)
140
-
141
- // Force state update since refill is lazy
142
- b.Take(1)
143
-
144
- b.mu.Lock()
145
- allowAtAfterSleep := b.allowAt
146
- b.mu.Unlock()
147
-
148
- // allowAt should have moved forward due to slack refill cap
149
- // After sleeping more than maxSlack, the timeline should be capped at now - maxSlack
150
- if allowAtAfterSleep.Equal(initialAllowAt) {
151
- t.Error("allowAt should have moved forward after sleep")
152
- }
153
-}
154
-
155
-// TestTakeConcurrent tests concurrent Take calls
156
-func TestTakeConcurrent(t *testing.T) {
157
- rate := int64(100 * 1024) // 100 KiB/s
158
- burst := rate / 10
159
- b := NewBucket(rate, burst)
160
- if b == nil {
161
- t.Fatal("NewBucket failed")
162
- }
163
-
164
- const numGoroutines = 10
165
- const bytesPerGoroutine = int64(10 * 1024) // 10 KiB each
166
-
167
- var wg sync.WaitGroup
168
- wg.Add(numGoroutines)
169
-
170
- start := time.Now()
171
- for range numGoroutines {
172
- go func() {
173
- defer wg.Done()
174
- b.Take(bytesPerGoroutine)
175
- }()
176
- }
177
- wg.Wait()
178
- elapsed := time.Since(start)
179
-
180
- // Total bytes: numGoroutines * bytesPerGoroutine = 100 KiB
181
- // At 100 KiB/s, should take roughly 1 second (minus burst)
182
- // Should at least take some time (not complete instantly)
183
- if elapsed < 500*time.Millisecond {
184
- t.Errorf("concurrent Takes completed too quickly: %v", elapsed)
185
- }
186
-}
187
-
188
-// TestTakeSequentialBurst tests sequential Takes within burst capacity
189
-func TestTakeSequentialBurst(t *testing.T) {
190
- rate := int64(10 * 1024) // 10 KiB/s
191
- burst := rate // 1 second burst
192
- b := NewBucket(rate, burst)
193
- if b == nil {
194
- t.Fatal("NewBucket failed")
195
- }
196
-
197
- // All Takes within burst should complete quickly
198
- start := time.Now()
199
- for range 10 {
200
- b.Take(rate / 10) // Take 1/10 of burst each time
201
- }
202
- elapsed := time.Since(start)
203
-
204
- if elapsed > 100*time.Millisecond {
205
- t.Errorf("burst Takes took too long: %v", elapsed)
206
- }
207
-}
208
-
209
-// TestCopyNilBucket tests that Copy with nil bucket just calls io.Copy
210
-func TestCopyNilBucket(t *testing.T) {
211
- src := strings.NewReader("hello, world")
212
- var dst bytes.Buffer
213
-
214
- n, err := Copy(&dst, src, nil)
215
- if err != nil {
216
- t.Fatalf("Copy failed: %v", err)
217
- }
218
- if n != int64(len("hello, world")) {
219
- t.Errorf("copied %d bytes, want %d", n, len("hello, world"))
220
- }
221
- if dst.String() != "hello, world" {
222
- t.Errorf("copied data = %q, want %q", dst.String(), "hello, world")
223
- }
224
-}
225
-
226
-// TestCopyWithRateLimit tests that Copy properly rate limits
227
-func TestCopyWithRateLimit(t *testing.T) {
228
- rate := int64(512 * 1024) // 512 KiB/s
229
- burst := rate
230
- data := make([]byte, 256*1024) // 256 KiB (half burst)
231
-
232
- src := bytes.NewReader(data)
233
- var dst bytes.Buffer
234
- b := NewBucket(rate, burst)
235
- if b == nil {
236
- t.Fatal("NewBucket failed")
237
- }
238
-
239
- start := time.Now()
240
- n, err := Copy(&dst, src, b)
241
- elapsed := time.Since(start)
242
-
243
- if err != nil {
244
- t.Fatalf("Copy failed: %v", err)
245
- }
246
- if n != int64(len(data)) {
247
- t.Errorf("copied %d bytes, want %d", n, len(data))
248
- }
249
- // Should complete quickly since we're within burst capacity
250
- if elapsed > 200*time.Millisecond {
251
- t.Errorf("Copy took too long: %v", elapsed)
252
- }
253
-}
254
-
255
-// TestCopyErrorHandling tests Copy error handling paths
256
-func TestCopyErrorHandling(t *testing.T) {
257
- // Test reader error
258
- errReader := &errReader{err: errors.New("read error")}
259
- var dst bytes.Buffer
260
- b := NewBucket(1000, 1000)
261
-
262
- _, err := Copy(&dst, errReader, b)
263
- if err == nil {
264
- t.Error("expected error from reader, got nil")
265
- }
266
- if err != errReader.err {
267
- t.Errorf("got error %v, want %v", err, errReader.err)
268
- }
269
-}
270
-
271
-// TestCopyShortWrite tests short write detection
272
-func TestCopyShortWrite(t *testing.T) {
273
- data := []byte("hello world")
274
- shortWriter := &shortWriter{maxWrite: 3} // Only writes 3 bytes at a time
275
- src := bytes.NewReader(data)
276
- b := NewBucket(1000, 1000)
277
-
278
- n, err := Copy(shortWriter, src, b)
279
- if err != io.ErrShortWrite {
280
- t.Errorf("got error %v, want %v", err, io.ErrShortWrite)
281
- }
282
- // Should have written some bytes but not all
283
- if n == 0 {
284
- t.Error("expected some bytes to be written")
285
- }
286
-}
287
-
288
-// TestCopyConcurrent tests concurrent Copy operations
289
-func TestCopyConcurrent(t *testing.T) {
290
- rate := int64(100 * 1024) // 100 KiB/s
291
- burst := rate / 10
292
- b := NewBucket(rate, burst)
293
- if b == nil {
294
- t.Fatal("NewBucket failed")
295
- }
296
-
297
- const numGoroutines = 5
298
- data := make([]byte, 10*1024) // 10 KiB each
299
-
300
- var wg sync.WaitGroup
301
- wg.Add(numGoroutines)
302
-
303
- start := time.Now()
304
- for range numGoroutines {
305
- go func() {
306
- defer wg.Done()
307
- src := bytes.NewReader(data)
308
- var dst bytes.Buffer
309
- Copy(&dst, src, b)
310
- }()
311
- }
312
- wg.Wait()
313
- elapsed := time.Since(start)
314
-
315
- // Should take some time due to rate limiting
316
- if elapsed < 300*time.Millisecond {
317
- t.Errorf("concurrent Copies completed too quickly: %v", elapsed)
318
- }
319
-}
320
-
321
-// TestCopyWriteError tests write error handling in Copy
322
-func TestCopyWriteError(t *testing.T) {
323
- data := []byte("test data")
324
- src := bytes.NewReader(data)
325
- errWriter := &errWriter{err: errors.New("write error")}
326
- b := NewBucket(1000, 1000)
327
-
328
- n, err := Copy(errWriter, src, b)
329
- if err == nil {
330
- t.Error("expected write error, got nil")
331
- }
332
- if err != errWriter.err {
333
- t.Errorf("got error %v, want %v", err, errWriter.err)
334
- }
335
- if n == 0 {
336
- t.Error("expected some bytes to be written before error")
337
- }
338
-}
339
-
340
-// TestTakeLargeBytes tests Take with very large byte counts
341
-func TestTakeLargeBytes(t *testing.T) {
342
- rate := int64(1024) // 1 KiB/s
343
- burst := rate // 1 second burst
344
- b := NewBucket(rate, burst)
345
- if b == nil {
346
- t.Fatal("NewBucket failed")
347
- }
348
-
349
- // Take a large amount that exceeds burst
350
- start := time.Now()
351
- b.Take(rate * 5) // 5 seconds worth
352
- elapsed := time.Since(start)
353
-
354
- // Should take several seconds (minus burst)
355
- if elapsed < 3*time.Second {
356
- t.Errorf("large Take completed too quickly: %v", elapsed)
357
- }
358
-}
359
-
360
-// TestBufferPool tests that the buffer pool works correctly
361
-func TestBufferPool(t *testing.T) {
362
- data := make([]byte, 64*1024) // Exactly buffer size
363
- src := bytes.NewReader(data)
364
- var dst bytes.Buffer
365
- b := NewBucket(100*1024*1024, 100*1024*1024)
366
-
367
- n, err := Copy(&dst, src, b)
368
- if err != nil {
369
- t.Fatalf("Copy failed: %v", err)
370
- }
371
- if n != int64(len(data)) {
372
- t.Errorf("copied %d bytes, want %d", n, len(data))
373
- }
374
-
375
- // Test with data larger than buffer
376
- largeData := make([]byte, 200*1024) // 200 KiB (larger than 64 KiB buffer)
377
- src2 := bytes.NewReader(largeData)
378
- var dst2 bytes.Buffer
379
-
380
- n2, err := Copy(&dst2, src2, b)
381
- if err != nil {
382
- t.Fatalf("Copy failed: %v", err)
383
- }
384
- if n2 != int64(len(largeData)) {
385
- t.Errorf("copied %d bytes, want %d", n2, len(largeData))
386
- }
387
-}
388
-
389
-// Helper types for testing
390
-
391
-type errReader struct {
392
- err error
393
-}
394
-
395
-func (r *errReader) Read(p []byte) (n int, err error) {
396
- return 0, r.err
397
-}
398
-
399
-type shortWriter struct {
400
- maxWrite int
401
- written int
402
-}
403
-
404
-func (w *shortWriter) Write(p []byte) (n int, err error) {
405
- if w.maxWrite <= 0 {
406
- return 0, io.ErrShortWrite
407
- }
408
- if len(p) > w.maxWrite {
409
- n = w.maxWrite
410
- w.maxWrite = 0
411
- return n, io.ErrShortWrite
412
- }
413
- n = len(p)
414
- w.written += n
415
- return n, nil
416
-}
417
-
418
-type errWriter struct {
419
- err error
420
-}
421
-
422
-func (w *errWriter) Write(p []byte) (n int, err error) {
423
- return len(p), w.err
424
-}
portal/utils/sni/parser.go
new
+265
@@ -0,0 +1,265 @@
1
+// Package sni provides TLS ClientHello parsing to extract SNI (Server Name Indication).
2
+// This is used for routing TLS connections in the TLS passthrough architecture.
3
+package sni
4
+
5
+import (
6
+ "bytes"
7
+ "encoding/binary"
8
+ "errors"
9
+ "fmt"
10
+ "io"
11
+)
12
+
13
+var (
14
+ // ErrInvalidTLSRecord is returned when the TLS record is malformed
15
+ ErrInvalidTLSRecord = errors.New("invalid TLS record")
16
+ // ErrNotClientHello is returned when the record is not a ClientHello
17
+ ErrNotClientHello = errors.New("not a ClientHello message")
18
+ // ErrNoSNI is returned when the ClientHello doesn't contain SNI
19
+ ErrNoSNI = errors.New("no SNI found in ClientHello")
20
+)
21
+
22
+// ExtractSNI extracts the SNI hostname from a TLS ClientHello message.
23
+// It reads from the provided reader and returns the SNI hostname.
24
+// The reader should be positioned at the start of the TLS record.
25
+func ExtractSNI(r io.Reader) (string, error) {
26
+ // Read the TLS record header (5 bytes)
27
+ // ContentType (1) + Version (2) + Length (2)
28
+ header := make([]byte, 5)
29
+ if _, err := io.ReadFull(r, header); err != nil {
30
+ return "", fmt.Errorf("reading TLS header: %w", err)
31
+ }
32
+
33
+ // Check ContentType (0x16 = Handshake)
34
+ if header[0] != 0x16 {
35
+ return "", ErrNotClientHello
36
+ }
37
+
38
+ // Read the handshake message length
39
+ recordLen := binary.BigEndian.Uint16(header[3:5])
40
+ if recordLen < 4 {
41
+ return "", ErrInvalidTLSRecord
42
+ }
43
+
44
+ // Read the full handshake message
45
+ record := make([]byte, recordLen)
46
+ if _, err := io.ReadFull(r, record); err != nil {
47
+ return "", fmt.Errorf("reading TLS record: %w", err)
48
+ }
49
+
50
+ // Parse the handshake message
51
+ return parseHandshake(record)
52
+}
53
+
54
+// parseHandshake parses a TLS handshake message and extracts SNI.
55
+func parseHandshake(data []byte) (string, error) {
56
+ if len(data) < 4 {
57
+ return "", ErrInvalidTLSRecord
58
+ }
59
+
60
+ // HandshakeType (1) + Length (3)
61
+ handshakeType := data[0]
62
+ declaredLen := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
63
+ if declaredLen < 0 || declaredLen > len(data)-4 {
64
+ return "", ErrInvalidTLSRecord
65
+ }
66
+
67
+ // Check if it's a ClientHello (0x01)
68
+ if handshakeType != 0x01 {
69
+ return "", ErrNotClientHello
70
+ }
71
+
72
+ // Skip handshake header (4 bytes)
73
+ return parseClientHello(data[4 : 4+declaredLen])
74
+}
75
+
76
+// parseClientHello parses a ClientHello message and extracts SNI.
77
+func parseClientHello(data []byte) (string, error) {
78
+ // client_version(2) + random(32) + session_id_len(1)
79
+ if len(data) < 35 {
80
+ return "", ErrInvalidTLSRecord
81
+ }
82
+
83
+ offset := 0
84
+
85
+ // Client Version (2 bytes)
86
+ offset += 2
87
+
88
+ // Random (32 bytes)
89
+ offset += 32
90
+
91
+ if offset >= len(data) {
92
+ return "", ErrInvalidTLSRecord
93
+ }
94
+
95
+ // Session ID Length (1 byte) + Session ID
96
+ sessionIDLen := int(data[offset])
97
+ offset += 1 + sessionIDLen
98
+
99
+ if offset > len(data) {
100
+ return "", ErrInvalidTLSRecord
101
+ }
102
+
103
+ // Cipher Suites Length (2 bytes) + Cipher Suites
104
+ if offset+2 > len(data) {
105
+ return "", ErrInvalidTLSRecord
106
+ }
107
+ cipherSuitesLen := int(binary.BigEndian.Uint16(data[offset : offset+2]))
108
+ offset += 2 + cipherSuitesLen
109
+
110
+ if offset > len(data) {
111
+ return "", ErrInvalidTLSRecord
112
+ }
113
+
114
+ // Compression Methods Length (1 byte) + Compression Methods
115
+ if offset+1 > len(data) {
116
+ return "", ErrInvalidTLSRecord
117
+ }
118
+ compressionMethodsLen := int(data[offset])
119
+ offset += 1 + compressionMethodsLen
120
+
121
+ if offset > len(data) {
122
+ return "", ErrInvalidTLSRecord
123
+ }
124
+
125
+ // Extensions Length (2 bytes)
126
+ if offset+2 > len(data) {
127
+ return "", ErrNoSNI
128
+ }
129
+ extensionsLen := int(binary.BigEndian.Uint16(data[offset : offset+2]))
130
+ offset += 2
131
+
132
+ if extensionsLen == 0 || offset+extensionsLen > len(data) {
133
+ return "", ErrNoSNI
134
+ }
135
+
136
+ // Parse extensions
137
+ extensions := data[offset : offset+extensionsLen]
138
+ return parseExtensions(extensions)
139
+}
140
+
141
+// parseExtensions parses TLS extensions and extracts SNI.
142
+func parseExtensions(data []byte) (string, error) {
143
+ offset := 0
144
+
145
+ for offset < len(data) {
146
+ if offset+4 > len(data) {
147
+ return "", ErrInvalidTLSRecord
148
+ }
149
+
150
+ // Extension Type (2 bytes)
151
+ extType := binary.BigEndian.Uint16(data[offset : offset+2])
152
+ offset += 2
153
+
154
+ // Extension Length (2 bytes)
155
+ extLen := int(binary.BigEndian.Uint16(data[offset : offset+2]))
156
+ offset += 2
157
+
158
+ if offset+extLen > len(data) {
159
+ return "", ErrInvalidTLSRecord
160
+ }
161
+
162
+ // Extension Type 0x0000 = server_name (SNI)
163
+ if extType == 0x0000 {
164
+ return parseSNIExtension(data[offset : offset+extLen])
165
+ }
166
+
167
+ offset += extLen
168
+ }
169
+
170
+ return "", ErrNoSNI
171
+}
172
+
173
+// parseSNIExtension parses the SNI extension and returns the hostname.
174
+func parseSNIExtension(data []byte) (string, error) {
175
+ if len(data) < 2 {
176
+ return "", ErrNoSNI
177
+ }
178
+
179
+ // SNI List Length (2 bytes)
180
+ listLen := int(binary.BigEndian.Uint16(data[0:2]))
181
+ if listLen == 0 || 2+listLen > len(data) {
182
+ return "", ErrNoSNI
183
+ }
184
+
185
+ offset := 2
186
+ end := 2 + listLen
187
+
188
+ for offset < end {
189
+ if offset+3 > end {
190
+ return "", ErrInvalidTLSRecord
191
+ }
192
+
193
+ // Name Type (1 byte)
194
+ nameType := data[offset]
195
+ offset++
196
+
197
+ // Name Length (2 bytes)
198
+ nameLen := int(binary.BigEndian.Uint16(data[offset : offset+2]))
199
+ offset += 2
200
+
201
+ if offset+nameLen > end {
202
+ return "", ErrInvalidTLSRecord
203
+ }
204
+
205
+ // Name Type 0x00 = host_name
206
+ if nameType == 0x00 {
207
+ if nameLen == 0 {
208
+ return "", ErrNoSNI
209
+ }
210
+ return string(data[offset : offset+nameLen]), nil
211
+ }
212
+
213
+ offset += nameLen
214
+ }
215
+
216
+ return "", ErrNoSNI
217
+}
218
+
219
+// PeekSNI peeks at the SNI from a connection without consuming the data.
220
+// It returns the SNI and a new reader that includes the peeked data.
221
+// This is useful for routing connections before fully reading them.
222
+func PeekSNI(r io.Reader, bufSize int) (string, io.Reader, error) {
223
+ if bufSize < 5 {
224
+ return "", nil, fmt.Errorf("peek buffer too small: %d", bufSize)
225
+ }
226
+
227
+ // Read TLS record header first so we only read the exact record size.
228
+ header := make([]byte, 5)
229
+ if _, err := io.ReadFull(r, header); err != nil {
230
+ return "", nil, fmt.Errorf("peeking TLS header: %w", err)
231
+ }
232
+ if header[0] != 0x16 {
233
+ reader := io.MultiReader(bytes.NewReader(header), r)
234
+ return "", reader, ErrNotClientHello
235
+ }
236
+
237
+ recordLen := int(binary.BigEndian.Uint16(header[3:5]))
238
+ if recordLen <= 0 {
239
+ reader := io.MultiReader(bytes.NewReader(header), r)
240
+ return "", reader, ErrInvalidTLSRecord
241
+ }
242
+
243
+ totalLen := 5 + recordLen
244
+ if totalLen > bufSize {
245
+ reader := io.MultiReader(bytes.NewReader(header), r)
246
+ return "", reader, fmt.Errorf("TLS record too large for peek buffer: need %d bytes, have %d", totalLen, bufSize)
247
+ }
248
+
249
+ buf := make([]byte, totalLen)
250
+ copy(buf, header)
251
+ if _, err := io.ReadFull(r, buf[5:]); err != nil {
252
+ reader := io.MultiReader(bytes.NewReader(buf[:5]), r)
253
+ return "", reader, fmt.Errorf("peeking TLS record: %w", err)
254
+ }
255
+
256
+ // Create a reader that includes the peeked data.
257
+ reader := io.MultiReader(bytes.NewReader(buf), r)
258
+
259
+ sni, err := ExtractSNI(bytes.NewReader(buf))
260
+ if err != nil {
261
+ return "", reader, err
262
+ }
263
+
264
+ return sni, reader, nil
265
+}
portal/utils/sni/parser_test.go
new
+164
@@ -0,0 +1,164 @@
1
+package sni
2
+
3
+import (
4
+ "bytes"
5
+ "encoding/binary"
6
+ "io"
7
+ "strings"
8
+ "testing"
9
+)
10
+
11
+func TestExtractSNI(t *testing.T) {
12
+ clientHello := buildClientHello("example.com", true)
13
+
14
+ sni, err := ExtractSNI(bytes.NewReader(clientHello))
15
+ if err != nil {
16
+ t.Fatalf("ExtractSNI failed: %v", err)
17
+ }
18
+ if sni != "example.com" {
19
+ t.Errorf("Expected SNI 'example.com', got '%s'", sni)
20
+ }
21
+}
22
+
23
+func TestExtractSNI_NoSNI(t *testing.T) {
24
+ clientHello := buildClientHello("", false)
25
+
26
+ _, err := ExtractSNI(bytes.NewReader(clientHello))
27
+ if err != ErrNoSNI {
28
+ t.Errorf("Expected ErrNoSNI, got: %v", err)
29
+ }
30
+}
31
+
32
+func TestExtractSNI_NotClientHello(t *testing.T) {
33
+ serverHello := buildTLSRecord(0x02, nil)
34
+
35
+ _, err := ExtractSNI(bytes.NewReader(serverHello))
36
+ if err != ErrNotClientHello {
37
+ t.Errorf("Expected ErrNotClientHello, got: %v", err)
38
+ }
39
+}
40
+
41
+func TestPeekSNI(t *testing.T) {
42
+ clientHello := buildClientHello("example.com", true)
43
+
44
+ sni, reader, err := PeekSNI(bytes.NewReader(clientHello), 4096)
45
+ if err != nil {
46
+ t.Fatalf("PeekSNI failed: %v", err)
47
+ }
48
+ if sni != "example.com" {
49
+ t.Errorf("Expected SNI 'example.com', got '%s'", sni)
50
+ }
51
+
52
+ // Verify we can still read the full ClientHello from the returned reader.
53
+ buf, err := io.ReadAll(reader)
54
+ if err != nil {
55
+ t.Fatalf("Reading from returned reader failed: %v", err)
56
+ }
57
+ if !bytes.Equal(buf, clientHello) {
58
+ t.Error("Returned reader doesn't contain the full ClientHello")
59
+ }
60
+}
61
+
62
+func TestExtractSNI_TruncatedClientHello(t *testing.T) {
63
+ record := buildTLSRecord(0x01, make([]byte, 34)) // 1 byte short for session_id_len field access
64
+ _, err := ExtractSNI(bytes.NewReader(record))
65
+ if err != ErrInvalidTLSRecord {
66
+ t.Fatalf("expected ErrInvalidTLSRecord, got: %v", err)
67
+ }
68
+}
69
+
70
+func TestExtractSNI_InvalidHandshakeLength(t *testing.T) {
71
+ record := buildTLSRecord(0x01, []byte{0x03, 0x03, 0x00, 0x00, 0x00})
72
+ // Corrupt handshake declared length to exceed available bytes
73
+ record[6] = 0x00
74
+ record[7] = 0x01
75
+ record[8] = 0x00
76
+
77
+ _, err := ExtractSNI(bytes.NewReader(record))
78
+ if err != ErrInvalidTLSRecord {
79
+ t.Fatalf("expected ErrInvalidTLSRecord, got: %v", err)
80
+ }
81
+}
82
+
83
+func TestPeekSNI_NotClientHelloPreservesData(t *testing.T) {
84
+ payload := []byte("plaintext")
85
+ buf := append([]byte{0x17, 0x03, 0x03, 0x00, byte(len(payload))}, payload...)
86
+
87
+ _, reader, err := PeekSNI(bytes.NewReader(buf), 4096)
88
+ if err != ErrNotClientHello {
89
+ t.Fatalf("expected ErrNotClientHello, got: %v", err)
90
+ }
91
+
92
+ got, readErr := io.ReadAll(reader)
93
+ if readErr != nil {
94
+ t.Fatalf("failed to read returned reader: %v", readErr)
95
+ }
96
+ if !bytes.Equal(got, buf) {
97
+ t.Fatalf("returned reader did not preserve original bytes")
98
+ }
99
+}
100
+
101
+func TestPeekSNI_RecordTooLarge(t *testing.T) {
102
+ clientHello := buildClientHello(strings.Repeat("a", 10)+".example.com", true)
103
+
104
+ _, reader, err := PeekSNI(bytes.NewReader(clientHello), 64)
105
+ if err == nil || !strings.Contains(err.Error(), "too large") {
106
+ t.Fatalf("expected record too large error, got: %v", err)
107
+ }
108
+
109
+ got, readErr := io.ReadAll(reader)
110
+ if readErr != nil {
111
+ t.Fatalf("failed to read returned reader: %v", readErr)
112
+ }
113
+ if !bytes.Equal(got, clientHello) {
114
+ t.Fatalf("returned reader did not preserve original bytes")
115
+ }
116
+}
117
+
118
+func buildClientHello(sni string, includeSNI bool) []byte {
119
+ body := make([]byte, 0, 128)
120
+ body = append(body, 0x03, 0x03) // TLS 1.2
121
+ body = append(body, make([]byte, 32)...)
122
+ body = append(body, 0x00) // Session ID length
123
+ body = append(body, 0x00, 0x02) // Cipher Suites length
124
+ body = append(body, 0x00, 0x2f) // TLS_RSA_WITH_AES_128_CBC_SHA
125
+ body = append(body, 0x01, 0x00) // Compression methods
126
+ extensions := make([]byte, 0, 64) // Extensions
127
+ if includeSNI {
128
+ host := []byte(sni)
129
+ sniData := make([]byte, 2+1+2+len(host)) // list_len + name_type + name_len + host
130
+ binary.BigEndian.PutUint16(sniData[0:2], uint16(1+2+len(host)))
131
+ sniData[2] = 0x00 // host_name
132
+ binary.BigEndian.PutUint16(sniData[3:5], uint16(len(host)))
133
+ copy(sniData[5:], host)
134
+
135
+ ext := make([]byte, 4+len(sniData)) // ext_type + ext_len + ext_data
136
+ binary.BigEndian.PutUint16(ext[0:2], 0x0000)
137
+ binary.BigEndian.PutUint16(ext[2:4], uint16(len(sniData)))
138
+ copy(ext[4:], sniData)
139
+ extensions = append(extensions, ext...)
140
+ }
141
+ body = append(body, byte(len(extensions)>>8), byte(len(extensions)))
142
+ body = append(body, extensions...)
143
+
144
+ return buildTLSRecord(0x01, body)
145
+}
146
+
147
+func buildTLSRecord(handshakeType byte, handshakeBody []byte) []byte {
148
+ handshake := make([]byte, 4+len(handshakeBody))
149
+ handshake[0] = handshakeType
150
+ handshakeLen := len(handshakeBody)
151
+ handshake[1] = byte(handshakeLen >> 16)
152
+ handshake[2] = byte(handshakeLen >> 8)
153
+ handshake[3] = byte(handshakeLen)
154
+ copy(handshake[4:], handshakeBody)
155
+
156
+ record := make([]byte, 5+len(handshake))
157
+ record[0] = 0x16 // Handshake
158
+ record[1] = 0x03 // TLS 1.x
159
+ record[2] = 0x01 // TLS 1.0 record version
160
+ recordLen := len(handshake)
161
+ binary.BigEndian.PutUint16(record[3:5], uint16(recordLen))
162
+ copy(record[5:], handshake)
163
+ return record
164
+}
portal/utils/sni/router.go
new
+415
@@ -0,0 +1,415 @@
1
+// Package sni provides TLS SNI-based TCP routing for the Portal relay.
2
+package sni
3
+
4
+import (
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+ "net"
9
+ "strings"
10
+ "sync"
11
+ "time"
12
+
13
+ "github.com/rs/zerolog/log"
14
+)
15
+
16
+var (
17
+ // ErrNoRoute is returned when no route is found for the SNI
18
+ ErrNoRoute = errors.New("no route found for SNI")
19
+ // ErrRouterClosed is returned when the router is closed
20
+ ErrRouterClosed = errors.New("router is closed")
21
+)
22
+
23
+const (
24
+ // maxTLSRecordSize is TLS plaintext limit (16KB) plus allowance for overhead.
25
+ // Using this avoids dropping valid large ClientHello messages.
26
+ maxTLSRecordSize = 16*1024 + 2048
27
+)
28
+
29
+// Route represents a registered route
30
+type Route struct {
31
+ SNI string
32
+ TargetAddr string
33
+ LeaseID string
34
+ LeaseName string
35
+}
36
+
37
+// Router handles SNI-based TCP routing
38
+type Router struct {
39
+ mu sync.RWMutex
40
+ routes map[string]*Route // SNI -> Route
41
+ leases map[string]*Route // LeaseID -> Route
42
+ listener net.Listener
43
+
44
+ // Callback for new connections
45
+ onConnection func(conn net.Conn, route *Route)
46
+
47
+ stopCh chan struct{}
48
+ stopOnce sync.Once
49
+ wg sync.WaitGroup
50
+}
51
+
52
+// NewRouter creates a new SNI router
53
+func NewRouter() *Router {
54
+ return &Router{
55
+ routes: make(map[string]*Route),
56
+ leases: make(map[string]*Route),
57
+ stopCh: make(chan struct{}),
58
+ }
59
+}
60
+
61
+// SetConnectionCallback sets the callback for new connections
62
+func (r *Router) SetConnectionCallback(cb func(conn net.Conn, route *Route)) {
63
+ r.mu.Lock()
64
+ defer r.mu.Unlock()
65
+ r.onConnection = cb
66
+}
67
+
68
+// RegisterRoute registers a new route for an SNI
69
+func (r *Router) RegisterRoute(sni, targetAddr, leaseID, leaseName string) error {
70
+ r.mu.Lock()
71
+ defer r.mu.Unlock()
72
+
73
+ select {
74
+ case <-r.stopCh:
75
+ return ErrRouterClosed
76
+ default:
77
+ }
78
+
79
+ sni = strings.ToLower(strings.TrimSpace(sni))
80
+ if sni == "" {
81
+ return fmt.Errorf("sni is required")
82
+ }
83
+
84
+ route := &Route{
85
+ SNI: sni,
86
+ TargetAddr: targetAddr,
87
+ LeaseID: leaseID,
88
+ LeaseName: leaseName,
89
+ }
90
+
91
+ // Remove previous SNI entry when a lease is re-registered with a new name.
92
+ if oldRoute, ok := r.leases[leaseID]; ok && oldRoute.SNI != sni {
93
+ delete(r.routes, oldRoute.SNI)
94
+ }
95
+ // Keep lease index consistent when SNI is reassigned to another lease.
96
+ if oldRoute, ok := r.routes[sni]; ok && oldRoute.LeaseID != leaseID {
97
+ delete(r.leases, oldRoute.LeaseID)
98
+ }
99
+
100
+ r.routes[sni] = route
101
+ r.leases[leaseID] = route
102
+
103
+ log.Info().
104
+ Str("sni", sni).
105
+ Str("target", targetAddr).
106
+ Str("lease_id", leaseID).
107
+ Msg("[SNI] Route registered")
108
+
109
+ return nil
110
+}
111
+
112
+// UnregisterRoute removes a route for an SNI
113
+func (r *Router) UnregisterRoute(sni string) {
114
+ r.mu.Lock()
115
+ defer r.mu.Unlock()
116
+
117
+ sni = strings.ToLower(strings.TrimSpace(sni))
118
+
119
+ if route, ok := r.routes[sni]; ok {
120
+ delete(r.routes, sni)
121
+ delete(r.leases, route.LeaseID)
122
+ log.Info().
123
+ Str("sni", sni).
124
+ Str("lease_id", route.LeaseID).
125
+ Msg("[SNI] Route unregistered")
126
+ }
127
+}
128
+
129
+// UnregisterRouteByLeaseID removes a route by lease ID
130
+func (r *Router) UnregisterRouteByLeaseID(leaseID string) {
131
+ r.mu.Lock()
132
+ defer r.mu.Unlock()
133
+
134
+ if route, ok := r.leases[leaseID]; ok {
135
+ delete(r.routes, route.SNI)
136
+ delete(r.leases, leaseID)
137
+ log.Info().
138
+ Str("sni", route.SNI).
139
+ Str("lease_id", leaseID).
140
+ Msg("[SNI] Route unregistered")
141
+ }
142
+}
143
+
144
+// GetRoute returns the route for an SNI
145
+func (r *Router) GetRoute(sni string) (*Route, bool) {
146
+ r.mu.RLock()
147
+ defer r.mu.RUnlock()
148
+
149
+ sni = strings.ToLower(strings.TrimSpace(sni))
150
+
151
+ // Try exact match first
152
+ if route, ok := r.routes[sni]; ok {
153
+ return route, true
154
+ }
155
+
156
+ // Try wildcard match (e.g., *.example.com)
157
+ parts := strings.Split(sni, ".")
158
+ for i := 1; i < len(parts); i++ {
159
+ wildcard := "*." + strings.Join(parts[i:], ".")
160
+ if route, ok := r.routes[wildcard]; ok {
161
+ return route, true
162
+ }
163
+ }
164
+
165
+ return nil, false
166
+}
167
+
168
+// GetRouteByLeaseID returns the route for a lease ID
169
+func (r *Router) GetRouteByLeaseID(leaseID string) (*Route, bool) {
170
+ r.mu.RLock()
171
+ defer r.mu.RUnlock()
172
+
173
+ route, ok := r.leases[leaseID]
174
+ return route, ok
175
+}
176
+
177
+// GetAllRoutes returns all registered routes
178
+func (r *Router) GetAllRoutes() []*Route {
179
+ r.mu.RLock()
180
+ defer r.mu.RUnlock()
181
+
182
+ routes := make([]*Route, 0, len(r.routes))
183
+ for _, route := range r.routes {
184
+ routes = append(routes, route)
185
+ }
186
+ return routes
187
+}
188
+
189
+// Start starts the SNI router on the given address
190
+func (r *Router) Start(addr string) error {
191
+ listener, err := net.Listen("tcp", addr)
192
+ if err != nil {
193
+ return fmt.Errorf("failed to listen on %s: %w", addr, err)
194
+ }
195
+
196
+ r.mu.Lock()
197
+ r.listener = listener
198
+ r.mu.Unlock()
199
+
200
+ log.Info().
201
+ Str("addr", addr).
202
+ Msg("[SNI] Router started")
203
+
204
+ r.wg.Add(1)
205
+ go r.acceptLoop(listener)
206
+
207
+ return nil
208
+}
209
+
210
+// Stop stops the SNI router
211
+func (r *Router) Stop() error {
212
+ r.stopOnce.Do(func() {
213
+ close(r.stopCh)
214
+
215
+ r.mu.Lock()
216
+ if r.listener != nil {
217
+ r.listener.Close()
218
+ }
219
+ r.mu.Unlock()
220
+ })
221
+
222
+ r.wg.Wait()
223
+ log.Info().Msg("[SNI] Router stopped")
224
+ return nil
225
+}
226
+
227
+// Addr returns the router's listen address
228
+func (r *Router) Addr() net.Addr {
229
+ r.mu.RLock()
230
+ defer r.mu.RUnlock()
231
+
232
+ if r.listener != nil {
233
+ return r.listener.Addr()
234
+ }
235
+ return nil
236
+}
237
+
238
+// acceptLoop accepts incoming connections
239
+func (r *Router) acceptLoop(listener net.Listener) {
240
+ defer r.wg.Done()
241
+
242
+ for {
243
+ conn, err := listener.Accept()
244
+ if err != nil {
245
+ select {
246
+ case <-r.stopCh:
247
+ return
248
+ default:
249
+ log.Error().Err(err).Msg("[SNI] Accept error")
250
+ continue
251
+ }
252
+ }
253
+
254
+ r.wg.Add(1)
255
+ go r.handleConnection(conn)
256
+ }
257
+}
258
+
259
+// handleConnection handles a single connection
260
+func (r *Router) handleConnection(clientConn net.Conn) {
261
+ defer r.wg.Done()
262
+
263
+ // Set a deadline for reading the ClientHello
264
+ clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
265
+
266
+ // Peek at the SNI from the ClientHello
267
+ sni, peekedReader, err := PeekSNI(clientConn, maxTLSRecordSize)
268
+ if err != nil {
269
+ log.Error().
270
+ Err(err).
271
+ Str("remote", clientConn.RemoteAddr().String()).
272
+ Msg("[SNI] Failed to extract SNI")
273
+ clientConn.Close()
274
+ return
275
+ }
276
+
277
+ // Clear the deadline
278
+ clientConn.SetReadDeadline(time.Time{})
279
+
280
+ // Find the route
281
+ route, ok := r.GetRoute(sni)
282
+ if !ok {
283
+ log.Warn().
284
+ Str("sni", sni).
285
+ Str("remote", clientConn.RemoteAddr().String()).
286
+ Msg("[SNI] No route found")
287
+ clientConn.Close()
288
+ return
289
+ }
290
+
291
+ log.Debug().
292
+ Str("sni", sni).
293
+ Str("target", route.TargetAddr).
294
+ Str("remote", clientConn.RemoteAddr().String()).
295
+ Msg("[SNI] Route found")
296
+
297
+ // Wrap the connection so the callback can still read the peeked bytes.
298
+ wrappedConn := &peekedConn{
299
+ Conn: clientConn,
300
+ reader: peekedReader,
301
+ }
302
+
303
+ // Call the connection callback if set
304
+ r.mu.RLock()
305
+ onConnection := r.onConnection
306
+ r.mu.RUnlock()
307
+
308
+ if onConnection != nil {
309
+ onConnection(wrappedConn, route)
310
+ return
311
+ }
312
+
313
+ // Default behavior: proxy to target
314
+ r.proxyConnection(clientConn, peekedReader, route)
315
+}
316
+
317
+// proxyConnection proxies data between client and target
318
+func (r *Router) proxyConnection(clientConn net.Conn, clientReader io.Reader, route *Route) {
319
+ defer clientConn.Close()
320
+
321
+ // Connect to target
322
+ targetConn, err := net.DialTimeout("tcp", route.TargetAddr, 10*time.Second)
323
+ if err != nil {
324
+ log.Error().
325
+ Err(err).
326
+ Str("target", route.TargetAddr).
327
+ Str("sni", route.SNI).
328
+ Msg("[SNI] Failed to connect to target")
329
+ return
330
+ }
331
+ defer targetConn.Close()
332
+
333
+ log.Info().
334
+ Str("sni", route.SNI).
335
+ Str("target", route.TargetAddr).
336
+ Str("client", clientConn.RemoteAddr().String()).
337
+ Msg("[SNI] Connection established")
338
+
339
+ // Create error channels
340
+ errCh := make(chan error, 2)
341
+
342
+ // Client -> Target
343
+ go func() {
344
+ _, err := io.Copy(targetConn, clientReader)
345
+ errCh <- err
346
+ targetConn.Close()
347
+ }()
348
+
349
+ // Target -> Client
350
+ go func() {
351
+ _, err := io.Copy(clientConn, targetConn)
352
+ errCh <- err
353
+ clientConn.Close()
354
+ }()
355
+
356
+ // Wait for either direction to close
357
+ <-errCh
358
+
359
+ log.Debug().
360
+ Str("sni", route.SNI).
361
+ Str("target", route.TargetAddr).
362
+ Msg("[SNI] Connection closed")
363
+}
364
+
365
+// BridgeConnections bridges two connections
366
+func BridgeConnections(conn1, conn2 net.Conn) {
367
+ defer conn1.Close()
368
+ defer conn2.Close()
369
+
370
+ errCh := make(chan error, 2)
371
+
372
+ // Conn1 -> Conn2
373
+ go func() {
374
+ _, err := io.Copy(conn2, conn1)
375
+ errCh <- err
376
+ conn2.Close()
377
+ }()
378
+
379
+ // Conn2 -> Conn1
380
+ go func() {
381
+ _, err := io.Copy(conn1, conn2)
382
+ errCh <- err
383
+ conn1.Close()
384
+ }()
385
+
386
+ // Wait for either direction to close
387
+ <-errCh
388
+}
389
+
390
+// ExtractSNIFromConnection extracts SNI from a connection without consuming data.
391
+// It returns the SNI and a wrapped connection that includes the peeked data.
392
+func ExtractSNIFromConnection(conn net.Conn, bufSize int) (string, net.Conn, error) {
393
+ sni, reader, err := PeekSNI(conn, bufSize)
394
+ if err != nil {
395
+ return "", nil, err
396
+ }
397
+
398
+ // Wrap the connection to include the peeked data.
399
+ wrappedConn := &peekedConn{
400
+ Conn: conn,
401
+ reader: reader,
402
+ }
403
+
404
+ return sni, wrappedConn, nil
405
+}
406
+
407
+// peekedConn wraps a net.Conn to include peeked data
408
+type peekedConn struct {
409
+ net.Conn
410
+ reader io.Reader
411
+}
412
+
413
+func (c *peekedConn) Read(p []byte) (int, error) {
414
+ return c.reader.Read(p)
415
+}
portal/utils/wsstream/wsstream.go
deleted
-88
@@ -1,88 +0,0 @@
1
-package wsstream
2
-
3
-import (
4
- "io"
5
- "strings"
6
- "sync"
7
-
8
- "github.com/gorilla/websocket"
9
-)
10
-
11
-// webSocketConn defines the interface for WebSocket connections.
12
-// This allows for mocking in tests while using the real websocket.Conn in production.
13
-type webSocketConn interface {
14
- NextReader() (int, io.Reader, error)
15
- WriteMessage(int, []byte) error
16
- Close() error
17
-}
18
-
19
-// WsStream wraps a WebSocket connection to implement io.Reader and io.Writer.
20
-type WsStream struct {
21
- Conn webSocketConn
22
- currentReader io.Reader
23
- writeMu sync.Mutex
24
- readMu sync.Mutex
25
-}
26
-
27
-// New creates a new WsStream from a gorilla/websocket connection.
28
-func New(conn *websocket.Conn) *WsStream {
29
- return &WsStream{
30
- Conn: conn,
31
- }
32
-}
33
-
34
-func (g *WsStream) Read(p []byte) (n int, err error) {
35
- g.readMu.Lock()
36
- defer g.readMu.Unlock()
37
-
38
- // Handle empty buffer - standard io.Reader behavior
39
- if len(p) == 0 {
40
- return 0, nil
41
- }
42
-
43
- for {
44
- // Get a reader if we don't have one
45
- if g.currentReader == nil {
46
- _, reader, err := g.Conn.NextReader()
47
- if err != nil {
48
- // Convert websocket close errors to io.EOF
49
- if err != nil && strings.HasPrefix(err.Error(), "websocket: close ") {
50
- return 0, io.EOF
51
- }
52
- return 0, err
53
- }
54
- g.currentReader = reader
55
- }
56
-
57
- n, err = g.currentReader.Read(p)
58
- if err == io.EOF {
59
- // Current message exhausted, try to get next one
60
- g.currentReader = nil
61
- continue
62
- }
63
-
64
- if err != nil && strings.HasPrefix(err.Error(), "websocket: close ") {
65
- return 0, io.EOF
66
- }
67
-
68
- return n, err
69
- }
70
-}
71
-
72
-func (g *WsStream) Write(p []byte) (n int, err error) {
73
- g.writeMu.Lock()
74
- defer g.writeMu.Unlock()
75
- err = g.Conn.WriteMessage(websocket.BinaryMessage, p)
76
- if err != nil {
77
- if strings.HasPrefix(err.Error(), "websocket: close ") {
78
- return 0, io.EOF
79
- }
80
- return 0, err
81
- }
82
-
83
- return len(p), nil
84
-}
85
-
86
-func (g *WsStream) Close() error {
87
- return g.Conn.Close()
88
-}
portal/utils/wsstream/wsstream_test.go
deleted
-450
@@ -1,450 +0,0 @@
1
-package wsstream
2
-
3
-import (
4
- "bytes"
5
- "errors"
6
- "io"
7
- "sync"
8
- "testing"
9
-
10
- "github.com/gorilla/websocket"
11
-)
12
-
13
-// mockWebSocketConn is a mock implementation of websocket.Conn for testing
14
-type mockWebSocketConn struct {
15
- mu sync.Mutex
16
- readData [][]byte
17
- readIndex int
18
- writeData [][]byte
19
- closeCalled bool
20
- nextReaderErr error
21
- writeMessageErr error
22
- closeErr error
23
-}
24
-
25
-func newMockConn(data []byte) *mockWebSocketConn {
26
- return &mockWebSocketConn{
27
- readData: [][]byte{data},
28
- }
29
-}
30
-
31
-func (m *mockWebSocketConn) NextReader() (messageType int, r io.Reader, err error) {
32
- m.mu.Lock()
33
- defer m.mu.Unlock()
34
-
35
- if m.nextReaderErr != nil {
36
- return 0, nil, m.nextReaderErr
37
- }
38
-
39
- if m.readIndex >= len(m.readData) {
40
- return 0, nil, io.EOF
41
- }
42
-
43
- data := m.readData[m.readIndex]
44
- m.readIndex++
45
- return websocket.BinaryMessage, bytes.NewReader(data), nil
46
-}
47
-
48
-func (m *mockWebSocketConn) WriteMessage(messageType int, data []byte) error {
49
- m.mu.Lock()
50
- defer m.mu.Unlock()
51
-
52
- if m.writeMessageErr != nil {
53
- return m.writeMessageErr
54
- }
55
-
56
- // Copy data since caller may reuse the buffer
57
- dataCopy := make([]byte, len(data))
58
- copy(dataCopy, data)
59
- m.writeData = append(m.writeData, dataCopy)
60
- return nil
61
-}
62
-
63
-func (m *mockWebSocketConn) Close() error {
64
- m.mu.Lock()
65
- defer m.mu.Unlock()
66
- m.closeCalled = true
67
- return m.closeErr
68
-}
69
-
70
-// TestWsStream_Read tests the Read method
71
-func TestWsStream_Read(t *testing.T) {
72
- t.Run("single message", func(t *testing.T) {
73
- data := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
74
- mock := newMockConn(data)
75
- stream := &WsStream{Conn: mock}
76
-
77
- buf := make([]byte, 10)
78
- n, err := stream.Read(buf)
79
-
80
- if err != nil {
81
- t.Fatalf("Read() error = %v", err)
82
- }
83
- if n != 5 {
84
- t.Errorf("Read() n = %v, want 5", n)
85
- }
86
- if !bytes.Equal(buf[:5], data) {
87
- t.Errorf("Read() data = %v, want %v", buf[:5], data)
88
- }
89
- })
90
-
91
- t.Run("multiple reads from same message", func(t *testing.T) {
92
- data := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
93
- mock := newMockConn(data)
94
- stream := &WsStream{Conn: mock}
95
-
96
- buf1 := make([]byte, 3)
97
- n1, err := stream.Read(buf1)
98
- if err != nil {
99
- t.Fatalf("First Read() error = %v", err)
100
- }
101
- if n1 != 3 {
102
- t.Errorf("First Read() n = %v, want 3", n1)
103
- }
104
-
105
- buf2 := make([]byte, 10)
106
- n2, err := stream.Read(buf2)
107
- if err != nil {
108
- t.Fatalf("Second Read() error = %v", err)
109
- }
110
- if n2 != 5 {
111
- t.Errorf("Second Read() n = %v, want 5", n2)
112
- }
113
- if !bytes.Equal(buf2[:5], []byte{0x04, 0x05, 0x06, 0x07, 0x08}) {
114
- t.Errorf("Second Read() data = %v, want [4 5 6 7 8]", buf2[:5])
115
- }
116
- })
117
-
118
- t.Run("multiple messages", func(t *testing.T) {
119
- mock := &mockWebSocketConn{
120
- readData: [][]byte{
121
- {0x01, 0x02},
122
- {0x03, 0x04},
123
- },
124
- }
125
- stream := &WsStream{Conn: mock}
126
-
127
- buf := make([]byte, 10)
128
- n1, err := stream.Read(buf)
129
- if err != nil {
130
- t.Fatalf("First Read() error = %v", err)
131
- }
132
- if n1 != 2 {
133
- t.Errorf("First Read() n = %v, want 2", n1)
134
- }
135
-
136
- n2, err := stream.Read(buf)
137
- if err != nil {
138
- t.Fatalf("Second Read() error = %v", err)
139
- }
140
- if n2 != 2 {
141
- t.Errorf("Second Read() n = %v, want 2", n2)
142
- }
143
- })
144
-
145
- t.Run("empty buffer", func(t *testing.T) {
146
- mock := newMockConn([]byte{0x01})
147
- stream := &WsStream{Conn: mock}
148
-
149
- buf := make([]byte, 0)
150
- n, err := stream.Read(buf)
151
- if err != nil {
152
- t.Fatalf("Read() error = %v", err)
153
- }
154
- if n != 0 {
155
- t.Errorf("Read() n = %v, want 0", n)
156
- }
157
- })
158
-
159
- t.Run("EOF after message", func(t *testing.T) {
160
- mock := newMockConn([]byte{0x01, 0x02})
161
- stream := &WsStream{Conn: mock}
162
-
163
- buf := make([]byte, 10)
164
- _, err := stream.Read(buf)
165
- if err != nil {
166
- t.Fatalf("First Read() error = %v", err)
167
- }
168
-
169
- _, err = stream.Read(buf)
170
- if err != io.EOF {
171
- t.Errorf("Second Read() error = %v, want io.EOF", err)
172
- }
173
- })
174
-
175
- t.Run("NextReader error", func(t *testing.T) {
176
- mock := &mockWebSocketConn{
177
- nextReaderErr: errors.New("connection reset"),
178
- }
179
- stream := &WsStream{Conn: mock}
180
-
181
- buf := make([]byte, 10)
182
- _, err := stream.Read(buf)
183
- if err == nil {
184
- t.Error("Read() error = nil, want error")
185
- }
186
- })
187
-
188
- t.Run("websocket close error", func(t *testing.T) {
189
- mock := &mockWebSocketConn{
190
- nextReaderErr: &websocket.CloseError{
191
- Code: websocket.CloseNormalClosure,
192
- Text: "normal closure",
193
- },
194
- }
195
- stream := &WsStream{Conn: mock}
196
-
197
- buf := make([]byte, 10)
198
- _, err := stream.Read(buf)
199
- if err != io.EOF {
200
- t.Errorf("Read() error = %v, want io.EOF", err)
201
- }
202
- })
203
-
204
- t.Run("concurrent reads", func(t *testing.T) {
205
- data := []byte{0x01, 0x02, 0x03, 0x04}
206
- mock := &mockWebSocketConn{
207
- readData: [][]byte{data, data},
208
- }
209
- stream := &WsStream{Conn: mock}
210
-
211
- var wg sync.WaitGroup
212
- errors := make(chan error, 2)
213
-
214
- for range 2 {
215
- wg.Add(1)
216
- go func() {
217
- defer wg.Done()
218
- buf := make([]byte, 4)
219
- _, err := stream.Read(buf)
220
- errors <- err
221
- }()
222
- }
223
-
224
- wg.Wait()
225
- close(errors)
226
-
227
- for err := range errors {
228
- if err != nil && err != io.EOF {
229
- t.Errorf("Concurrent Read() error = %v", err)
230
- }
231
- }
232
- })
233
-}
234
-
235
-// TestWsStream_Write tests the Write method
236
-func TestWsStream_Write(t *testing.T) {
237
- t.Run("successful write", func(t *testing.T) {
238
- mock := newMockConn(nil)
239
- stream := &WsStream{Conn: mock}
240
-
241
- data := []byte{0x01, 0x02, 0x03}
242
- n, err := stream.Write(data)
243
-
244
- if err != nil {
245
- t.Fatalf("Write() error = %v", err)
246
- }
247
- if n != 3 {
248
- t.Errorf("Write() n = %v, want 3", n)
249
- }
250
- if len(mock.writeData) != 1 {
251
- t.Errorf("Write() messages written = %v, want 1", len(mock.writeData))
252
- }
253
- if !bytes.Equal(mock.writeData[0], data) {
254
- t.Errorf("Write() data = %v, want %v", mock.writeData[0], data)
255
- }
256
- })
257
-
258
- t.Run("empty write", func(t *testing.T) {
259
- mock := newMockConn(nil)
260
- stream := &WsStream{Conn: mock}
261
-
262
- data := []byte{}
263
- n, err := stream.Write(data)
264
-
265
- if err != nil {
266
- t.Fatalf("Write() error = %v", err)
267
- }
268
- if n != 0 {
269
- t.Errorf("Write() n = %v, want 0", n)
270
- }
271
- })
272
-
273
- t.Run("multiple writes", func(t *testing.T) {
274
- mock := newMockConn(nil)
275
- stream := &WsStream{Conn: mock}
276
-
277
- stream.Write([]byte{0x01})
278
- stream.Write([]byte{0x02})
279
- stream.Write([]byte{0x03})
280
-
281
- if len(mock.writeData) != 3 {
282
- t.Errorf("Write() messages written = %v, want 3", len(mock.writeData))
283
- }
284
- })
285
-
286
- t.Run("write error", func(t *testing.T) {
287
- mock := &mockWebSocketConn{
288
- writeMessageErr: errors.New("write failed"),
289
- }
290
- stream := &WsStream{Conn: mock}
291
-
292
- data := []byte{0x01, 0x02}
293
- _, err := stream.Write(data)
294
-
295
- if err == nil {
296
- t.Error("Write() error = nil, want error")
297
- }
298
- })
299
-
300
- t.Run("websocket close error returns EOF", func(t *testing.T) {
301
- mock := &mockWebSocketConn{
302
- writeMessageErr: &websocket.CloseError{
303
- Code: websocket.CloseGoingAway,
304
- Text: "going away",
305
- },
306
- }
307
- stream := &WsStream{Conn: mock}
308
-
309
- data := []byte{0x01, 0x02}
310
- _, err := stream.Write(data)
311
-
312
- if err != io.EOF {
313
- t.Errorf("Write() error = %v, want io.EOF", err)
314
- }
315
- })
316
-
317
- t.Run("concurrent writes", func(t *testing.T) {
318
- mock := newMockConn(nil)
319
- stream := &WsStream{Conn: mock}
320
-
321
- var wg sync.WaitGroup
322
- for i := range 10 {
323
- wg.Add(1)
324
- go func(b byte) {
325
- defer wg.Done()
326
- stream.Write([]byte{b})
327
- }(byte(i))
328
- }
329
-
330
- wg.Wait()
331
-
332
- if len(mock.writeData) != 10 {
333
- t.Errorf("Concurrent Write() messages = %v, want 10", len(mock.writeData))
334
- }
335
- })
336
-}
337
-
338
-// TestWsStream_Close tests the Close method
339
-func TestWsStream_Close(t *testing.T) {
340
- t.Run("successful close", func(t *testing.T) {
341
- mock := newMockConn(nil)
342
- stream := &WsStream{Conn: mock}
343
-
344
- err := stream.Close()
345
-
346
- if err != nil {
347
- t.Fatalf("Close() error = %v", err)
348
- }
349
- if !mock.closeCalled {
350
- t.Error("Close() did not call underlying Conn.Close()")
351
- }
352
- })
353
-
354
- t.Run("close with error", func(t *testing.T) {
355
- mock := &mockWebSocketConn{
356
- closeErr: errors.New("close failed"),
357
- }
358
- stream := &WsStream{Conn: mock}
359
-
360
- err := stream.Close()
361
-
362
- if err == nil {
363
- t.Error("Close() error = nil, want error")
364
- }
365
- })
366
-
367
- t.Run("multiple closes", func(t *testing.T) {
368
- mock := newMockConn(nil)
369
- stream := &WsStream{Conn: mock}
370
-
371
- stream.Close()
372
- err := stream.Close()
373
-
374
- // Second close should not panic, just return whatever Conn.Close returns
375
- if !mock.closeCalled {
376
- t.Error("Close() did not call underlying Conn.Close()")
377
- }
378
- _ = err // We don't care about the error on second close
379
- })
380
-}
381
-
382
-// TestWsStream_ReadWrite tests full read-write cycle
383
-func TestWsStream_ReadWrite(t *testing.T) {
384
- t.Run("full cycle", func(t *testing.T) {
385
- mock := newMockConn(nil)
386
- stream := &WsStream{Conn: mock}
387
-
388
- // Write some data
389
- writeData := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
390
- _, err := stream.Write(writeData)
391
- if err != nil {
392
- t.Fatalf("Write() error = %v", err)
393
- }
394
-
395
- // Verify write
396
- if len(mock.writeData) != 1 {
397
- t.Fatalf("Write() messages = %v, want 1", len(mock.writeData))
398
- }
399
-
400
- // Now test reading
401
- readMock := &mockWebSocketConn{
402
- readData: [][]byte{writeData},
403
- }
404
- readStream := &WsStream{Conn: readMock}
405
-
406
- buf := make([]byte, 10)
407
- n, err := readStream.Read(buf)
408
- if err != nil {
409
- t.Fatalf("Read() error = %v", err)
410
- }
411
-
412
- if n != 5 {
413
- t.Errorf("Read() n = %v, want 5", n)
414
- }
415
- if !bytes.Equal(buf[:5], writeData) {
416
- t.Errorf("Read() data = %v, want %v", buf[:5], writeData)
417
- }
418
- })
419
-}
420
-
421
-// BenchmarkWsStream_Read benchmarks the Read method
422
-func BenchmarkWsStream_Read(b *testing.B) {
423
- data := make([]byte, 1024)
424
- mock := newMockConn(data)
425
- stream := &WsStream{Conn: mock}
426
-
427
- buf := make([]byte, 1024)
428
-
429
- b.ResetTimer()
430
- for i := range b.N {
431
- stream.Read(buf)
432
- // Reset for next iteration
433
- if i%1000 == 999 {
434
- mock.readIndex = 0
435
- }
436
- }
437
-}
438
-
439
-// BenchmarkWsStream_Write benchmarks the Write method
440
-func BenchmarkWsStream_Write(b *testing.B) {
441
- mock := newMockConn(nil)
442
- stream := &WsStream{Conn: mock}
443
-
444
- data := make([]byte, 1024)
445
-
446
- b.ResetTimer()
447
- for range b.N {
448
- stream.Write(data)
449
- }
450
-}
sdk/listener.go
new
+518
@@ -0,0 +1,518 @@
1
+package sdk
2
+
3
+import (
4
+ "bytes"
5
+ "context"
6
+ "crypto/tls"
7
+ "encoding/json"
8
+ "errors"
9
+ "fmt"
10
+ "io"
11
+ "net"
12
+ "net/http"
13
+ "net/url"
14
+ "strings"
15
+ "sync"
16
+ "time"
17
+
18
+ "github.com/rs/zerolog/log"
19
+ "golang.org/x/crypto/acme/autocert"
20
+ "golang.org/x/net/websocket"
21
+ "gosuda.org/portal/portal"
22
+)
23
+
24
+const (
25
+ relayKeepaliveInterval = 10 * time.Second
26
+ reverseReadTimeout = 1 * time.Second
27
+ reverseStartMarker = byte(0x01)
28
+ defaultReverseWorkers = 2
29
+ maxReverseWorkers = 16
30
+ defaultReverseDialTimeout = 5 * time.Second
31
+)
32
+
33
+// Listener is a net.Listener backed by relay tunnel registration.
34
+// The relay connects to this listener after SNI routing resolves the lease.
35
+type Listener struct {
36
+ relayAddr string
37
+ lease *portal.Lease
38
+
39
+ httpClient *http.Client
40
+
41
+ mu sync.RWMutex
42
+ listener net.Listener
43
+ closed bool
44
+ acceptCh chan net.Conn
45
+ reverseWorkers int
46
+ reverseDialTimeout time.Duration
47
+
48
+ // TLS configuration
49
+ tlsConfig *tls.Config
50
+ autocertMgr *autocert.Manager
51
+
52
+ stopCh chan struct{}
53
+ closeOnce sync.Once
54
+ wg sync.WaitGroup
55
+}
56
+
57
+var _ net.Listener = (*Listener)(nil)
58
+
59
+// NewListener creates a relay-backed listener.
60
+// If tlsConfig is provided, the listener will perform TLS handshake on incoming connections.
61
+func NewListener(relayAddr string, lease *portal.Lease, tlsConfig *tls.Config, autocertMgr *autocert.Manager, reverseWorkers int, reverseDialTimeout time.Duration) (*Listener, error) {
62
+ if lease == nil {
63
+ return nil, fmt.Errorf("lease is required")
64
+ }
65
+ if lease.ID == "" {
66
+ return nil, fmt.Errorf("lease ID is required")
67
+ }
68
+ if lease.Name == "" {
69
+ return nil, fmt.Errorf("lease name is required")
70
+ }
71
+ if strings.TrimSpace(lease.ReverseToken) == "" {
72
+ return nil, fmt.Errorf("lease reverse token is required")
73
+ }
74
+
75
+ apiURL, err := normalizeRelayAPIURL(relayAddr)
76
+ if err != nil {
77
+ return nil, err
78
+ }
79
+
80
+ if reverseWorkers <= 0 {
81
+ reverseWorkers = defaultReverseWorkers
82
+ }
83
+ if reverseWorkers > maxReverseWorkers {
84
+ reverseWorkers = maxReverseWorkers
85
+ }
86
+ if reverseDialTimeout <= 0 {
87
+ reverseDialTimeout = defaultReverseDialTimeout
88
+ }
89
+
90
+ return &Listener{
91
+ relayAddr: apiURL,
92
+ lease: lease,
93
+ httpClient: &http.Client{
94
+ Timeout: 10 * time.Second,
95
+ },
96
+ tlsConfig: tlsConfig,
97
+ autocertMgr: autocertMgr,
98
+ stopCh: make(chan struct{}),
99
+ acceptCh: make(chan net.Conn, 128),
100
+ reverseWorkers: reverseWorkers,
101
+ reverseDialTimeout: reverseDialTimeout,
102
+ }, nil
103
+}
104
+
105
+// Start initializes the local listener, then registers it to relay.
106
+func (l *Listener) Start() error {
107
+ l.mu.Lock()
108
+ if l.closed {
109
+ l.mu.Unlock()
110
+ return net.ErrClosed
111
+ }
112
+ if l.listener != nil {
113
+ l.mu.Unlock()
114
+ return nil
115
+ }
116
+ l.mu.Unlock()
117
+
118
+ rawListener, err := net.Listen("tcp", ":0")
119
+ if err != nil {
120
+ return fmt.Errorf("listen local tunnel socket: %w", err)
121
+ }
122
+
123
+ addr, ok := rawListener.Addr().(*net.TCPAddr)
124
+ if !ok {
125
+ rawListener.Close()
126
+ return fmt.Errorf("unexpected listener address type: %T", rawListener.Addr())
127
+ }
128
+
129
+ // Use localhost for local development. For production/Docker, this should be configurable.
130
+ tunnelAddr := fmt.Sprintf("127.0.0.1:%d", addr.Port)
131
+ if err := l.registerWithRelay(tunnelAddr); err != nil {
132
+ rawListener.Close()
133
+ return fmt.Errorf("register lease with relay: %w", err)
134
+ }
135
+
136
+ l.mu.Lock()
137
+ if l.closed {
138
+ l.mu.Unlock()
139
+ rawListener.Close()
140
+ return net.ErrClosed
141
+ }
142
+ l.lease.Address = tunnelAddr
143
+ l.listener = rawListener
144
+ l.mu.Unlock()
145
+
146
+ l.wg.Add(1)
147
+ go l.keepaliveLoop()
148
+ l.wg.Add(1)
149
+ go l.localAcceptLoop(rawListener)
150
+ for i := 0; i < l.reverseWorkers; i++ {
151
+ l.wg.Add(1)
152
+ go l.reverseAcceptWorker(i)
153
+ }
154
+
155
+ log.Info().
156
+ Str("lease_id", l.lease.ID).
157
+ Str("name", l.lease.Name).
158
+ Str("relay", l.relayAddr).
159
+ Str("tunnel_addr", tunnelAddr).
160
+ Int("reverse_workers", l.reverseWorkers).
161
+ Msg("[SDK] Relay listener started")
162
+
163
+ return nil
164
+}
165
+
166
+// Accept waits for the next connection from relay.
167
+// If TLS is enabled, it performs TLS handshake before returning the connection.
168
+func (l *Listener) Accept() (net.Conn, error) {
169
+ l.mu.RLock()
170
+ closed := l.closed
171
+ tlsConfig := l.tlsConfig
172
+ l.mu.RUnlock()
173
+ if closed {
174
+ return nil, net.ErrClosed
175
+ }
176
+
177
+ var conn net.Conn
178
+ select {
179
+ case <-l.stopCh:
180
+ return nil, net.ErrClosed
181
+ case conn = <-l.acceptCh:
182
+ if conn == nil {
183
+ return nil, net.ErrClosed
184
+ }
185
+ }
186
+
187
+ // If TLS is enabled, wrap the connection and perform handshake
188
+ if tlsConfig != nil {
189
+ tlsConn := tls.Server(conn, tlsConfig)
190
+ if err := tlsConn.Handshake(); err != nil {
191
+ conn.Close()
192
+ return nil, fmt.Errorf("TLS handshake failed: %w", err)
193
+ }
194
+ return tlsConn, nil
195
+ }
196
+
197
+ return conn, nil
198
+}
199
+
200
+// Close unregisters lease from relay and closes local listener.
201
+func (l *Listener) Close() error {
202
+ var retErr error
203
+ l.closeOnce.Do(func() {
204
+ close(l.stopCh)
205
+
206
+ l.mu.Lock()
207
+ l.closed = true
208
+ listener := l.listener
209
+ l.listener = nil
210
+ l.mu.Unlock()
211
+
212
+ if listener != nil {
213
+ retErr = listener.Close()
214
+ }
215
+
216
+ l.wg.Wait()
217
+
218
+ if err := l.unregisterFromRelay(); err != nil {
219
+ log.Warn().Err(err).Str("lease_id", l.lease.ID).Msg("[SDK] Failed to unregister lease")
220
+ if retErr == nil {
221
+ retErr = err
222
+ }
223
+ }
224
+ })
225
+
226
+ return retErr
227
+}
228
+
229
+// Addr returns local listener address.
230
+func (l *Listener) Addr() net.Addr {
231
+ l.mu.RLock()
232
+ defer l.mu.RUnlock()
233
+ if l.listener != nil {
234
+ return l.listener.Addr()
235
+ }
236
+ return nil
237
+}
238
+
239
+// LeaseID returns lease ID registered to relay.
240
+func (l *Listener) LeaseID() string {
241
+ return l.lease.ID
242
+}
243
+
244
+func (l *Listener) keepaliveLoop() {
245
+ defer l.wg.Done()
246
+
247
+ ticker := time.NewTicker(relayKeepaliveInterval)
248
+ defer ticker.Stop()
249
+
250
+ for {
251
+ select {
252
+ case <-l.stopCh:
253
+ return
254
+ case <-ticker.C:
255
+ if err := l.sendKeepalive(); err != nil {
256
+ log.Warn().Err(err).Str("lease_id", l.lease.ID).Msg("[SDK] Relay keepalive failed")
257
+ }
258
+ }
259
+ }
260
+}
261
+
262
+func (l *Listener) localAcceptLoop(listener net.Listener) {
263
+ defer l.wg.Done()
264
+
265
+ for {
266
+ conn, err := listener.Accept()
267
+ if err != nil {
268
+ select {
269
+ case <-l.stopCh:
270
+ return
271
+ default:
272
+ log.Warn().Err(err).Str("lease_id", l.lease.ID).Msg("[SDK] Local accept error")
273
+ continue
274
+ }
275
+ }
276
+
277
+ select {
278
+ case <-l.stopCh:
279
+ conn.Close()
280
+ return
281
+ case l.acceptCh <- conn:
282
+ }
283
+ }
284
+}
285
+
286
+func (l *Listener) reverseAcceptWorker(workerID int) {
287
+ defer l.wg.Done()
288
+
289
+ for {
290
+ select {
291
+ case <-l.stopCh:
292
+ return
293
+ default:
294
+ }
295
+
296
+ conn, err := l.openReverseConnection()
297
+ if err != nil {
298
+ select {
299
+ case <-l.stopCh:
300
+ return
301
+ case <-time.After(500 * time.Millisecond):
302
+ }
303
+ continue
304
+ }
305
+
306
+ if err := l.waitForReverseStart(conn); err != nil {
307
+ conn.Close()
308
+ if errors.Is(err, net.ErrClosed) {
309
+ return
310
+ }
311
+ log.Debug().
312
+ Err(err).
313
+ Str("lease_id", l.lease.ID).
314
+ Int("worker_id", workerID).
315
+ Msg("[SDK] Reverse wait failed")
316
+ continue
317
+ }
318
+
319
+ select {
320
+ case <-l.stopCh:
321
+ conn.Close()
322
+ return
323
+ case l.acceptCh <- conn:
324
+ }
325
+ }
326
+}
327
+
328
+func (l *Listener) openReverseConnection() (net.Conn, error) {
329
+ connectURL, err := relayConnectURL(l.relayAddr, l.lease.ID, l.lease.ReverseToken)
330
+ if err != nil {
331
+ return nil, err
332
+ }
333
+
334
+ cfg, err := websocket.NewConfig(connectURL, l.relayAddr)
335
+ if err != nil {
336
+ return nil, fmt.Errorf("new reverse websocket config: %w", err)
337
+ }
338
+ cfg.Dialer = &net.Dialer{
339
+ Timeout: l.reverseDialTimeout,
340
+ }
341
+ ctx, cancel := context.WithTimeout(context.Background(), l.reverseDialTimeout)
342
+ defer cancel()
343
+
344
+ conn, err := cfg.DialContext(ctx)
345
+ if err != nil {
346
+ return nil, fmt.Errorf("dial reverse websocket: %w", err)
347
+ }
348
+ conn.PayloadType = websocket.BinaryFrame
349
+ return conn, nil
350
+}
351
+
352
+func (l *Listener) waitForReverseStart(conn net.Conn) error {
353
+ var marker [1]byte
354
+ for {
355
+ _ = conn.SetReadDeadline(time.Now().Add(reverseReadTimeout))
356
+ _, err := io.ReadFull(conn, marker[:])
357
+ if err == nil {
358
+ _ = conn.SetReadDeadline(time.Time{})
359
+ if marker[0] != reverseStartMarker {
360
+ return fmt.Errorf("invalid reverse marker: %d", marker[0])
361
+ }
362
+ return nil
363
+ }
364
+
365
+ if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
366
+ select {
367
+ case <-l.stopCh:
368
+ return net.ErrClosed
369
+ default:
370
+ continue
371
+ }
372
+ }
373
+
374
+ select {
375
+ case <-l.stopCh:
376
+ return net.ErrClosed
377
+ default:
378
+ return err
379
+ }
380
+ }
381
+}
382
+
383
+func (l *Listener) registerWithRelay(tunnelAddr string) error {
384
+ reqBody := struct {
385
+ LeaseID string `json:"lease_id"`
386
+ Name string `json:"name"`
387
+ Address string `json:"address"`
388
+ Metadata portal.Metadata `json:"metadata"`
389
+ TLSEnabled bool `json:"tls_enabled"`
390
+ ReverseToken string `json:"reverse_token"`
391
+ }{
392
+ LeaseID: l.lease.ID,
393
+ Name: l.lease.Name,
394
+ Address: tunnelAddr,
395
+ Metadata: l.lease.Metadata,
396
+ TLSEnabled: l.lease.TLSEnabled,
397
+ ReverseToken: l.lease.ReverseToken,
398
+ }
399
+
400
+ return l.postJSON("/api/register", reqBody)
401
+}
402
+
403
+func (l *Listener) unregisterFromRelay() error {
404
+ reqBody := struct {
405
+ LeaseID string `json:"lease_id"`
406
+ }{
407
+ LeaseID: l.lease.ID,
408
+ }
409
+ return l.postJSON("/api/unregister", reqBody)
410
+}
411
+
412
+func (l *Listener) sendKeepalive() error {
413
+ reqBody := struct {
414
+ LeaseID string `json:"lease_id"`
415
+ ReverseToken string `json:"reverse_token"`
416
+ }{
417
+ LeaseID: l.lease.ID,
418
+ ReverseToken: l.lease.ReverseToken,
419
+ }
420
+ return l.postJSON("/api/renew", reqBody)
421
+}
422
+
423
+func (l *Listener) postJSON(path string, body any) error {
424
+ payload, err := json.Marshal(body)
425
+ if err != nil {
426
+ return fmt.Errorf("marshal request: %w", err)
427
+ }
428
+
429
+ endpoint := strings.TrimSuffix(l.relayAddr, "/") + path
430
+ resp, err := l.httpClient.Post(endpoint, "application/json", bytes.NewReader(payload))
431
+ if err != nil {
432
+ return fmt.Errorf("POST %s: %w", path, err)
433
+ }
434
+ defer resp.Body.Close()
435
+
436
+ if resp.StatusCode != http.StatusOK {
437
+ data, _ := io.ReadAll(resp.Body)
438
+ return fmt.Errorf("POST %s failed: status=%d body=%s", path, resp.StatusCode, strings.TrimSpace(string(data)))
439
+ }
440
+ return nil
441
+}
442
+
443
+func normalizeRelayAPIURL(raw string) (string, error) {
444
+ raw = strings.TrimSpace(raw)
445
+ if raw == "" {
446
+ return "", fmt.Errorf("empty relay URL")
447
+ }
448
+
449
+ // Accept host:port input.
450
+ if !strings.Contains(raw, "://") {
451
+ raw = "http://" + raw
452
+ }
453
+
454
+ u, err := url.Parse(raw)
455
+ if err != nil {
456
+ return "", fmt.Errorf("parse relay URL: %w", err)
457
+ }
458
+ if u.Host == "" {
459
+ return "", fmt.Errorf("relay URL missing host: %q", raw)
460
+ }
461
+
462
+ if host := strings.ToLower(strings.TrimSpace(u.Hostname())); strings.HasSuffix(host, ".localhost") {
463
+ port := u.Port()
464
+ if port != "" {
465
+ u.Host = net.JoinHostPort("localhost", port)
466
+ } else {
467
+ u.Host = "localhost"
468
+ }
469
+ }
470
+
471
+ switch u.Scheme {
472
+ case "ws":
473
+ u.Scheme = "http"
474
+ case "wss":
475
+ u.Scheme = "https"
476
+ case "http", "https":
477
+ default:
478
+ return "", fmt.Errorf("unsupported relay URL scheme: %q", u.Scheme)
479
+ }
480
+
481
+ u.RawQuery = ""
482
+ u.Fragment = ""
483
+ u.Path = strings.TrimSuffix(u.Path, "/")
484
+ if u.Path == "/relay" {
485
+ u.Path = ""
486
+ }
487
+
488
+ return strings.TrimSuffix(u.String(), "/"), nil
489
+}
490
+
491
+func relayConnectURL(relayAddr, leaseID, token string) (string, error) {
492
+ if strings.TrimSpace(leaseID) == "" {
493
+ return "", fmt.Errorf("leaseID is required")
494
+ }
495
+ if strings.TrimSpace(token) == "" {
496
+ return "", fmt.Errorf("reverse token is required")
497
+ }
498
+
499
+ u, err := url.Parse(relayAddr)
500
+ if err != nil {
501
+ return "", fmt.Errorf("parse relay URL: %w", err)
502
+ }
503
+ switch u.Scheme {
504
+ case "http":
505
+ u.Scheme = "ws"
506
+ case "https":
507
+ u.Scheme = "wss"
508
+ default:
509
+ return "", fmt.Errorf("unsupported relay URL scheme: %q", u.Scheme)
510
+ }
511
+ u.Path = "/api/connect"
512
+ q := u.Query()
513
+ q.Set("lease_id", leaseID)
514
+ q.Set("token", token)
515
+ u.RawQuery = q.Encode()
516
+ u.Fragment = ""
517
+ return u.String(), nil
518
+}
sdk/listener_test.go
new
+89
@@ -0,0 +1,89 @@
1
+package sdk
2
+
3
+import (
4
+ "strings"
5
+ "testing"
6
+)
7
+
8
+func TestNormalizeRelayAPIURL(t *testing.T) {
9
+ t.Parallel()
10
+
11
+ tests := []struct {
12
+ name string
13
+ in string
14
+ want string
15
+ wantErr bool
16
+ }{
17
+ {name: "ws relay path", in: "ws://localhost:4017/relay", want: "http://localhost:4017"},
18
+ {name: "wss relay path", in: "wss://example.com/relay", want: "https://example.com"},
19
+ {name: "localhost subdomain to localhost", in: "http://demo-app.localhost:4017", want: "http://localhost:4017"},
20
+ {name: "localhost subdomain with relay path", in: "ws://demo-app.localhost:4017/relay", want: "http://localhost:4017"},
21
+ {name: "http base", in: "http://example.com", want: "http://example.com"},
22
+ {name: "https base", in: "https://example.com/", want: "https://example.com"},
23
+ {name: "bare host", in: "localhost:4017", want: "http://localhost:4017"},
24
+ {name: "invalid scheme", in: "ftp://example.com", wantErr: true},
25
+ {name: "empty", in: "", wantErr: true},
26
+ }
27
+
28
+ for _, tt := range tests {
29
+ tt := tt
30
+ t.Run(tt.name, func(t *testing.T) {
31
+ t.Parallel()
32
+
33
+ got, err := normalizeRelayAPIURL(tt.in)
34
+ if tt.wantErr {
35
+ if err == nil {
36
+ t.Fatalf("expected error for input %q, got none", tt.in)
37
+ }
38
+ return
39
+ }
40
+ if err != nil {
41
+ t.Fatalf("unexpected error for input %q: %v", tt.in, err)
42
+ }
43
+ if got != tt.want {
44
+ t.Fatalf("normalizeRelayAPIURL(%q) = %q, want %q", tt.in, got, tt.want)
45
+ }
46
+ })
47
+ }
48
+}
49
+
50
+func TestFirstRelayAPIURL(t *testing.T) {
51
+ t.Parallel()
52
+
53
+ got, err := firstRelayAPIURL([]string{"invalid://relay", "ws://localhost:4017/relay"})
54
+ if err != nil {
55
+ t.Fatalf("unexpected error: %v", err)
56
+ }
57
+ if got != "http://localhost:4017" {
58
+ t.Fatalf("unexpected relay URL: got %q", got)
59
+ }
60
+
61
+ if _, err := firstRelayAPIURL(nil); err == nil {
62
+ t.Fatal("expected error with no bootstrap servers")
63
+ }
64
+}
65
+
66
+func TestRelayConnectURL(t *testing.T) {
67
+ t.Parallel()
68
+
69
+ got, err := relayConnectURL("http://localhost:4017", "lease-1", "token-1")
70
+ if err != nil {
71
+ t.Fatalf("unexpected error: %v", err)
72
+ }
73
+ if !strings.HasPrefix(got, "ws://localhost:4017/api/connect?") {
74
+ t.Fatalf("unexpected URL prefix: %q", got)
75
+ }
76
+ if !strings.Contains(got, "lease_id=lease-1") {
77
+ t.Fatalf("missing lease_id in URL: %q", got)
78
+ }
79
+ if !strings.Contains(got, "token=token-1") {
80
+ t.Fatalf("missing token in URL: %q", got)
81
+ }
82
+
83
+ if _, err := relayConnectURL("http://localhost:4017", "", "token-1"); err == nil {
84
+ t.Fatal("expected error for empty lease ID")
85
+ }
86
+ if _, err := relayConnectURL("http://localhost:4017", "lease-1", ""); err == nil {
87
+ t.Fatal("expected error for empty token")
88
+ }
89
+}
sdk/sdk.go
+135
-678
@@ -1,188 +1,64 @@
1
+// Package sdk provides a client for registering leases with the Portal relay.
2
package sdk
3
4
import (
4
- "context"
5
- "encoding/json"
5
+ "crypto/rand"
6
+ "crypto/tls"
7
+ "encoding/hex"
8
"fmt"
7
- "io"
9
"net"
9
- "strings"
10
"sync"
11
"time"
12
13
"github.com/rs/zerolog/log"
14
-
14
+ "golang.org/x/crypto/acme/autocert"
15
"gosuda.org/portal/portal"
16
- "gosuda.org/portal/portal/core/cryptoops"
17
- "gosuda.org/portal/portal/core/proto/rdsec"
18
- "gosuda.org/portal/portal/core/proto/rdverb"
16
"gosuda.org/portal/utils"
17
)
18
22
-func NewCredential() *cryptoops.Credential {
23
- cred, err := cryptoops.NewCredential()
24
- if err != nil {
25
- log.Fatal().Err(err).Msg("Failed to create credential")
26
- }
27
- return cred
28
-}
29
-
19
+// Client is a minimal client for lease registration with the relay.
20
type Client struct {
31
- config *ClientConfig
21
mu sync.Mutex
22
+ config *ClientConfig
23
34
- relays map[string]*connRelay
35
- listeners map[string]*listener
24
+ leases map[string]*portal.Lease
25
26
stopch chan struct{}
38
- stopOnce sync.Once // Ensure stopch is closed only once
39
- waitGroup sync.WaitGroup // Track all listener workers
27
+ stopOnce sync.Once
28
+ waitGroup sync.WaitGroup
29
}
30
31
+// NewClient creates a new SDK client.
32
func NewClient(opt ...ClientOption) (*Client, error) {
43
- log.Debug().Msg("[SDK] Creating new Client")
44
-
33
config := &ClientConfig{
46
- Dialer: utils.NewWebSocketDialer(),
47
- HealthCheckInterval: 10 * time.Second,
48
- ReconnectMaxRetries: 0,
49
- ReconnectInterval: 5 * time.Second,
34
+ BootstrapServers: []string{},
35
+ ReverseWorkers: 2,
36
+ ReverseDialTimeout: 5 * time.Second,
37
}
38
39
for _, o := range opt {
40
o(config)
41
}
42
56
- client := &Client{
57
- relays: make(map[string]*connRelay),
58
- listeners: make(map[string]*listener),
59
- config: config,
60
- stopch: make(chan struct{}),
61
- }
62
-
63
- // Initialize relays from bootstrap servers
64
- var connectionErrors []error
65
- for _, server := range config.BootstrapServers {
66
- normalized, err := utils.NormalizePortalURL(server)
67
- if err != nil {
68
- log.Error().
69
- Err(err).
70
- Str("server", server).
71
- Msg("[SDK] Invalid bootstrap server")
72
- connectionErrors = append(connectionErrors, err)
73
- continue
74
- }
75
-
76
- err = client.AddRelay(normalized, config.Dialer)
77
- if err != nil {
78
- log.Error().
79
- Err(err).
80
- Str("server", normalized).
81
- Msg("[SDK] Failed to connect to bootstrap server")
82
- connectionErrors = append(connectionErrors, err)
83
- continue
84
- }
85
- log.Debug().
86
- Str("server_raw", server).
87
- Str("server", normalized).
88
- Msg("[SDK] Successfully connected to bootstrap server")
89
- }
90
-
91
- // If no relays were successfully connected, return an error
92
- if len(client.relays) == 0 && len(config.BootstrapServers) > 0 {
93
- log.Error().Int("attempted", len(config.BootstrapServers)).Msg("[SDK] Failed to connect to any bootstrap servers")
94
- return nil, fmt.Errorf("failed to connect to any bootstrap servers: %v", connectionErrors)
95
- }
96
-
97
- log.Debug().Int("relay_count", len(client.relays)).Msg("[SDK] Client created successfully")
98
- return client, nil
43
+ return &Client{
44
+ config: config,
45
+ leases: make(map[string]*portal.Lease),
46
+ stopch: make(chan struct{}),
47
+ }, nil
48
}
49
101
-func (g *Client) Dial(cred *cryptoops.Credential, leaseID string, alpn string) (*connection, error) {
102
- log.Debug().
103
- Str("lease_id", leaseID).
104
- Str("alpn", alpn).
105
- Msg("[SDK] Dialing to lease")
50
+// Listen creates a listener and registers it with the relay.
51
+// In TLS passthrough mode, this registers the lease and returns a listener
52
+// that accepts connections from the relay.
53
+func (c *Client) Listen(name string, options ...MetadataOption) (net.Listener, error) {
54
+ c.mu.Lock()
55
+ defer c.mu.Unlock()
56
107
- g.mu.Lock()
108
- relays := make([]*connRelay, 0, len(g.relays))
109
- for _, server := range g.relays {
110
- relays = append(relays, server)
111
- }
112
- g.mu.Unlock()
113
-
114
- log.Debug().Int("relay_count", len(relays)).Msg("[SDK] Checking relays for lease")
115
-
116
- var wg sync.WaitGroup
117
- var availableRelaysMu sync.Mutex
118
- var availableRelays []*connRelay
119
-
120
- for _, relay := range relays {
121
- wg.Add(1)
122
- go func(relay *connRelay) {
123
- defer wg.Done()
124
- info, err := relay.client.GetRelayInfo()
125
- if err != nil {
126
- log.Debug().Err(err).Str("relay", relay.addr).Msg("[SDK] Failed to get relay info")
127
- return
128
- }
129
-
130
- for _, lease := range info.Leases {
131
- if lease.Identity.Id == leaseID {
132
- log.Debug().Str("relay", relay.addr).Str("lease_id", leaseID).Msg("[SDK] Found lease on relay")
133
- availableRelaysMu.Lock()
134
- availableRelays = append(availableRelays, relay)
135
- availableRelaysMu.Unlock()
136
- break
137
- }
138
- }
139
- }(relay)
57
+ // Validate name
58
+ if name == "" {
59
+ return nil, fmt.Errorf("name is required")
60
}
141
- wg.Wait()
142
-
143
- if len(availableRelays) == 0 {
144
- log.Warn().Str("lease_id", leaseID).Msg("[SDK] No available relay found for lease")
145
- return nil, ErrNoAvailableRelay
146
- }
147
-
148
- log.Debug().Int("available_relays", len(availableRelays)).Str("lease_id", leaseID).Msg("[SDK] Attempting to connect")
149
-
150
- for _, relay := range availableRelays {
151
- log.Debug().Str("relay", relay.addr).Str("lease_id", leaseID).Msg("[SDK] Requesting connection")
152
- code, conn, err := relay.client.RequestConnection(leaseID, alpn, cred)
153
- if err != nil || code != rdverb.ResponseCode_RESPONSE_CODE_ACCEPTED {
154
- log.Debug().
155
- Err(err).
156
- Str("relay", relay.addr).
157
- Str("code", code.String()).
158
- Msg("[SDK] Connection request failed, trying next relay")
159
- continue
160
- }
161
- log.Debug().
162
- Str("relay", relay.addr).
163
- Str("lease_id", leaseID).
164
- Str("local", conn.LocalID()).
165
- Str("remote", conn.RemoteID()).
166
- Msg("[SDK] Connection established successfully")
167
- return &connection{via: relay, conn: conn, localAddr: conn.LocalID(), remoteAddr: conn.RemoteID()}, nil
168
- }
169
-
170
- log.Warn().Str("lease_id", leaseID).Msg("[SDK] All connection attempts failed")
171
- return nil, ErrNoAvailableRelay
172
-}
173
-
174
-func (g *Client) Listen(cred *cryptoops.Credential, name string, alpns []string, options ...MetadataOption) (*listener, error) {
175
- log.Debug().
176
- Str("lease_id", cred.ID()).
177
- Str("name", name).
178
- Strs("alpns", alpns).
179
- Msg("[SDK] Creating listener")
180
-
181
- // Validate name is URL-safe
61
if !utils.IsURLSafeName(name) {
183
- log.Error().
184
- Str("name", name).
185
- Msg("[SDK] Lease name contains invalid characters")
62
return nil, ErrInvalidName
63
}
64
@@ -191,575 +67,156 @@ func (g *Client) Listen(cred *cryptoops.Credential, name string, alpns []string,
67
option(&metadata)
68
}
69
194
- var metadataValue string
195
- metadataJSON, err := json.Marshal(metadata)
70
+ relayAddr, err := firstRelayAPIURL(c.config.BootstrapServers)
71
if err != nil {
197
- log.Warn().Err(err).Msg("[SDK] Failed to marshal metadata")
198
- } else {
199
- metadataValue = string(metadataJSON)
72
+ return nil, err
73
}
74
202
- g.mu.Lock()
203
- defer g.mu.Unlock()
204
-
205
- // Check if client is closed
206
- select {
207
- case <-g.stopch:
208
- log.Error().Msg("[SDK] Cannot create listener, client is closed")
209
- return nil, ErrClientClosed
210
- default:
211
- // Client is still open
75
+ // Create lease
76
+ reverseToken, err := generateToken(16)
77
+ if err != nil {
78
+ return nil, fmt.Errorf("generate reverse token: %w", err)
79
+ }
80
+
81
+ lease := &portal.Lease{
82
+ ID: generateID(),
83
+ Name: name,
84
+ Address: "",
85
+ TLSEnabled: c.config.TLSEnabled,
86
+ ReverseToken: reverseToken,
87
+ Metadata: portal.Metadata{
88
+ Description: metadata.Description,
89
+ Tags: metadata.Tags,
90
+ Thumbnail: metadata.Thumbnail,
91
+ Owner: metadata.Owner,
92
+ Hide: metadata.Hide,
93
+ },
94
+ Expires: time.Now().Add(30 * time.Second),
95
}
96
214
- // Check if listener already exists
215
- if _, exists := g.listeners[cred.ID()]; exists {
216
- log.Warn().Str("lease_id", cred.ID()).Msg("[SDK] Listener already exists")
217
- return nil, ErrListenerExists
218
- }
97
+ // Build TLS config if enabled
98
+ var tlsConfig *tls.Config
99
+ var autocertMgr *autocert.Manager
100
220
- lease := &rdverb.Lease{
221
- Identity: &rdsec.Identity{
222
- Id: cred.ID(),
223
- PublicKey: cred.PublicKey(),
224
- },
225
- Name: name,
226
- Alpn: alpns,
227
- Metadata: metadataValue,
101
+ if c.config.TLSEnabled {
102
+ tlsConfig, autocertMgr, err = buildTLSConfig(c.config)
103
+ if err != nil {
104
+ return nil, fmt.Errorf("build TLS config: %w", err)
105
+ }
106
}
107
230
- // Create listener with lease metadata for re-registration
231
- listener := &listener{
232
- cred: cred,
233
- lease: lease,
234
- conns: make(map[*connection]struct{}),
235
- connCh: make(chan *connection, 100),
236
- closed: false,
108
+ listener, err := NewListener(relayAddr, lease, tlsConfig, autocertMgr, c.config.ReverseWorkers, c.config.ReverseDialTimeout)
109
+ if err != nil {
110
+ return nil, fmt.Errorf("create relay listener: %w", err)
111
}
238
-
239
- // Register listener
240
- g.listeners[cred.ID()] = listener
241
-
242
- log.Debug().
243
- Str("lease_id", cred.ID()).
244
- Int("relay_count", len(g.relays)).
245
- Msg("[SDK] Registering lease with relays")
246
-
247
- // Register lease with all available relays
248
- for _, relay := range g.relays {
249
- go func(r *connRelay) {
250
- err := r.client.RegisterLease(cred, listener.lease)
251
- if err != nil {
252
- log.Error().Err(err).Str("relay", r.addr).Msg("[SDK] Failed to register lease")
253
- } else {
254
- log.Debug().Str("relay", r.addr).Msg("[SDK] Lease registered successfully")
255
- // Store lease info in listener for future re-registration
256
- listener.mu.Lock()
257
- listener.lease = lease
258
- listener.mu.Unlock()
259
- }
260
- }(relay)
112
+ if err := listener.Start(); err != nil {
113
+ return nil, fmt.Errorf("start relay listener: %w", err)
114
}
115
263
- // Start listener worker for each relay
264
- for _, relay := range g.relays {
265
- g.waitGroup.Add(1)
266
- go g.listenerWorker(relay)
116
+ c.leases[lease.ID] = lease
117
+
118
+ if c.config.TLSEnabled {
119
+ log.Info().
120
+ Str("lease_id", lease.ID).
121
+ Str("name", name).
122
+ Bool("tls", true).
123
+ Msg("[SDK] Lease registered with TLS")
124
+ } else {
125
+ log.Info().
126
+ Str("lease_id", lease.ID).
127
+ Str("name", name).
128
+ Msg("[SDK] Lease registered")
129
}
130
269
- log.Debug().Str("lease_id", cred.ID()).Msg("[SDK] Listener created successfully")
131
return listener, nil
132
}
133
273
-func (g *Client) listenerWorker(server *connRelay) {
274
- defer g.waitGroup.Done()
275
- log.Debug().Str("relay", server.addr).Msg("[SDK] Listener worker started")
276
-
277
- for {
278
- select {
279
- case <-server.stop:
280
- log.Debug().Str("relay", server.addr).Msg("[SDK] Listener worker stopped")
281
- return
282
- case incoming, ok := <-server.client.IncomingConnection():
283
- if !ok {
284
- log.Debug().Str("relay", server.addr).Msg("[SDK] Incoming connection channel closed")
285
- return // Channel closed
286
- }
287
-
288
- lease := incoming.LeaseID()
289
- log.Debug().
290
- Str("relay", server.addr).
291
- Str("lease_id", lease).
292
- Str("local", incoming.LocalID()).
293
- Str("remote", incoming.RemoteID()).
294
- Msg("[SDK] Received incoming connection")
295
-
296
- g.mu.Lock()
297
- listener, exists := g.listeners[lease]
298
- g.mu.Unlock()
299
-
300
- if !exists {
301
- log.Warn().Str("lease_id", lease).Msg("[SDK] No listener found for lease, closing connection")
302
- incoming.Close() // Close unused connection
303
- continue
304
- }
305
-
306
- conn := &connection{
307
- via: server,
308
- conn: incoming.SecureConnection,
309
- localAddr: incoming.LocalID(),
310
- remoteAddr: incoming.RemoteID(),
311
- }
312
-
313
- listener.mu.Lock()
314
- // Check if listener is still active
315
- if listener.closed {
316
- log.Debug().Str("lease_id", lease).Msg("[SDK] Listener closed, rejecting connection")
317
- listener.mu.Unlock()
318
- conn.Close()
319
- continue
320
- }
321
- listener.conns[conn] = struct{}{}
322
- listener.mu.Unlock()
323
-
324
- // Send connection to listener (non-blocking)
325
- select {
326
- case listener.connCh <- conn:
327
- log.Debug().Str("lease_id", lease).Msg("[SDK] Connection sent to listener channel")
328
- // Connection sent successfully
329
- default:
330
- // Channel full, close connection
331
- log.Warn().Str("lease_id", lease).Msg("[SDK] Listener channel full, closing connection")
332
- listener.mu.Lock()
333
- delete(listener.conns, conn)
334
- listener.mu.Unlock()
335
- conn.Close()
336
- }
337
- }
134
+// buildTLSConfig builds TLS configuration from client config
135
+func buildTLSConfig(config *ClientConfig) (*tls.Config, *autocert.Manager, error) {
136
+ tlsConfig := &tls.Config{
137
+ MinVersion: tls.VersionTLS12,
138
}
339
-}
340
-
341
-func (g *Client) Close() error {
342
- log.Debug().Msg("[SDK] Closing Client")
343
- var errs []error
139
345
- // Signal all goroutines to stop (only once)
346
- g.stopOnce.Do(func() {
347
- close(g.stopch)
348
- })
140
+ var autocertMgr *autocert.Manager
141
350
- g.mu.Lock()
351
- listeners := make([]*listener, 0, len(g.listeners))
352
- for _, listener := range g.listeners {
353
- listeners = append(listeners, listener)
354
- }
355
- relays := make([]*connRelay, 0, len(g.relays))
356
- for _, relay := range g.relays {
357
- relays = append(relays, relay)
358
- }
359
- g.mu.Unlock()
360
-
361
- // Close all listeners first
362
- for _, listener := range listeners {
363
- if err := listener.Close(); err != nil {
364
- log.Error().Err(err).Msg("[SDK] Error closing listener")
365
- errs = append(errs, err)
142
+ if config.TLSAutocert {
143
+ // Use Let's Encrypt autocert
144
+ if config.TLSDomain == "" {
145
+ return nil, nil, fmt.Errorf("TLS domain is required for autocert")
146
}
367
- }
147
369
- // Stop all relays
370
- for _, relay := range relays {
371
- if err := g.RemoveRelay(relay.addr); err != nil && err != ErrRelayNotFound {
372
- log.Error().Err(err).Str("relay", relay.addr).Msg("[SDK] Error removing relay")
373
- errs = append(errs, err)
148
+ autocertDir := config.TLSAutocertDir
149
+ if autocertDir == "" {
150
+ autocertDir = "autocert-cache"
151
}
375
- }
376
-
377
- // Wait for all listener workers to finish
378
- log.Debug().Msg("[SDK] Waiting for all workers to finish")
379
- g.waitGroup.Wait()
152
381
- log.Debug().Msg("[SDK] Client closed successfully")
382
- if len(errs) > 0 {
383
- return errs[0]
384
- }
385
- return nil
386
-}
387
-
388
-// healthCheckWorker periodically checks relay health and reconnects if needed
389
-func (g *Client) healthCheckWorker(relay *connRelay) {
390
- defer g.waitGroup.Done()
391
-
392
- ticker := time.NewTicker(g.config.HealthCheckInterval)
393
- defer ticker.Stop()
394
-
395
- log.Debug().Str("relay", relay.addr).Msg("[SDK] Health check worker started")
396
-
397
- for {
398
- select {
399
- case <-g.stopch:
400
- log.Debug().Str("relay", relay.addr).Msg("[SDK] Health check worker stopped (client closing)")
401
- return
402
- case <-relay.stop:
403
- log.Debug().Str("relay", relay.addr).Msg("[SDK] Health check worker stopped (relay stopped)")
404
- return
405
- case <-ticker.C:
406
- // Check if client is still active
407
- select {
408
- case <-g.stopch:
409
- return
410
- case <-relay.stop:
411
- return
412
- default:
413
- }
414
-
415
- relay.mu.Lock()
416
- client := relay.client
417
- relay.mu.Unlock()
418
-
419
- if client == nil {
420
- log.Warn().Str("relay", relay.addr).Msg("[SDK] Relay client is nil, attempting reconnection")
421
- g.reconnectRelay(relay)
422
- continue
423
- }
424
-
425
- // Perform health check using Ping
426
- _, err := client.Ping()
427
- if err != nil {
428
- log.Warn().
429
- Err(err).
430
- Str("relay", relay.addr).
431
- Msg("[SDK] Health check failed, attempting reconnection")
432
- g.reconnectRelay(relay)
433
- // Continue monitoring instead of returning
434
- continue
435
- }
153
+ autocertMgr = &autocert.Manager{
154
+ Cache: autocert.DirCache(autocertDir),
155
+ Prompt: autocert.AcceptTOS,
156
+ HostPolicy: autocert.HostWhitelist(config.TLSDomain),
157
}
437
- }
438
-}
439
-
440
-// reconnectRelay attempts to reconnect to a relay server
441
-func (g *Client) reconnectRelay(relay *connRelay) {
442
- addr := relay.addr
443
- dialer := relay.dialer
158
445
- log.Debug().Str("relay", addr).Msg("[SDK] Starting reconnection process")
446
-
447
- // Remove the failed relay
448
- if err := g.RemoveRelay(addr); err != nil && err != ErrRelayNotFound {
449
- log.Error().Err(err).Str("relay", addr).Msg("[SDK] Error removing relay during reconnection")
450
- }
451
-
452
- // Start reconnection in a goroutine
453
- g.waitGroup.Add(1)
454
- go func() {
455
- defer g.waitGroup.Done()
456
-
457
- retries := 0
458
- maxRetries := g.config.ReconnectMaxRetries
459
-
460
- for {
461
- // Check if client is shutting down
462
- select {
463
- case <-g.stopch:
464
- log.Debug().Str("relay", addr).Msg("[SDK] Reconnection cancelled (client closing)")
465
- return
466
- default:
467
- }
468
-
469
- // Attempt reconnection
470
- err := g.AddRelay(addr, dialer)
471
- if err == nil {
472
- log.Info().Str("relay", addr).Msg("[SDK] Reconnection successful")
473
- return
474
- }
475
-
476
- if err == ErrRelayExists {
477
- log.Debug().Str("relay", addr).Msg("[SDK] Relay already exists, reconnection complete")
478
- return
479
- }
480
-
481
- retries++
482
-
483
- // Check retry limit (0 or negative means infinite retries)
484
- if maxRetries > 0 && retries >= maxRetries {
485
- log.Error().
486
- Err(err).
487
- Str("relay", addr).
488
- Int("retries", retries).
489
- Msg("[SDK] Reconnection failed after max retries")
490
- return
491
- }
492
-
493
- log.Warn().
494
- Err(err).
495
- Str("relay", addr).
496
- Int("attempt", retries).
497
- Msg("[SDK] Reconnection failed, retrying")
498
-
499
- // Wait before next retry with context awareness
500
- select {
501
- case <-g.stopch:
502
- log.Debug().Str("relay", addr).Msg("[SDK] Reconnection cancelled during wait")
503
- return
504
- case <-time.After(g.config.ReconnectInterval):
505
- // Continue to next retry
506
- }
159
+ tlsConfig.GetCertificate = autocertMgr.GetCertificate
160
+ log.Info().
161
+ Str("domain", config.TLSDomain).
162
+ Str("cache_dir", autocertDir).
163
+ Msg("[SDK] Using Let's Encrypt autocert for TLS")
164
+ } else if config.TLSCert != "" && config.TLSKey != "" {
165
+ // Use provided certificate
166
+ cert, err := tls.LoadX509KeyPair(config.TLSCert, config.TLSKey)
167
+ if err != nil {
168
+ return nil, nil, fmt.Errorf("load TLS certificate: %w", err)
169
}
508
- }()
509
-}
510
-
511
-// AddRelay adds a new relay server to the client
512
-func (g *Client) AddRelay(addr string, dialer func(context.Context, string) (io.ReadWriteCloser, error)) error {
513
- g.mu.Lock()
514
- defer g.mu.Unlock()
515
-
516
- // Check if relay already exists
517
- if _, exists := g.relays[addr]; exists {
518
- return ErrRelayExists
519
- }
520
-
521
- // Connect to relay
522
- conn, err := dialer(context.Background(), addr)
523
- if err != nil {
524
- return err
525
- }
526
-
527
- // Create relay client
528
- relayClient := portal.NewRelayClient(conn)
529
- if relayClient == nil {
530
- conn.Close()
531
- return ErrRelayNotFound
532
- }
533
-
534
- // Add relay
535
- relay := &connRelay{
536
- addr: addr,
537
- client: relayClient,
538
- dialer: dialer,
539
- stop: make(chan struct{}),
540
- }
541
- g.relays[addr] = relay
542
-
543
- // Register all existing leases with the new relay
544
- for _, listener := range g.listeners {
545
- cred := listener.cred // immutable
546
- lease := listener.lease.CloneVT()
547
- go func(cred *cryptoops.Credential, lease *rdverb.Lease) {
548
- err := relayClient.RegisterLease(cred, lease)
549
- if err != nil {
550
- log.Error().
551
- Err(err).
552
- Str("relay", addr).
553
- Str("lease_id", cred.ID()).
554
- Msg("[SDK] Failed to register lease with new relay")
555
- } else {
556
- log.Debug().
557
- Str("relay", addr).
558
- Str("lease_id", cred.ID()).
559
- Msg("[SDK] Lease registered with new relay")
560
- }
561
- }(cred, lease)
170
+ tlsConfig.Certificates = []tls.Certificate{cert}
171
+ log.Info().
172
+ Str("cert", config.TLSCert).
173
+ Str("key", config.TLSKey).
174
+ Msg("[SDK] Using custom TLS certificate")
175
+ } else {
176
+ return nil, nil, fmt.Errorf("TLS enabled but no certificate source configured (set TLSAutocert=true or provide TLSCert/TLSKey)")
177
}
178
564
- // Start listener worker for the new relay
565
- g.waitGroup.Add(1)
566
- go g.listenerWorker(relay)
567
-
568
- // Start health monitoring for this relay
569
- g.waitGroup.Add(1)
570
- go g.healthCheckWorker(relay)
571
-
572
- log.Info().Str("relay", addr).Msg("[SDK] New relay added successfully")
573
-
574
- return nil
179
+ return tlsConfig, autocertMgr, nil
180
}
181
577
-// RemoveRelay removes a relay server from the client
578
-func (g *Client) RemoveRelay(addr string) error {
579
- g.mu.Lock()
580
- relay, exists := g.relays[addr]
581
- if !exists {
582
- g.mu.Unlock()
583
- return ErrRelayNotFound
584
- }
585
-
586
- // Remove from map immediately to prevent duplicate removals
587
- delete(g.relays, addr)
588
- g.mu.Unlock()
589
-
590
- log.Debug().Str("relay", addr).Msg("[SDK] Removing relay")
591
-
592
- // Signal relay to stop (only once)
593
- relay.stopOnce.Do(func() {
594
- close(relay.stop)
182
+// Close closes the client.
183
+func (c *Client) Close() error {
184
+ c.stopOnce.Do(func() {
185
+ close(c.stopch)
186
})
596
-
597
- // Close relay client
598
- relay.mu.Lock()
599
- client := relay.client
600
- relay.mu.Unlock()
601
-
602
- if client != nil {
603
- if err := client.Close(); err != nil {
604
- log.Error().Err(err).Str("relay", addr).Msg("[SDK] Error closing relay client")
605
- return err
606
- }
607
- }
608
-
609
- log.Debug().Str("relay", addr).Msg("[SDK] Relay removed successfully")
187
+ c.waitGroup.Wait()
188
return nil
189
}
190
613
-// GetRelays returns a list of all relay addresses
614
-func (g *Client) GetRelays() []string {
615
- g.mu.Lock()
616
- defer g.mu.Unlock()
617
-
618
- relays := make([]string, 0, len(g.relays))
619
- for addr := range g.relays {
620
- relays = append(relays, addr)
191
+func firstRelayAPIURL(bootstrapServers []string) (string, error) {
192
+ if len(bootstrapServers) == 0 {
193
+ return "", ErrNoAvailableRelay
194
}
195
623
- return relays
624
-}
625
-
626
-func (g *Client) LookupName(name string) (*rdverb.Lease, error) {
627
- log.Debug().Str("name", name).Msg("[SDK] Looking up name")
628
-
629
- g.mu.Lock()
630
- relays := make([]*connRelay, 0, len(g.relays))
631
- for _, server := range g.relays {
632
- relays = append(relays, server)
633
- }
634
- g.mu.Unlock()
635
-
636
- for _, relay := range relays {
637
- info, err := relay.client.GetRelayInfo()
638
- if err != nil {
639
- log.Error().Err(err).Str("relay", relay.addr).Msg("[SDK] Error getting relay info")
640
- continue
641
- }
642
-
643
- for _, lease := range info.Leases {
644
- if strings.EqualFold(lease.Name, name) {
645
- log.Debug().Str("name", name).Str("id", lease.Identity.Id).Msg("[SDK] Found lease")
646
- return lease, nil
647
- }
196
+ for _, relay := range bootstrapServers {
197
+ normalized, err := normalizeRelayAPIURL(relay)
198
+ if err == nil {
199
+ return normalized, nil
200
}
201
}
650
- return nil, ErrNoAvailableRelay
651
-}
652
-
653
-type listener struct {
654
- mu sync.Mutex
655
-
656
- cred *cryptoops.Credential
657
- lease *rdverb.Lease
202
659
- conns map[*connection]struct{}
660
-
661
- connCh chan *connection
662
- closed bool
203
+ return "", ErrNoAvailableRelay
204
}
205
665
-// Implement net.Listener interface for Listener
666
-func (l *listener) Accept() (net.Conn, error) {
667
- conn, ok := <-l.connCh
668
- if !ok {
669
- return nil, net.ErrClosed
670
- }
671
- return conn, nil
206
+// generateID generates a unique ID for the lease.
207
+func generateID() string {
208
+ b := make([]byte, 16)
209
+ rand.Read(b)
210
+ return hex.EncodeToString(b)
211
}
212
674
-func (l *listener) Close() error {
675
- l.mu.Lock()
676
- defer l.mu.Unlock()
677
-
678
- if l.closed {
679
- return nil
213
+func generateToken(size int) (string, error) {
214
+ if size <= 0 {
215
+ size = 16
216
}
681
-
682
- l.closed = true
683
-
684
- // Close the connection channel first to prevent new connections
685
- close(l.connCh)
686
-
687
- // Close all active connections
688
- for conn := range l.conns {
689
- if err := conn.Close(); err != nil {
690
- log.Error().Err(err).Msg("[SDK] Error closing connection")
691
- }
692
- delete(l.conns, conn)
217
+ b := make([]byte, size)
218
+ if _, err := rand.Read(b); err != nil {
219
+ return "", err
220
}
694
-
695
- // Clear the connections map
696
- l.conns = make(map[*connection]struct{})
697
-
698
- return nil
699
-}
700
-
701
-func (l *listener) Addr() net.Addr {
702
- return addr(l.cred.ID())
703
-}
704
-
705
-type connRelay struct {
706
- addr string
707
- client *portal.RelayClient
708
- dialer func(context.Context, string) (io.ReadWriteCloser, error)
709
- stop chan struct{}
710
- stopOnce sync.Once // Ensure stop channel is closed only once
711
- mu sync.Mutex
712
-}
713
-
714
-var _ net.Conn = (*connection)(nil)
715
-
716
-type connection struct {
717
- via *connRelay
718
- localAddr string
719
- remoteAddr string
720
- conn *cryptoops.SecureConnection
721
-}
722
-
723
-func (r *connection) Read(b []byte) (n int, err error) {
724
- return r.conn.Read(b)
725
-}
726
-
727
-func (r *connection) Write(b []byte) (n int, err error) {
728
- return r.conn.Write(b)
729
-}
730
-
731
-func (r *connection) Close() error {
732
- return r.conn.Close()
733
-}
734
-
735
-func (r *connection) LocalAddr() net.Addr {
736
- return addr(r.localAddr)
737
-}
738
-
739
-func (r *connection) RemoteAddr() net.Addr {
740
- return addr(r.remoteAddr)
741
-}
742
-
743
-func (r *connection) SetDeadline(t time.Time) error {
744
- return r.conn.SetDeadline(t)
745
-}
746
-
747
-func (r *connection) SetReadDeadline(t time.Time) error {
748
- return r.conn.SetReadDeadline(t)
749
-}
750
-
751
-func (r *connection) SetWriteDeadline(t time.Time) error {
752
- return r.conn.SetWriteDeadline(t)
753
-}
754
-
755
-var _ net.Addr = (*addr)(nil)
756
-
757
-type addr string
758
-
759
-func (a addr) Network() string {
760
- return "portal"
761
-}
762
-
763
-func (a addr) String() string {
764
- return string(a)
221
+ return hex.EncodeToString(b), nil
222
}
sdk/sdk_e2e_test.go
deleted
-391
@@ -1,391 +0,0 @@
1
-package sdk
2
-
3
-import (
4
- "bufio"
5
- "context"
6
- "fmt"
7
- "io"
8
- "net"
9
- "net/http"
10
- "os"
11
- "testing"
12
- "time"
13
-
14
- "github.com/rs/zerolog"
15
- "github.com/rs/zerolog/log"
16
- "github.com/stretchr/testify/assert"
17
- "github.com/stretchr/testify/require"
18
-
19
- "gosuda.org/portal/portal"
20
- "gosuda.org/portal/portal/core/cryptoops"
21
- "gosuda.org/portal/utils"
22
-)
23
-
24
-func init() {
25
- // Set zerolog to Debug level for testing
26
- zerolog.SetGlobalLevel(zerolog.DebugLevel)
27
- log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
28
-}
29
-
30
-// TestE2E_ClientToAppThroughRelay tests the full end-to-end flow:
31
-// SDK Client -> Relay Server -> Demo App
32
-func TestE2E_ClientToAppThroughRelay(t *testing.T) {
33
- log.Info().Msg("=== Starting E2E Test ===")
34
-
35
- // 1. Create relay server credential
36
- log.Info().Msg("[TEST] Step 1: Creating relay server credential")
37
- relayServerCred, err := cryptoops.NewCredential()
38
- require.NoError(t, err, "Failed to create relay server credential")
39
- log.Debug().Str("relay_id", relayServerCred.ID()).Msg("[TEST] Relay server credential created")
40
-
41
- // 2. Start relay server
42
- log.Info().Msg("[TEST] Step 2: Starting relay server")
43
- relayServer := portal.NewRelayServer(relayServerCred, []string{"ws://127.0.0.1:14017/relay"})
44
- relayServer.Start()
45
- defer relayServer.Stop()
46
-
47
- // Start WebSocket server for relay
48
- relayAddr := "127.0.0.1:14017"
49
- relayMux := http.NewServeMux()
50
- relayMux.HandleFunc("/relay", func(w http.ResponseWriter, r *http.Request) {
51
- log.Debug().Str("remote", r.RemoteAddr).Msg("[TEST] Relay server accepting WebSocket connection")
52
- stream, _, err := utils.UpgradeToWSStream(w, r, nil)
53
- if err != nil {
54
- log.Error().Err(err).Msg("[TEST] Failed to upgrade WebSocket")
55
- return
56
- }
57
- if err := relayServer.HandleConnection(stream); err != nil {
58
- log.Error().Err(err).Msg("[TEST] Relay server error handling connection")
59
- }
60
- })
61
-
62
- relayHTTPServer := &http.Server{
63
- Addr: relayAddr,
64
- Handler: relayMux,
65
- }
66
-
67
- go func() {
68
- log.Info().Str("addr", relayAddr).Msg("[TEST] Relay HTTP server starting")
69
- if err := relayHTTPServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
70
- log.Error().Err(err).Msg("[TEST] Relay HTTP server error")
71
- }
72
- }()
73
- defer func() {
74
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
75
- defer cancel()
76
- relayHTTPServer.Shutdown(ctx)
77
- }()
78
-
79
- // Wait for relay server to start
80
- time.Sleep(500 * time.Millisecond)
81
- log.Info().Msg("[TEST] Relay server started")
82
-
83
- // 3. Create app credential and SDK client
84
- log.Info().Msg("[TEST] Step 3: Creating app (listener) credential")
85
- appCred := NewCredential()
86
- log.Debug().Str("app_id", appCred.ID()).Msg("[TEST] App credential created")
87
-
88
- // 4. Create app SDK client and register listener
89
- log.Info().Msg("[TEST] Step 4: Creating app SDK client")
90
- appClient, err := NewClient(func(c *ClientConfig) {
91
- c.BootstrapServers = []string{"ws://127.0.0.1:14017/relay"}
92
- })
93
- require.NoError(t, err, "Failed to create app SDK client")
94
- defer appClient.Close()
95
- log.Info().Msg("[TEST] App SDK client created")
96
-
97
- // 5. Register listener on app side
98
- log.Info().Msg("[TEST] Step 5: Registering app listener")
99
- appListener, err := appClient.Listen(appCred, "test-app", []string{"http/1.1"})
100
- require.NoError(t, err, "Failed to create app listener")
101
- defer appListener.Close()
102
- log.Info().Str("lease_id", appCred.ID()).Msg("[TEST] App listener registered")
103
-
104
- // 6. Start serving HTTP on app listener
105
- log.Info().Msg("[TEST] Step 6: Starting HTTP server on app listener")
106
- appMux := http.NewServeMux()
107
- testMessage := "Hello from E2E Test App!"
108
- appMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
109
- log.Debug().
110
- Str("method", r.Method).
111
- Str("path", r.URL.Path).
112
- Str("remote", r.RemoteAddr).
113
- Msg("[TEST] App received HTTP request")
114
- fmt.Fprintf(w, "%s\n", testMessage)
115
- fmt.Fprintf(w, "Request from: %s\n", r.RemoteAddr)
116
- })
117
-
118
- appErrChan := make(chan error, 1)
119
- go func() {
120
- log.Info().Msg("[TEST] App HTTP server starting on listener")
121
- appErrChan <- http.Serve(appListener, appMux)
122
- }()
123
-
124
- // Wait for app to be ready
125
- time.Sleep(1 * time.Second)
126
- log.Info().Msg("[TEST] App is ready to accept connections")
127
-
128
- // 7. Create client credential
129
- log.Info().Msg("[TEST] Step 7: Creating client credential")
130
- clientCred := NewCredential()
131
- log.Debug().Str("client_id", clientCred.ID()).Msg("[TEST] Client credential created")
132
-
133
- // 8. Create client SDK client
134
- log.Info().Msg("[TEST] Step 8: Creating client SDK client")
135
- clientSDK, err := NewClient(func(c *ClientConfig) {
136
- c.BootstrapServers = []string{"ws://127.0.0.1:14017/relay"}
137
- })
138
- require.NoError(t, err, "Failed to create client SDK")
139
- defer clientSDK.Close()
140
- log.Info().Msg("[TEST] Client SDK client created")
141
-
142
- // 9. Wait for lease to be fully registered
143
- log.Info().Msg("[TEST] Step 9: Waiting for lease propagation")
144
- time.Sleep(2 * time.Second)
145
-
146
- // 10. Dial to app through relay
147
- log.Info().Msg("[TEST] Step 10: Client dialing to app through relay")
148
- conn, err := clientSDK.Dial(clientCred, appCred.ID(), "http/1.1")
149
- require.NoError(t, err, "Failed to dial to app")
150
- defer conn.Close()
151
- log.Info().
152
- Str("local", conn.LocalAddr().String()).
153
- Str("remote", conn.RemoteAddr().String()).
154
- Msg("[TEST] Connection established")
155
-
156
- // 11. Send HTTP request through the connection
157
- log.Info().Msg("[TEST] Step 11: Sending HTTP request through connection")
158
-
159
- // Create HTTP request
160
- req, err := http.NewRequest("GET", "http://test-app/", nil)
161
- require.NoError(t, err, "Failed to create HTTP request")
162
-
163
- // Write HTTP request to connection
164
- if err := req.Write(conn); err != nil {
165
- require.NoError(t, err, "Failed to write HTTP request")
166
- }
167
- log.Debug().Msg("[TEST] HTTP request sent")
168
-
169
- // Read HTTP response
170
- log.Info().Msg("[TEST] Step 12: Reading HTTP response")
171
- resp, err := http.ReadResponse(bufio.NewReader(conn), req)
172
- require.NoError(t, err, "Failed to read HTTP response")
173
- defer resp.Body.Close()
174
-
175
- log.Debug().
176
- Int("status_code", resp.StatusCode).
177
- Str("status", resp.Status).
178
- Msg("[TEST] HTTP response received")
179
-
180
- // Read response body
181
- body, err := io.ReadAll(resp.Body)
182
- require.NoError(t, err, "Failed to read response body")
183
-
184
- responseStr := string(body)
185
- log.Info().Str("body", responseStr).Msg("[TEST] Response body received")
186
-
187
- // 12. Verify response
188
- log.Info().Msg("[TEST] Step 13: Verifying response")
189
- require.Equal(t, http.StatusOK, resp.StatusCode, "Expected status code 200")
190
- require.NotEmpty(t, body, "Expected non-empty response body")
191
-
192
- // Check if response contains test message
193
- bodyStr := string(body)
194
- require.NotEmpty(t, bodyStr, "Response body is empty")
195
- log.Info().Str("response", bodyStr).Msg("[TEST] Response verification successful")
196
-
197
- log.Info().Msg("=== E2E Test Completed Successfully ===")
198
-}
199
-
200
-// TestE2E_MultipleConnections tests multiple concurrent connections
201
-func TestE2E_MultipleConnections(t *testing.T) {
202
- log.Info().Msg("=== Starting Multiple Connections Test ===")
203
-
204
- // Setup relay server
205
- relayServerCred, err := cryptoops.NewCredential()
206
- require.NoError(t, err, "Failed to create relay server credential")
207
-
208
- relayServer := portal.NewRelayServer(relayServerCred, []string{"ws://127.0.0.1:14018/relay"})
209
- relayServer.Start()
210
- defer relayServer.Stop()
211
-
212
- relayAddr := "127.0.0.1:14018"
213
- relayMux := http.NewServeMux()
214
- relayMux.HandleFunc("/relay", func(w http.ResponseWriter, r *http.Request) {
215
- stream, _, err := utils.UpgradeToWSStream(w, r, nil)
216
- if err != nil {
217
- return
218
- }
219
- relayServer.HandleConnection(stream)
220
- })
221
-
222
- relayHTTPServer := &http.Server{
223
- Addr: relayAddr,
224
- Handler: relayMux,
225
- }
226
-
227
- go relayHTTPServer.ListenAndServe()
228
- defer func() {
229
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
230
- defer cancel()
231
- relayHTTPServer.Shutdown(ctx)
232
- }()
233
-
234
- time.Sleep(500 * time.Millisecond)
235
-
236
- // Setup app
237
- appCred := NewCredential()
238
-
239
- appClient, err := NewClient(func(c *ClientConfig) {
240
- c.BootstrapServers = []string{"ws://127.0.0.1:14018/relay"}
241
- })
242
- require.NoError(t, err, "Failed to create app SDK client")
243
- defer appClient.Close()
244
-
245
- appListener, err := appClient.Listen(appCred, "multi-test-app", []string{"http/1.1"})
246
- require.NoError(t, err, "Failed to create app listener")
247
- defer appListener.Close()
248
-
249
- // Serve echo server
250
- go func() {
251
- for {
252
- conn, err := appListener.Accept()
253
- if err != nil {
254
- return
255
- }
256
- go func(c net.Conn) {
257
- defer c.Close()
258
- io.Copy(c, c) // Echo back
259
- }(conn)
260
- }
261
- }()
262
-
263
- time.Sleep(1 * time.Second)
264
-
265
- // Create client
266
- clientCred := NewCredential()
267
-
268
- clientSDK, err := NewClient(func(c *ClientConfig) {
269
- c.BootstrapServers = []string{"ws://127.0.0.1:14018/relay"}
270
- })
271
- require.NoError(t, err, "Failed to create client SDK")
272
- defer clientSDK.Close()
273
-
274
- time.Sleep(2 * time.Second)
275
-
276
- // Test multiple concurrent connections
277
- numConnections := 5
278
- log.Info().Int("count", numConnections).Msg("[TEST] Testing multiple concurrent connections")
279
-
280
- for i := range numConnections {
281
-
282
- go func() {
283
- log.Debug().Int("conn_num", i).Msg("[TEST] Starting connection")
284
-
285
- conn, err := clientSDK.Dial(clientCred, appCred.ID(), "http/1.1")
286
- if !assert.NoError(t, err, "Connection %d failed to dial", i) {
287
- return
288
- }
289
- defer conn.Close()
290
-
291
- testData := fmt.Sprintf("test-message-%d", i)
292
-
293
- // Write test data
294
- _, err = conn.Write([]byte(testData))
295
- if !assert.NoError(t, err, "Connection %d failed to write", i) {
296
- return
297
- }
298
-
299
- // Read echoed data
300
- buf := make([]byte, len(testData))
301
- _, err = io.ReadFull(conn, buf)
302
- if !assert.NoError(t, err, "Connection %d failed to read", i) {
303
- return
304
- }
305
-
306
- if !assert.Equal(t, testData, string(buf), "Connection %d: unexpected echo data", i) {
307
- return
308
- }
309
-
310
- log.Debug().Int("conn_num", i).Msg("[TEST] Connection successful")
311
- }()
312
- }
313
-
314
- time.Sleep(5 * time.Second)
315
- log.Info().Msg("=== Multiple Connections Test Completed ===")
316
-}
317
-
318
-// TestE2E_ConnectionTimeout tests timeout scenarios
319
-func TestE2E_ConnectionTimeout(t *testing.T) {
320
- log.Info().Msg("=== Starting Connection Timeout Test ===")
321
-
322
- // Create client with non-existent relay
323
- clientCred := NewCredential()
324
-
325
- // This should fail or timeout appropriately
326
- ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
327
- defer cancel()
328
-
329
- done := make(chan error, 1)
330
- go func() {
331
- _, err := NewClient(func(c *ClientConfig) {
332
- c.BootstrapServers = []string{"ws://127.0.0.1:19999/relay"} // Non-existent
333
- })
334
- done <- err
335
- }()
336
-
337
- select {
338
- case err := <-done:
339
- require.Error(t, err, "Expected error when connecting to non-existent relay")
340
- log.Info().Err(err).Msg("[TEST] Got expected error")
341
- case <-ctx.Done():
342
- require.Fail(t, "Connection attempt did not complete within timeout")
343
- }
344
-
345
- // Try to dial to non-existent lease
346
- relayServerCred := NewCredential()
347
-
348
- relayServer := portal.NewRelayServer(relayServerCred, []string{"ws://127.0.0.1:14019/relay"})
349
- relayServer.Start()
350
- defer relayServer.Stop()
351
-
352
- relayAddr := "127.0.0.1:14019"
353
- relayMux := http.NewServeMux()
354
- relayMux.HandleFunc("/relay", func(w http.ResponseWriter, r *http.Request) {
355
- stream, _, err := utils.UpgradeToWSStream(w, r, nil)
356
- if err != nil {
357
- return
358
- }
359
- relayServer.HandleConnection(stream)
360
- })
361
-
362
- relayHTTPServer := &http.Server{
363
- Addr: relayAddr,
364
- Handler: relayMux,
365
- }
366
-
367
- go relayHTTPServer.ListenAndServe()
368
- defer func() {
369
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
370
- defer cancel()
371
- relayHTTPServer.Shutdown(ctx)
372
- }()
373
-
374
- time.Sleep(500 * time.Millisecond)
375
-
376
- clientSDK, err := NewClient(func(c *ClientConfig) {
377
- c.BootstrapServers = []string{"ws://127.0.0.1:14019/relay"}
378
- })
379
- require.NoError(t, err, "Failed to create client SDK")
380
- defer clientSDK.Close()
381
-
382
- time.Sleep(1 * time.Second)
383
-
384
- // Try to dial to non-existent lease
385
- log.Info().Msg("[TEST] Attempting to dial non-existent lease")
386
- _, err = clientSDK.Dial(clientCred, "non-existent-lease-id", "http/1.1")
387
- require.Error(t, err, "Expected error when dialing non-existent lease")
388
- log.Info().Err(err).Msg("[TEST] Got expected error for non-existent lease")
389
-
390
- log.Info().Msg("=== Connection Timeout Test Completed ===")
391
-}
sdk/types.go
+52
@@ -24,6 +24,17 @@ type ClientConfig struct {
24
HealthCheckInterval time.Duration // Interval for health checks (default: 10 seconds)
25
ReconnectMaxRetries int // Maximum reconnection attempts (default: 0 = infinite)
26
ReconnectInterval time.Duration // Interval between reconnection attempts (default: 5 seconds)
27
+ ReverseWorkers int // Number of reverse websocket workers per listener (default: 2)
28
+ ReverseDialTimeout time.Duration // Reverse websocket dial timeout (default: 5 seconds)
29
+
30
+ // TLS configuration for tunnel server mode
31
+ TLSEnabled bool // Enable TLS listener
32
+ TLSDomain string // Domain for TLS certificate
33
+ TLSCert string // Path to TLS certificate file (optional)
34
+ TLSKey string // Path to TLS key file (optional)
35
+ TLSAutocert bool // Use Let's Encrypt autocert
36
+ TLSListenAddr string // Listen address for TLS (default: ":443")
37
+ TLSAutocertDir string // Directory for autocert cache
38
}
39
40
type ClientOption func(*ClientConfig)
@@ -58,6 +69,47 @@ func WithReconnectInterval(interval time.Duration) ClientOption {
69
}
70
}
71
72
+func WithReverseWorkers(workers int) ClientOption {
73
+ return func(c *ClientConfig) {
74
+ c.ReverseWorkers = workers
75
+ }
76
+}
77
+
78
+func WithReverseDialTimeout(timeout time.Duration) ClientOption {
79
+ return func(c *ClientConfig) {
80
+ c.ReverseDialTimeout = timeout
81
+ }
82
+}
83
+
84
+// TLS configuration options
85
+func WithTLS(domain string) ClientOption {
86
+ return func(c *ClientConfig) {
87
+ c.TLSEnabled = true
88
+ c.TLSDomain = domain
89
+ c.TLSAutocert = true
90
+ }
91
+}
92
+
93
+func WithTLSCert(certPath, keyPath string) ClientOption {
94
+ return func(c *ClientConfig) {
95
+ c.TLSCert = certPath
96
+ c.TLSKey = keyPath
97
+ c.TLSAutocert = false
98
+ }
99
+}
100
+
101
+func WithTLSListenAddr(addr string) ClientOption {
102
+ return func(c *ClientConfig) {
103
+ c.TLSListenAddr = addr
104
+ }
105
+}
106
+
107
+func WithTLSAutocertDir(dir string) ClientOption {
108
+ return func(c *ClientConfig) {
109
+ c.TLSAutocertDir = dir
110
+ }
111
+}
112
+
113
type Metadata struct {
114
Description string `json:"description"`
115
Tags []string `json:"tags"`
utils/utils_test.go
-165
@@ -1,13 +1,11 @@
1
package utils
2
3
import (
4
- "context"
4
"net/http/httptest"
5
"strings"
6
"testing"
7
8
"github.com/stretchr/testify/assert"
10
- "github.com/stretchr/testify/require"
9
)
10
11
func TestIsURLSafeName(t *testing.T) {
@@ -236,8 +234,6 @@ func TestIsSubdomain(t *testing.T) {
234
}
235
}
236
239
-// Tests for http.go functions
240
-
237
func TestIsHTMLContentType(t *testing.T) {
238
tests := []struct {
239
name string
@@ -471,164 +467,3 @@ func TestDefaultBootstrapFrom(t *testing.T) {
467
})
468
}
469
}
474
-
475
-// Tests for ws.go functions
476
-
477
-func TestNewWebSocketDialer(t *testing.T) {
478
- ctx := context.Background()
479
- dialer := NewWebSocketDialer()
480
-
481
- // Test with invalid URL - should error
482
- _, err := dialer(ctx, "not-a-url")
483
- assert.Error(t, err)
484
-
485
- // Test with unreachable server - should error
486
- _, err = dialer(ctx, "ws://localhost:9999/unreachable")
487
- assert.Error(t, err)
488
-}
489
-
490
-func TestUpgradeWebSocket(t *testing.T) {
491
- tests := []struct {
492
- name string
493
- requestHeaders map[string]string
494
- expectError bool
495
- }{
496
- {
497
- name: "valid websocket upgrade request",
498
- requestHeaders: map[string]string{
499
- "Connection": "Upgrade",
500
- "Upgrade": "websocket",
501
- "Sec-WebSocket-Version": "13",
502
- "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
503
- },
504
- expectError: false,
505
- },
506
- {
507
- name: "missing upgrade header",
508
- requestHeaders: map[string]string{
509
- "Connection": "Upgrade",
510
- },
511
- expectError: true,
512
- },
513
- {
514
- name: "no headers",
515
- requestHeaders: map[string]string{},
516
- expectError: true,
517
- },
518
- }
519
-
520
- for _, tt := range tests {
521
- t.Run(tt.name, func(t *testing.T) {
522
- req := httptest.NewRequest("GET", "/", nil)
523
- for k, v := range tt.requestHeaders {
524
- req.Header.Set(k, v)
525
- }
526
-
527
- w := httptest.NewRecorder()
528
-
529
- conn, err := UpgradeWebSocket(w, req, nil)
530
-
531
- if tt.expectError {
532
- assert.Error(t, err)
533
- assert.Nil(t, conn)
534
- } else {
535
- // If no error, we should get a connection
536
- // Note: The response might have been written already
537
- if err == nil {
538
- assert.NotNil(t, conn)
539
- conn.Close()
540
- } else {
541
- // Some error cases are acceptable in test environment
542
- assert.NotNil(t, err)
543
- }
544
- }
545
- })
546
- }
547
-}
548
-
549
-func TestUpgradeToWSStream(t *testing.T) {
550
- tests := []struct {
551
- name string
552
- requestHeaders map[string]string
553
- }{
554
- {
555
- name: "valid websocket upgrade request",
556
- requestHeaders: map[string]string{
557
- "Connection": "Upgrade",
558
- "Upgrade": "websocket",
559
- "Sec-WebSocket-Version": "13",
560
- "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
561
- },
562
- },
563
- {
564
- name: "missing connection header",
565
- requestHeaders: map[string]string{
566
- "Upgrade": "websocket",
567
- },
568
- },
569
- }
570
-
571
- for _, tt := range tests {
572
- t.Run(tt.name, func(t *testing.T) {
573
- req := httptest.NewRequest("GET", "/", nil)
574
- for k, v := range tt.requestHeaders {
575
- req.Header.Set(k, v)
576
- }
577
-
578
- w := httptest.NewRecorder()
579
-
580
- stream, conn, err := UpgradeToWSStream(w, req, nil)
581
-
582
- // Check return values
583
- if tt.requestHeaders["Connection"] == "Upgrade" && tt.requestHeaders["Upgrade"] == "websocket" {
584
- // Valid upgrade request
585
- if err == nil {
586
- require.NotNil(t, stream, "stream should not be nil on success")
587
- require.NotNil(t, conn, "conn should not be nil on success")
588
- conn.Close()
589
- }
590
- // Note: In test environment, upgrade might fail for various reasons
591
- // The important thing is the function doesn't panic
592
- } else {
593
- // Invalid request should error
594
- if err == nil {
595
- require.NotNil(t, stream)
596
- require.NotNil(t, conn)
597
- conn.Close()
598
- } else {
599
- assert.Nil(t, stream)
600
- assert.Nil(t, conn)
601
- }
602
- }
603
- })
604
- }
605
-}
606
-
607
-// Additional edge case tests for improved coverage
608
-
609
-func TestStripPort_EdgeCases(t *testing.T) {
610
- tests := []struct {
611
- name string
612
- input string
613
- expected string
614
- }{
615
- {"empty string", "", ""},
616
- {"no colon", "example.com", "example.com"},
617
- {"colon at end", "example.com:", "example.com:"},
618
- {"colon no port but path", "example.com:/path", "example.com:/path"},
619
- {"non-digit port", "example.com:abc", "example.com:abc"},
620
- {"mixed port", "example.com:12a34", "example.com:12a34"},
621
- {"multiple colons - last not all digits", "example.com:8080:extra", "example.com:8080:extra"},
622
- {"IPv6 with port", "[::1]:8080", "[::1]"},
623
- {"IPv6 no port", "[::1]", "[::1]"},
624
- {"just colon", ":", ":"},
625
- {"just digits after colon", ":8080", ""},
626
- }
627
-
628
- for _, tt := range tests {
629
- t.Run(tt.name, func(t *testing.T) {
630
- result := StripPort(tt.input)
631
- assert.Equal(t, tt.expected, result, "StripPort(%q)", tt.input)
632
- })
633
- }
634
-}
utils/ws.go
deleted
-46
@@ -1,46 +0,0 @@
1
-package utils
2
-
3
-import (
4
- "context"
5
- "io"
6
- "net/http"
7
-
8
- "github.com/gorilla/websocket"
9
-
10
- "gosuda.org/portal/portal/utils/wsstream"
11
-)
12
-
13
-// NewWebSocketDialer returns a dialer that establishes WebSocket connections
14
-// and wraps them as io.ReadWriteCloser.
15
-func NewWebSocketDialer() func(context.Context, string) (io.ReadWriteCloser, error) {
16
- return func(ctx context.Context, url string) (io.ReadWriteCloser, error) {
17
- wsConn, resp, err := websocket.DefaultDialer.Dial(url, nil)
18
- if err != nil {
19
- if resp != nil {
20
- resp.Body.Close()
21
- }
22
- return nil, err
23
- }
24
- // Response body is closed by the Dialer on successful connection
25
- return wsstream.New(wsConn), nil
26
- }
27
-}
28
-
29
-// defaultWebSocketUpgrader provides a permissive upgrader used across cmd binaries
30
-var defaultWebSocketUpgrader = websocket.Upgrader{
31
- CheckOrigin: func(r *http.Request) bool { return true },
32
-}
33
-
34
-// UpgradeWebSocket upgrades the request/response to a WebSocket connection using DefaultWebSocketUpgrader
35
-func UpgradeWebSocket(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) {
36
- return defaultWebSocketUpgrader.Upgrade(w, r, responseHeader)
37
-}
38
-
39
-// UpgradeToWSStream upgrades HTTP to WebSocket and wraps it as io.ReadWriteCloser
40
-func UpgradeToWSStream(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (io.ReadWriteCloser, *websocket.Conn, error) {
41
- wsConn, err := UpgradeWebSocket(w, r, responseHeader)
42
- if err != nil {
43
- return nil, nil, err
44
- }
45
- return wsstream.New(wsConn), wsConn, nil
46
-}