Architecture
How the Qubox components fit together — from capture backend to GPU presentation.
Architecture overview
Qubox is a five-binary, ten-crate Rust workspace. The data plane runs over QUIC; the control plane is JSON over the same QUIC stream; updates are signed with TUF. This page is a high-level map.
The five binaries
| Binary | Where it runs | What it does |
|---|---|---|
qubox-host-agent |
The machine being shared | Captures displays + audio, synthesises input |
qubox (daemon) |
Same as host agent | IPC, identity storage, TUF updates |
qubox-client-cli |
The viewer | Connects, decodes, presents frames on the GPU |
qubox-client-gui |
The viewer | Tauri/React wrapper around the CLI |
qubox-signaling-server |
Optional relay | NAT-traversal + identity pairing across nets |
The wire protocol
We use QUIC with a custom multiplexing layer. One QUIC connection carries multiple substreams:
- Control — bidirectional, JSON-encoded, one per session
- Media video — unidirectional datagrams, Annex-B H.264 / AV1
- Media audio — unidirectional datagrams, Opus
- Input — bidirectional, batched
- Clipboard — bidirectional, image + text
- Pen — bidirectional, high-frequency pointer events
QUIC's built-in TLS 1.3 gives us end-to-end encryption without an extra handshake. 0-RTT is supported for repeat connections.
Capture backends
The host agent picks the right capture backend at runtime:
| OS | Display backend | Audio backend |
|---|---|---|
| Linux | X11, PipeWire (Wayland) | PipeWire, PulseAudio |
| macOS | ScreenCaptureKit | CoreAudio |
| Windows | DXGI | WASAPI |
All backends expose the same qubox_display::DisplayCapture trait, so
the rest of the codebase is portable.
The transport layer
qubox-transport is a thin wrapper over quinn (Rust QUIC). It adds:
- Reconnection with exponential backoff
- Path probing (direct IP → relay → TURN)
- 0-RTT session resumption
- Datagram priorities (input > clipboard > video)
Identity and pairing
Each installation generates an Ed25519 keypair at first run. Pairing exchanges the public key plus a shared 6-word phrase derived from BIP-39. The shared phrase is the only authentication needed to add a device — no central authority is involved.
Pairings are stored locally in ~/.config/qubox/pairings.json and
never leave your machines.
Updates via TUF
We use The Update Framework for release distribution. The daemon verifies the metadata chain before applying any update:
- Pulls
root.json(pinned at first install) - Verifies
targets.jsonagainstroot.json - Verifies
timestamp.jsonagainstsnapshot.json - Verifies
snapshot.jsonagainstroot.json - Downloads the binary, verifies its hash against
targets.json, verifies its signature against the targets key
If any step fails, the update is rejected and the previous version keeps
running. Rollback to any prior version is one command:
qubox update --rollback.
Why QUIC, not WebRTC?
Both work. We chose QUIC because:
- Single stack — same Rust library for client, server, daemon, and CLI
- 0-RTT — sub-100ms resumption on flaky networks
- Datagrams — first-class support for unreliable, ordered or unordered data channels without an extra RTP layer
- No browser requirement — the relay doesn't need to be served over HTTPS; it's a pure binary protocol
The downside is no browser fallback. If you need a browser client, check back in 6 months — we're prototyping a WebTransport gateway.
What we'd do differently
We're honest about the trade-offs. The biggest one is no WebRTC fallback: if your network blocks UDP, you need a TCP-friendly workaround. The TURN fallback exists for that, but it adds latency.
The second is single-tenant relay: the signaling server is one process per deployment. A multi-tenant cloud relay with isolation boundaries is on the Team-tier roadmap.