# Spike: RDP Frame Transport Mechanisms **Status:** Research-based (not yet benchmarked) **Date:** 2026-03-17 **Target platform:** Windows (developing on macOS) **Wails version:** v3.0.0-alpha.74 --- ## Context When Wraith connects to a remote desktop via FreeRDP, the Go backend receives raw bitmap frames that must be delivered to the frontend for rendering on an HTML ``. This spike evaluates three transport approaches, estimating throughput for a 1920x1080 session at 30 fps. --- ## Approach 1: Local HTTP Endpoint ### How it works 1. Go spins up a local HTTP server on a random high port (`net.Listen("tcp", "127.0.0.1:0")`). 2. Each frame is JPEG-encoded and served at a predictable URL (e.g., `http://127.0.0.1:{port}/frame?session=3`). 3. The frontend fetches frames via `fetch()`, `` tag, or `ReadableStream` for chunked delivery. ### Throughput estimate | Metric | Value | |--------|-------| | 1080p RGBA raw | ~8 MB/frame | | 1080p JPEG (quality 80) | ~100-200 KB/frame | | At 30 fps (JPEG) | ~3-6 MB/s | | Loopback bandwidth | >1 GB/s | Loopback HTTP can handle this with headroom to spare. ### Pros - No base64 overhead — binary JPEG bytes transfer directly. - Standard HTTP semantics; easy to debug with browser DevTools. - Can use `Transfer-Encoding: chunked` or Server-Sent Events for push-based delivery. - Can serve multiple sessions on the same server with different paths. ### Cons - Requires an extra listening port on localhost. - Potential firewall or endpoint-security issues on locked-down Windows enterprise machines. - Slightly more complex setup (port allocation, CORS headers for Wails webview origin). --- ## Approach 2: Wails Bindings (Base64) ### How it works 1. Go encodes each frame as a JPEG, then base64-encodes the result. 2. A Wails-bound method (`SessionService.GetFrame(sessionID)`) returns the base64 string. 3. The frontend decodes the string, creates an `ImageBitmap` or sets it as a data URI, and draws it on a ``. ### Throughput estimate | Metric | Value | |--------|-------| | 1080p JPEG (quality 80) | ~100-200 KB/frame | | Base64 of JPEG (+33%) | ~133-270 KB/frame | | At 30 fps | ~4-8 MB/s of string data | | Wails IPC overhead | Negligible for this payload size | This is feasible. Modern JavaScript engines handle base64 decoding at several hundred MB/s. ### Pros - No extra ports — everything flows through the existing Wails IPC channel. - Works out of the box with Wails bindings; no additional infrastructure. - No firewall concerns. ### Cons - 33% base64 size overhead on every frame. - CPU cost of `base64.StdEncoding.EncodeToString()` in Go and `atob()` in JS on every frame (though both are fast). - Polling-based unless combined with Wails events to signal frame availability. - May bottleneck at very high resolutions (4K) or high FPS (60+). --- ## Approach 3: Wails Events (Streaming) ### How it works 1. Go emits each frame as a Wails event: `app.EmitEvent("frame:3", base64JpegString)`. 2. The frontend subscribes: `wails.Events.On("frame:3", handler)`. 3. The handler decodes and renders on canvas. ### Throughput estimate Same as Approach 2 — the payload is identical (base64 JPEG). The difference is delivery mechanism (push vs. pull). ### Pros - Push-based — the frontend receives frames as soon as they are available with no polling delay. - Natural Wails pattern; aligns with how other real-time data (connection status, notifications) already flows. ### Cons - Same 33% base64 overhead as Approach 2. - Wails event bus may not be optimised for high-frequency, large-payload events. This is unvalidated. - Harder to apply backpressure — if the frontend cannot keep up, events queue without flow control. --- ## Throughput Summary | Approach | Payload/frame | 30 fps throughput | Extra infra | |----------|--------------|-------------------|-------------| | 1 — Local HTTP | ~150 KB (binary JPEG) | ~4.5 MB/s | Localhost port | | 2 — Wails bindings | ~200 KB (base64 JPEG) | ~6 MB/s | None | | 3 — Wails events | ~200 KB (base64 JPEG) | ~6 MB/s | None | All three approaches are within comfortable limits for 1080p at 30 fps. The differentiator is operational simplicity, not raw throughput. --- ## Recommendation **Start with Approach 2 (base64 JPEG via Wails bindings).** Rationale: 1. JPEG compression brings 1080p frames down to ~200 KB, making the 33% base64 overhead manageable (~6 MB/s at 30 fps). 2. No extra ports or firewall concerns — important for enterprise Windows environments where Wraith will be deployed. 3. Simple implementation: one Go method, one frontend call per frame. 4. If polling latency is a problem, upgrade to Approach 3 (events) with minimal code change — the payload encoding is identical. **If benchmarking reveals issues** (dropped frames, high CPU from encoding), fall back to Approach 1 (local HTTP) which eliminates base64 overhead entirely. The migration path is straightforward: replace the `fetch(dataUri)` call with `fetch(httpUrl)`. --- ## Next Step Benchmark during Phase 3 when FreeRDP integration is in progress and real frame data is available. Key metrics to capture: - End-to-end frame latency (Go encode to canvas paint) - CPU utilisation on both Go and browser sides - Frame drop rate at 30 fps and 60 fps - Memory pressure from base64 string allocation/GC