Architecture

fernweh is a Rust workspace with a strict separation: all networking lives in one protocol crate, and everything above it is UI or platform shell.

Crate layout

Crate Responsibility

crates/tv-core

Pure protocol library — no Tauri, no UI. Samsung/Roku/LG/PS backends behind one TvBackend trait, the Fire TV (ADB) and Android TV Remote v2 backends, SSDP/DDP/mDNS discovery, the IR code database + renderers, and Wake-on-LAN. The only crate that opens sockets.

crates/tv-mock

In-process mock devices (self-signed TLS WebSocket, HTTP, UDP DDP, ADB, Android TV) for integration tests.

src-tauri

Tauri v2 shell (package fernweh-tauri): async commands, per-device state behind a tokio Mutex, the token store (OS keyring on desktop, file on mobile), and the IR / OCR plugin bridges.

ui

Leptos CSR frontend (Trunk → wasm); dark amber hardware-remote UI. Excluded from the cargo workspace (it targets wasm32-unknown-unknown).

The backend abstraction

Every network device speaks through one trait in crates/tv-core/src/backend.rs: TvBackend, driven by a KeyCommand enum. Each backend maps KeyCommand → a vendor keycode; an unknown mapping returns TvError::Unsupported — it never panics. error.rs defines the thiserror-based TvError. There are no unwrap() / expect() outside tests.

Discovery

A single discover() runs SSDP M-SEARCH (Samsung DIAL, roku:ecp, LG webos-second-screen), DDP for PlayStation, and mDNS (_androidtvremote2._tcp.local.) concurrently, fetches the device-description XML for friendly name / model / MAC, and dedups by IP over a short window. Vendor is confirmed from the description <manufacturer> field, because DIAL is not Samsung-exclusive. See ADR-0006 for how unknown DIAL responders are surfaced rather than dropped.

Backends at a glance

  • Samsungwss://{ip}:8002, self-signed cert accepted (LAN-only), pair → token, ms.remote.control clicks, WoL power-on.

  • Roku — plain HTTP ECP, no auth, POST /keypress/{key}.

  • LG webOSwss://{ip}:3001 (fallback ws://:3000), register → client-key; ssap:// commands plus a pointer-input socket for the d-pad — see ADR-0003.

  • PlayStation — DDP wake / standby (buttons need Remote Play, out of scope).

  • Fire TV — the ADB wire protocol over TCP 5555 — see ADR-0006.

  • Android TV — length-delimited protobuf over TLS 6466/6467, mutual-TLS pairing — see ADR-0007.

  • Universal IR — a code database rendered to carrier timings in tv-core::ir, transmitted by an Android plugin — see ADR-0005.

The rationale for each non-obvious choice is recorded in the Architecture Decisions.