# 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, unpacker, catalog)` boots the asynchronous runtime in the background and returns a `PeerRuntimeHandle` whose sender controls the peer. The injected `Unpacker` keeps archive extraction out of the peer crate's platform layer, and the catalog set gates which local game roots are announced or served. - `PeerCommand` represents the small control surface exposed to the UI layer: `ListGames`, `GetGame`, `FetchLatestFromPeers`, `DownloadGameFiles`, `InstallGame`, `UninstallGame`, `RemoveDownloadedGame`, `CancelDownload`, `SetGameDir`, and `GetPeerCount`. - `PeerEvent` enumerates everything the peer runtime reports back to the UI: library snapshots, download/install/uninstall lifecycle updates, runtime failures, 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 four 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`. 4. **Local game monitor** (`run_local_game_monitor`) – watches the configured game directory and each game root non-recursively, gates per-ID rescans while operations are active, emits local-library changes separately from active operation snapshots, and runs a 300-second fallback scan for missed events. `scan_local_library` maintains a lightweight on-disk index and produces both a `GameDB` and protocol summaries. A game is downloaded only when its root-level `version.ini` sentinel exists; `local/` being a directory is the install signal. ## 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` for a new download, or `PeerCommand::FetchLatestFromPeers` for an update that must bypass local archives. The selected peers are 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` starts a version-sentinel transaction, parks any old `version.ini` as `.version.ini.discarded`, prepares non-sentinel files, 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. `version.ini` chunks are buffered in memory and committed last via `.version.ini.tmp` followed by an atomic rename. Failures are accumulated and retried (up to `MAX_RETRY_COUNT`) via `retry_failed_chunks`; failed or cancelled downloads sweep `.version.ini.tmp` and `.version.ini.discarded` without restoring the previous sentinel. 6. After a successful sentinel commit, `PeerEvent::DownloadGameFilesFinished` is emitted and the peer auto-runs the install transaction. `PeerCommand::CancelDownload` cancels the tracked download token for an active transfer. The transfer task remains responsible for clearing `active_operations`, so the UI continues to treat active-operation snapshots as the single source of truth for whether a download is still running. ### Install Transactions Install, update, uninstall, downloaded-file removal, and startup recovery live under `src/install/`. Each game root has an atomic `.lanspread.json` intent log for install-side operations and uses Lanspread-owned `.local.installing/` and `.local.backup/` directories marked by `.lanspread_owned`. Startup recovery combines the recorded intent with the observed filesystem state and only deletes reserved directories when intent or marker ownership proves they belong to Lanspread. Downloaded-file removal is deliberately separate from uninstall: it only accepts catalog IDs that are direct children of the configured game directory, refuses installed or in-flight roots, and deletes the whole game root only after finding a regular root-level `version.ini` sentinel. ## 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 operation state, the catalog set, and the user-selected game directory. - The Tauri commands (`request_games`, `install_game`, `update_game`, `remove_downloaded_game`, and `update_game_directory`) translate UI actions into `PeerCommand`s. In particular, `update_game_directory` validates the filesystem path before storing it, loads the bundled catalog on first use, kicks off the peer runtime on demand, 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-*`, `game-install-*`, `game-uninstall-*`, `peer-*`). The Tauri crate now only provides the unrar sidecar through the injected `Unpacker`; rollback and cleanup live in the peer transaction code. ## 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 open files without truncating existing data, and root-level `version.ini` is written only after the rest of the download has succeeded. ## 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.