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(&registerReq); 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 -}