Architecture
jarvis-rs/crates/jarvis-relay/src/main.rsjarvis-rs/crates/jarvis-relay/src/session.rsjarvis-rs/crates/jarvis-relay/src/protocol.rsjarvis-rs/crates/jarvis-app/src/app_state/ws_server/
The relay is a thin message forwarder that never inspects payload content. All PTY data is end-to-end encrypted between desktop and mobile endpoints.
Relay Server
The relay server (jarvis-relay) is a standalone Tokio application that:
- Listens for TCP connections on a configurable port (default 8080)
- Accepts WebSocket upgrades
- Pairs connections by session ID (one desktop + one mobile per session)
- Forwards text frames bidirectionally between paired peers
Installation
CLI Arguments
Hello Protocol
The first message from each client identifies its role:Mobile Hello
Mobile clients join existing sessions:Response:The desktop also receives
{"type": "peer_connected"}.Session Lifecycle
Session Store
TheSessionStore (session.rs) maps session IDs to Session structs containing optional mpsc::Sender<String> handles for each role:
session_ttl (default 300 seconds) that have no mobile peer.
Rate Limiting
TheRateLimiter (rate_limit.rs) enforces connection limits to prevent resource exhaustion:
| Parameter | Default | Description |
|---|---|---|
max_connections_per_ip | 10 | Concurrent WebSocket connections per IP |
max_connect_rate_per_ip | 20 | New connections per IP per 60s window |
max_total_sessions | 1000 | Global session cap |
max_session_id_len | 64 | Maximum session ID length (bytes) |
Desktop Relay Client
The desktop side connects outbound to the relay server.Startup Sequence
Load Session ID
Session ID is loaded from disk (
~/.config/jarvis/relay_session_id) or generated as a 32-character alphanumeric string and persisted.Create Broadcaster
A
MobileBroadcaster (tokio broadcast::channel, capacity 256) is created for PTY output fan-out.Connect to Relay
The relay client task connects to the configured relay URL, sends
desktop_hello, and waits for session_ready.Message Flow: Desktop to Mobile
Message Flow: Mobile to Desktop
Wire Protocol (Inner)
Messages between desktop and mobile (inside the relay envelope):Server (Desktop) to Client (Mobile)
| Type | Fields |
|---|---|
pty_output | pane_id: u32, data: String |
pty_exit | pane_id: u32, code: u32 |
pane_list | panes: [PaneInfo], focused_id |
Client (Mobile) to Server (Desktop)
| Type | Fields |
|---|---|
pty_input | pane_id: u32, data: String |
pty_resize | pane_id: u32, cols: u16, rows: u16 |
ping | (empty) |
Relay Envelope
All messages between desktop and mobile are wrapped in a relay envelope:KeyExchange: Carries the DH public key (SPKI DER, base64) for ECDHEncrypted: AES-256-GCM ciphertext with base64-encoded IV and ciphertextPlaintext: Only accepted before encryption is established; rejected after cipher is active to prevent downgrade attacks
Once a cipher is established,
Plaintext envelopes are rejected to prevent relay-initiated downgrade attacks.Mobile Client
The mobile client is a React Native app (jarvis-mobile/) using TypeScript.
Key files:
lib/relay-connection.ts- WebSocket client implementationlib/crypto.ts- ECDH P-256 + AES-256-GCM encryptioncomponents/CodeTerminal.tsx- Terminal UI
Connection Flow
Key Exchange
ECDH key exchange establishes an AES-256-GCM cipher. See Mobile Pairing for details.
Status Tracking
Configuration
Fromjarvis-rs/crates/jarvis-config/src/schema/relay.rs:
Security
End-to-End Encryption
All PTY data is encrypted with AES-256-GCM using a shared key derived via ECDH between desktop and mobile. The relay server sees only opaque ciphertext.Downgrade Protection
- After a cipher is established,
Plaintextenvelopes are rejected PeerDisconnectedfrom the relay does not clear the cipher (prevents relay-in-the-middle downgrade attacks)- Cipher is only cleared on explicit pairing revocation
Rate Limiting
Per-IP connection limits and global session caps prevent resource exhaustion attacks.Session ID Security
Session IDs are 32-character alphanumeric strings (192 bits of entropy when randomly generated). They are:- Persisted to disk with restricted permissions (
0600on Unix) - Never logged or transmitted in plaintext (only over TLS WebSocket)
- Invalidated when pairing is revoked
Deployment
The relay server is designed to be deployed as a standalone service:Environment Variables
| Variable | Description | Default |
|---|---|---|
RUST_LOG | Logging level | jarvis_relay=info |
PORT | Port to listen on | 8080 |