Files
lanspread/crates/lanspread-peer/README.md
T

115 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.