115 lines
6.0 KiB
Markdown
115 lines
6.0 KiB
Markdown
# lanspread-peer
|
||
|
||
`lanspread-peer` is the networking runtime that lets Lanspread nodes find each
|
||
other on the local network, exchange library metadata, and transfer game files.
|
||
It is designed to run headless – other crates (most notably
|
||
`lanspread-tauri-deno-ts`) embed it and drive it through a channel-based API.
|
||
|
||
## Runtime Overview
|
||
|
||
- `start_peer(game_dir, tx_events, peer_game_db)` boots the asynchronous runtime in the
|
||
background and returns an `UnboundedSender<PeerCommand>` that the caller uses
|
||
for control. The function immediately forwards the supplied game directory via
|
||
`PeerCommand::SetGameDir` and keeps using the provided `PeerGameDB` so the UI
|
||
layer can observe live peer metadata.
|
||
- `PeerCommand` represents the small control surface exposed to the UI layer:
|
||
`ListGames`, `GetGame`, `DownloadGameFiles`, and `SetGameDir`.
|
||
- `PeerEvent` enumerates everything the peer runtime reports back to the UI:
|
||
library snapshots, download lifecycle updates, and peer membership changes.
|
||
- `PeerGameDB` collects remote peer metadata. It aggregates discovered peers’
|
||
`Game` definitions, tracks the latest ETI version per title, and keeps the
|
||
last seen list of `GameFileDescription` entries for each peer.
|
||
|
||
Internally the peer runtime owns three long-lived tasks that run for the
|
||
lifetime of the process:
|
||
|
||
1. **Server component** (`run_server_component`) – listens for QUIC connections,
|
||
advertises via mDNS, and serves `Request::ListGames`, `Request::GetGame`,
|
||
`Request::GetGameFileData`, and `Request::GetGameFileChunk` by reading from
|
||
the local game directory.
|
||
2. **Discovery loop** (`run_peer_discovery`) – uses the `lanspread-mdns`
|
||
helper to discover other peers. The blocking mDNS work is executed on a
|
||
dedicated thread via `tokio::task::spawn_blocking` so that the Tokio runtime
|
||
remains responsive.
|
||
3. **Ping service** (`run_ping_service`) – periodically issues QUIC ping requests
|
||
to keep peer liveness up to date and prunes stale entries from `PeerGameDB`.
|
||
|
||
`scan_local_library` maintains a lightweight on-disk index and produces both a
|
||
`GameDB` and protocol summaries. The resulting database is used to respond to
|
||
incoming metadata requests (`Request::ListGames` / `Request::GetGame`).
|
||
|
||
## Networking and File Transfer
|
||
|
||
- Transport is handled by [`s2n-quic`](https://github.com/aws/s2n-quic); TLS
|
||
cert/key material is compiled in from the repository root.
|
||
- Protocol messages are JSON-encoded structures defined in
|
||
`lanspread-proto::{Request, Response}`.
|
||
- File transfers stream raw bytes over dedicated bidirectional QUIC streams.
|
||
`peer::send_game_file_data` sends entire files, while
|
||
`peer::send_game_file_chunk` services ranged requests.
|
||
|
||
### Download Pipeline
|
||
|
||
When the UI asks to download a game:
|
||
|
||
1. The UI first issues `PeerCommand::GetGame`. Each peer that still reports the
|
||
game is queried via `request_game_details_from_peer`, and their file
|
||
manifests are merged inside `PeerGameDB`.
|
||
2. Once the UI receives `PeerEvent::GotGameFiles`, it forwards the selected file
|
||
list back with `PeerCommand::DownloadGameFiles`.
|
||
3. `download_game_files` prepares the filesystem (creating directories and
|
||
pre-sizing files where possible), emits `PeerEvent::DownloadGameFilesBegin`,
|
||
and builds a per-peer plan (`build_peer_plans`) that round-robins file chunks
|
||
across the available peers that advertise the latest version.
|
||
4. Each plan is executed in its own task (`download_from_peer`). Chunk requests
|
||
use per-chunk QUIC streams and write into pre-created files. The chunk writer
|
||
keeps existing data intact and only truncates when we intentionally fall back
|
||
to a full file transfer, which prevents corruption when multiple peers fill
|
||
different regions of the same file.
|
||
5. Failures are accumulated and retried (up to `MAX_RETRY_COUNT`) via
|
||
`retry_failed_chunks`. If everything succeeds,
|
||
`PeerEvent::DownloadGameFilesFinished` is emitted; otherwise the UI receives
|
||
`PeerEvent::DownloadGameFilesFailed`.
|
||
|
||
## Integration with `lanspread-tauri-deno-ts`
|
||
|
||
The Tauri application embeds this crate in
|
||
`crates/lanspread-tauri-deno-ts/src-tauri/src/lib.rs`:
|
||
|
||
- `LanSpreadState` holds onto the peer control channel, the latest aggregated
|
||
`GameDB`, per-game download state, and the user-selected game directory.
|
||
- The Tauri commands (`request_games`, `install_game`, `update_game`, and
|
||
`update_game_directory`) translate UI actions into `PeerCommand`s. In
|
||
particular, `update_game_directory` records the filesystem path, kicks off the
|
||
peer runtime on first use, and mirrors the installed/uninstalled state into
|
||
the UI-facing database.
|
||
- A background task consumes `PeerEvent`s and fans them out to the front-end via
|
||
Tauri publish/subscribe events (`games-list-updated`, `game-download-*`,
|
||
`peer-*`). Successful downloads trigger an `unrar` sidecar to unpack ETI
|
||
archives and clean up the temporary backup folders that are created when
|
||
updates begin.
|
||
- When downloads fail the Tauri layer restores the on-disk backup, keeping the
|
||
previous installation consistent even after partial transfers.
|
||
|
||
## Security & Operational Notes
|
||
|
||
- All QUIC connections are TLS encrypted; the shipped certificates are suitable
|
||
for local-network trust but should be rotated for production deployments.
|
||
- Peer discovery is restricted to the local link via mDNS.
|
||
- Long-running blocking mDNS calls are isolated on dedicated threads which keeps
|
||
the async runtime responsive even when discovery takes a long time.
|
||
- File writes are chunk-safe: partial chunk downloads now open files without
|
||
truncating existing data, avoiding the corruption that occurred previously
|
||
when multiple peers collectively filled a file.
|
||
|
||
## Known Limitations
|
||
|
||
- `PeerGameDB` currently models the latest metadata that other peers advertise.
|
||
If the UI needs to surface titles that only exist locally, additional merging
|
||
with the locally scanned `GameDB` will be required.
|
||
- The download planner uses a simple round-robin and does not yet take per-peer
|
||
throughput or failures into account when distributing work.
|
||
|
||
Refer to the source (particularly `src/lib.rs`) for the exact message shapes and
|
||
state machines.
|