diff --git a/crates/lanspread-peer/ARCHITECTURE.md b/crates/lanspread-peer/ARCHITECTURE.md index cb2c65b..548269d 100644 --- a/crates/lanspread-peer/ARCHITECTURE.md +++ b/crates/lanspread-peer/ARCHITECTURE.md @@ -27,7 +27,11 @@ chunked file transfers. When a peer is discovered: 1. Connect and send `Hello { peer_id, proto_ver, library_rev, library_digest, features }`. -2. Receive `HelloAck { peer_id, proto_ver, library_rev, library_digest, features }`. + `Hello` also carries the sender's advertised `listen_addr`; the QUIC source + port is only a temporary transport port and must not be recorded as the + peer's listener. +2. Receive `HelloAck { peer_id, proto_ver, listen_addr, library_rev, + library_digest, features }`. 3. If the remote `peer_id` is already known but the address changed, update it. 4. If protocol versions are incompatible, drop the peer (and keep mDNS watching). 5. If library digests match, do nothing else. @@ -51,12 +55,12 @@ When a peer is discovered: ### Summary and snapshot -- `LibrarySummary { library_rev, library_digest, game_count }` -- `LibrarySnapshot { library_rev, games: Vec }` +- `LibrarySummary { peer_id, summary: { library_rev, library_digest, game_count } }` +- `LibrarySnapshot { peer_id, snapshot: { library_rev, games: Vec } }` ### Delta updates -- `LibraryDelta { from_rev, to_rev, added, updated, removed }` +- `LibraryDelta { peer_id, delta: { from_rev, to_rev, added, updated, removed } }` - `removed` is a list of game IDs. - Deltas are idempotent; ignore if `to_rev` <= known rev. @@ -142,6 +146,8 @@ Most scans become O(number of game dirs), with full recursion only when needed. ## Fault tolerance rules - Every peer is keyed by `peer_id`, not by IP address. +- Peer addresses are listener addresses from mDNS or `Hello`/`HelloAck`, never + ephemeral QUIC source ports. - `library_rev` is monotonic and guards against out-of-order updates. - Any mismatch or missing delta falls back to `LibrarySnapshot`. - Loss of goodbye is harmless; stale timeout is authoritative. @@ -153,8 +159,10 @@ Most scans become O(number of game dirs), with full recursion only when needed. `LibraryDelta`, and optional `Goodbye` messages. - Thread `peer_id`, `library_rev`, and `manifest_hash` through all library and manifest-bearing types. - - Make `HelloAck` carry the remote `library_rev` and `manifest_hash` - so the client can immediately select `LibraryDelta` vs `LibrarySnapshot`. + - Make `Hello` and `HelloAck` carry the sender's `listen_addr`, + `library_rev`, and `library_digest` so both sides can record stable + listener addresses and immediately select `LibraryDelta` vs + `LibrarySnapshot`. 2. Peer identity: - Persist a stable `peer_id` (UUID) in the peer config and inject it into `PeerInfo` and `PeerGameDB` at startup. diff --git a/crates/lanspread-peer/src/handlers.rs b/crates/lanspread-peer/src/handlers.rs index a758d7c..050998e 100644 --- a/crates/lanspread-peer/src/handlers.rs +++ b/crates/lanspread-peer/src/handlers.rs @@ -8,8 +8,7 @@ use std::{ sync::Arc, }; -use lanspread_db::db::{Game, GameDB, GameFileDescription}; -use lanspread_proto::GameSummary; +use lanspread_db::db::{GameDB, GameFileDescription}; use tokio::sync::{RwLock, mpsc::UnboundedSender}; use crate::{ @@ -31,7 +30,7 @@ use crate::{ scan_local_library, version_ini_is_regular_file, }, - network::{request_game_details_from_peer, request_game_list_from_peer, send_library_delta}, + network::{request_game_details_from_peer, send_library_delta}, peer_db::PeerGameDB, remote_peer::ensure_peer_id_for_addr, services::perform_handshake_with_peer, @@ -706,6 +705,7 @@ pub async fn handle_connect_peer_command( ) { log::info!("Direct connect command received for {addr}"); let peer_id = ctx.peer_id.clone(); + let local_peer_addr = ctx.local_peer_addr.clone(); let local_library = ctx.local_library.clone(); let peer_game_db = ctx.peer_game_db.clone(); let tx_notify_ui = tx_notify_ui.clone(); @@ -713,54 +713,20 @@ pub async fn handle_connect_peer_command( ctx.task_tracker.spawn(async move { if let Err(err) = perform_handshake_with_peer( peer_id, + local_peer_addr, local_library, - peer_game_db.clone(), - tx_notify_ui.clone(), + peer_game_db, + tx_notify_ui, addr, None, ) .await { log::warn!("Failed direct connect to {addr}: {err}"); - return; - } - - if let Err(err) = refresh_direct_peer_games(&peer_game_db, &tx_notify_ui, addr).await { - log::warn!("Failed to refresh direct peer games from {addr}: {err}"); } }); } -async fn refresh_direct_peer_games( - peer_game_db: &Arc>, - tx_notify_ui: &UnboundedSender, - addr: SocketAddr, -) -> eyre::Result<()> { - let games = request_game_list_from_peer(addr).await?; - let summaries = games.into_iter().map(game_to_summary).collect::>(); - let peer_id = ensure_peer_id_for_addr(peer_game_db, addr).await; - { - let mut db = peer_game_db.write().await; - db.update_peer_games(&peer_id, summaries); - } - events::emit_peer_game_list(peer_game_db, tx_notify_ui).await; - Ok(()) -} - -fn game_to_summary(game: Game) -> GameSummary { - let availability = game.normalized_availability(); - GameSummary { - id: game.id, - name: game.name, - size: game.size, - downloaded: game.downloaded, - installed: game.installed, - eti_version: game.eti_game_version, - manifest_hash: 0, - availability, - } -} - // ============================================================================= // Game announcement helpers // ============================================================================= @@ -823,8 +789,9 @@ pub async fn update_and_announce_games( for peer_addr in peer_targets { let delta = delta.clone(); + let peer_id = ctx.peer_id.as_ref().clone(); ctx.task_tracker.spawn(async move { - if let Err(e) = send_library_delta(peer_addr, delta).await { + if let Err(e) = send_library_delta(peer_addr, &peer_id, delta).await { log::warn!("Failed to send library delta to {peer_addr}: {e}"); } }); diff --git a/crates/lanspread-peer/src/network.rs b/crates/lanspread-peer/src/network.rs index 0de6324..673bf93 100644 --- a/crates/lanspread-peer/src/network.rs +++ b/crates/lanspread-peer/src/network.rs @@ -125,20 +125,47 @@ pub async fn exchange_hello(peer_addr: SocketAddr, hello: Hello) -> eyre::Result pub async fn send_library_summary( peer_addr: SocketAddr, + peer_id: &str, summary: LibrarySummary, ) -> eyre::Result<()> { - send_oneway_request(peer_addr, Request::LibrarySummary(summary)).await + send_oneway_request( + peer_addr, + Request::LibrarySummary { + peer_id: peer_id.to_string(), + summary, + }, + ) + .await } pub async fn send_library_snapshot( peer_addr: SocketAddr, + peer_id: &str, snapshot: LibrarySnapshot, ) -> eyre::Result<()> { - send_oneway_request(peer_addr, Request::LibrarySnapshot(snapshot)).await + send_oneway_request( + peer_addr, + Request::LibrarySnapshot { + peer_id: peer_id.to_string(), + snapshot, + }, + ) + .await } -pub async fn send_library_delta(peer_addr: SocketAddr, delta: LibraryDelta) -> eyre::Result<()> { - send_oneway_request(peer_addr, Request::LibraryDelta(delta)).await +pub async fn send_library_delta( + peer_addr: SocketAddr, + peer_id: &str, + delta: LibraryDelta, +) -> eyre::Result<()> { + send_oneway_request( + peer_addr, + Request::LibraryDelta { + peer_id: peer_id.to_string(), + delta, + }, + ) + .await } pub async fn send_goodbye(peer_addr: SocketAddr, peer_id: String) -> eyre::Result<()> { diff --git a/crates/lanspread-peer/src/remote_peer.rs b/crates/lanspread-peer/src/remote_peer.rs index f90d6cd..e55fde5 100644 --- a/crates/lanspread-peer/src/remote_peer.rs +++ b/crates/lanspread-peer/src/remote_peer.rs @@ -2,9 +2,15 @@ use std::{net::SocketAddr, sync::Arc}; +use lanspread_db::db::Game; +use lanspread_proto::GameSummary; use tokio::sync::RwLock; -use crate::peer_db::{PeerGameDB, PeerId}; +use crate::{ + events, + network::request_game_list_from_peer, + peer_db::{PeerGameDB, PeerId}, +}; pub async fn ensure_peer_id_for_addr( peer_game_db: &Arc>, @@ -19,3 +25,37 @@ pub async fn ensure_peer_id_for_addr( db.upsert_peer(addr_id.clone(), peer_addr); addr_id } + +pub async fn refresh_peer_games( + peer_game_db: &Arc>, + tx_notify_ui: &tokio::sync::mpsc::UnboundedSender, + peer_addr: SocketAddr, + peer_id: &PeerId, +) -> eyre::Result<()> { + if peer_game_db.read().await.peer_game_count(peer_id) > 0 { + return Ok(()); + } + + let games = request_game_list_from_peer(peer_addr).await?; + let summaries = games.into_iter().map(game_to_summary).collect::>(); + { + let mut db = peer_game_db.write().await; + db.update_peer_games(peer_id, summaries); + } + events::emit_peer_game_list(peer_game_db, tx_notify_ui).await; + Ok(()) +} + +fn game_to_summary(game: Game) -> GameSummary { + let availability = game.normalized_availability(); + GameSummary { + id: game.id, + name: game.name, + size: game.size, + downloaded: game.downloaded, + installed: game.installed, + eti_version: game.eti_game_version, + manifest_hash: 0, + availability, + } +} diff --git a/crates/lanspread-peer/src/services/discovery.rs b/crates/lanspread-peer/src/services/discovery.rs index e25fc26..53a1dba 100644 --- a/crates/lanspread-peer/src/services/discovery.rs +++ b/crates/lanspread-peer/src/services/discovery.rs @@ -173,12 +173,14 @@ fn spawn_protocol_negotiation( ) { let peer_addr = info.addr; let peer_id_arc = ctx.peer_id.clone(); + let local_peer_addr = ctx.local_peer_addr.clone(); let local_library = ctx.local_library.clone(); let peer_game_db = ctx.peer_game_db.clone(); ctx.task_tracker.spawn(async move { if let Err(err) = perform_handshake_with_peer( peer_id_arc, + local_peer_addr, local_library, peer_game_db, tx_notify_ui, diff --git a/crates/lanspread-peer/src/services/handshake.rs b/crates/lanspread-peer/src/services/handshake.rs index 107075e..96a16f2 100644 --- a/crates/lanspread-peer/src/services/handshake.rs +++ b/crates/lanspread-peer/src/services/handshake.rs @@ -13,6 +13,7 @@ use crate::{ library::{LocalLibraryState, build_library_snapshot, build_library_summary}, network::{exchange_hello, send_library_delta, send_library_snapshot, send_library_summary}, peer_db::{PeerGameDB, PeerId, PeerUpsert}, + remote_peer::refresh_peer_games, }; enum LibraryUpdate { @@ -22,9 +23,11 @@ enum LibraryUpdate { pub(super) async fn build_hello_ack(ctx: &PeerCtx) -> HelloAck { let library_guard = ctx.local_library.read().await; + let listen_addr = *ctx.local_peer_addr.read().await; HelloAck { peer_id: ctx.peer_id.as_ref().clone(), proto_ver: PROTOCOL_VERSION, + listen_addr, library_rev: library_guard.revision, library_digest: library_guard.digest, features: default_features(), @@ -33,12 +36,15 @@ pub(super) async fn build_hello_ack(ctx: &PeerCtx) -> HelloAck { async fn build_hello_from_state( peer_id: &str, + local_peer_addr: &Arc>>, local_library: &Arc>, ) -> Hello { let library_guard = local_library.read().await; + let listen_addr = *local_peer_addr.read().await; Hello { peer_id: peer_id.to_string(), proto_ver: PROTOCOL_VERSION, + listen_addr, library_rev: library_guard.revision, library_digest: library_guard.digest, features: default_features(), @@ -47,13 +53,14 @@ async fn build_hello_from_state( pub(crate) async fn perform_handshake_with_peer( peer_id: Arc, + local_peer_addr: Arc>>, local_library: Arc>, peer_game_db: Arc>, tx_notify_ui: UnboundedSender, peer_addr: SocketAddr, peer_id_hint: Option, ) -> eyre::Result<()> { - let hello = build_hello_from_state(peer_id.as_ref(), &local_library).await; + let hello = build_hello_from_state(peer_id.as_ref(), &local_peer_addr, &local_library).await; let ack = exchange_hello(peer_addr, hello).await?; if ack.proto_ver != PROTOCOL_VERSION { @@ -79,10 +86,11 @@ pub(crate) async fn perform_handshake_with_peer( let _ = peer_game_db.write().await.remove_peer(expected); } + let record_addr = ack.listen_addr.unwrap_or(peer_addr); let upsert = record_remote_library( &peer_game_db, ack.peer_id.clone(), - peer_addr, + record_addr, ack.library_rev, ack.library_digest, ack.features.clone(), @@ -91,7 +99,8 @@ pub(crate) async fn perform_handshake_with_peer( after_peer_library_recorded( upsert, - peer_addr, + record_addr, + peer_id.as_ref(), ack.library_rev, ack.library_digest, &local_library, @@ -100,6 +109,12 @@ pub(crate) async fn perform_handshake_with_peer( ) .await; + if let Err(err) = + refresh_peer_games(&peer_game_db, &tx_notify_ui, record_addr, &ack.peer_id).await + { + log::warn!("Failed to refresh peer games from {record_addr}: {err}"); + } + Ok(()) } @@ -121,7 +136,8 @@ pub(super) async fn accept_inbound_hello( return build_hello_ack(ctx).await; } - if let Some(addr) = peer_record_addr(&ctx.peer_game_db, &hello.peer_id, remote_addr).await { + if let Some(addr) = peer_record_addr(&ctx.peer_game_db, &hello.peer_id, hello.listen_addr).await + { let upsert = record_remote_library( &ctx.peer_game_db, hello.peer_id.clone(), @@ -135,6 +151,7 @@ pub(super) async fn accept_inbound_hello( after_peer_library_recorded( upsert, addr, + ctx.peer_id.as_ref(), hello.library_rev, hello.library_digest, &ctx.local_library, @@ -142,6 +159,13 @@ pub(super) async fn accept_inbound_hello( &ctx.tx_notify_ui, ) .await; + + spawn_peer_game_refresh(ctx, addr, hello.peer_id.clone()); + } else { + log::debug!( + "Ignoring inbound hello from {} without a known listener address", + hello.peer_id + ); } build_hello_ack(ctx).await @@ -150,15 +174,28 @@ pub(super) async fn accept_inbound_hello( async fn peer_record_addr( peer_game_db: &Arc>, peer_id: &PeerId, - remote_addr: Option, + listen_addr: Option, ) -> Option { - let remote_addr = remote_addr?; let db = peer_game_db.read().await; - Some(db.peer_addr(peer_id).unwrap_or(remote_addr)) + listen_addr.or_else(|| db.peer_addr(peer_id)) } +fn spawn_peer_game_refresh(ctx: &PeerCtx, peer_addr: SocketAddr, peer_id: PeerId) { + let peer_game_db = ctx.peer_game_db.clone(); + let tx_notify_ui = ctx.tx_notify_ui.clone(); + ctx.task_tracker.spawn(async move { + if let Err(err) = + refresh_peer_games(&peer_game_db, &tx_notify_ui, peer_addr, &peer_id).await + { + log::warn!("Failed to refresh peer games from {peer_addr}: {err}"); + } + }); +} + +#[allow(clippy::too_many_arguments)] pub(super) fn spawn_library_resync( peer_id: Arc, + local_peer_addr: Arc>>, local_library: Arc>, peer_game_db: Arc>, tx_notify_ui: UnboundedSender, @@ -169,6 +206,7 @@ pub(super) fn spawn_library_resync( tokio::spawn(async move { if let Err(err) = perform_handshake_with_peer( peer_id, + local_peer_addr, local_library, peer_game_db, tx_notify_ui, @@ -196,9 +234,11 @@ async fn record_remote_library( upsert } +#[allow(clippy::too_many_arguments)] async fn after_peer_library_recorded( upsert: PeerUpsert, peer_addr: SocketAddr, + local_peer_id: &str, remote_library_rev: u64, remote_library_digest: u64, local_library: &Arc>, @@ -207,11 +247,12 @@ async fn after_peer_library_recorded( ) { if upsert.is_new { events::emit_peer_discovered(peer_game_db, tx_notify_ui, peer_addr).await; - send_local_library_summary(peer_addr, local_library).await; + send_local_library_summary(peer_addr, local_peer_id, local_library).await; } send_local_library_update_if_needed( peer_addr, + local_peer_id, local_library, remote_library_rev, remote_library_digest, @@ -221,15 +262,17 @@ async fn after_peer_library_recorded( async fn send_local_library_summary( peer_addr: SocketAddr, + local_peer_id: &str, local_library: &Arc>, ) { let summary = { let library_guard = local_library.read().await; build_library_summary(&library_guard) }; + let local_peer_id = local_peer_id.to_string(); tokio::spawn(async move { - if let Err(err) = send_library_summary(peer_addr, summary).await { + if let Err(err) = send_library_summary(peer_addr, &local_peer_id, summary).await { log::warn!("Failed to send library summary to {peer_addr}: {err}"); } }); @@ -237,16 +280,20 @@ async fn send_local_library_summary( async fn send_local_library_update_if_needed( peer_addr: SocketAddr, + local_peer_id: &str, local_library: &Arc>, remote_rev: u64, remote_digest: u64, ) { if let Some(update) = select_library_update(local_library, remote_rev, remote_digest).await { + let local_peer_id = local_peer_id.to_string(); tokio::spawn(async move { let result = match update { - LibraryUpdate::Delta(delta) => send_library_delta(peer_addr, delta).await, + LibraryUpdate::Delta(delta) => { + send_library_delta(peer_addr, &local_peer_id, delta).await + } LibraryUpdate::Snapshot(snapshot) => { - send_library_snapshot(peer_addr, snapshot).await + send_library_snapshot(peer_addr, &local_peer_id, snapshot).await } }; @@ -293,15 +340,33 @@ mod tests { async fn inbound_hello_keeps_existing_listening_addr() { let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new())); let advertised = addr([10, 66, 0, 2], 40000); - let transport_source = addr([10, 66, 0, 2], 52000); peer_game_db .write() .await .upsert_peer("peer".to_string(), advertised); - let record_addr = - peer_record_addr(&peer_game_db, &"peer".to_string(), Some(transport_source)).await; + let record_addr = peer_record_addr(&peer_game_db, &"peer".to_string(), None).await; assert_eq!(record_addr, Some(advertised)); } + + #[tokio::test] + async fn inbound_hello_prefers_reported_listening_addr() { + let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new())); + let advertised = addr([10, 66, 0, 2], 40000); + + let record_addr = + peer_record_addr(&peer_game_db, &"peer".to_string(), Some(advertised)).await; + + assert_eq!(record_addr, Some(advertised)); + } + + #[tokio::test] + async fn inbound_hello_without_known_listener_is_not_recorded() { + let peer_game_db = Arc::new(RwLock::new(PeerGameDB::new())); + + let record_addr = peer_record_addr(&peer_game_db, &"peer".to_string(), None).await; + + assert_eq!(record_addr, None); + } } diff --git a/crates/lanspread-peer/src/services/stream.rs b/crates/lanspread-peer/src/services/stream.rs index b03679f..23b48d8 100644 --- a/crates/lanspread-peer/src/services/stream.rs +++ b/crates/lanspread-peer/src/services/stream.rs @@ -14,7 +14,6 @@ use crate::{ events, local_games::{get_game_file_descriptions, is_local_dir_name, local_download_available}, peer::{send_game_file_chunk, send_game_file_data}, - remote_peer::ensure_peer_id_for_addr, services::handshake::{ accept_inbound_hello, perform_handshake_with_peer, @@ -82,16 +81,16 @@ async fn dispatch_request( send_response(framed_tx, Response::HelloAck(ack), "HelloAck").await } Request::ListGames => handle_list_games(ctx, framed_tx).await, - Request::LibrarySummary(summary) => { - handle_library_summary(ctx, remote_addr, summary).await; + Request::LibrarySummary { peer_id, summary } => { + handle_library_summary(ctx, peer_id, summary).await; framed_tx } - Request::LibrarySnapshot(snapshot) => { - handle_library_snapshot(ctx, remote_addr, snapshot).await; + Request::LibrarySnapshot { peer_id, snapshot } => { + handle_library_snapshot(ctx, peer_id, snapshot).await; framed_tx } - Request::LibraryDelta(delta) => { - handle_library_delta(ctx, remote_addr, delta).await; + Request::LibraryDelta { peer_id, delta } => { + handle_library_delta(ctx, peer_id, delta).await; framed_tx } Request::GetGame { id } => handle_get_game(ctx, id, framed_tx).await, @@ -160,20 +159,16 @@ async fn handle_list_games(ctx: &PeerCtx, framed_tx: ResponseWriter) -> Response send_response(framed_tx, Response::ListGames(games), "ListGames").await } -async fn handle_library_summary( - ctx: &PeerCtx, - remote_addr: Option, - summary: LibrarySummary, -) { - let Some(addr) = remote_addr else { - return; - }; - - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - let (previous_digest, previous_count, features) = { +async fn handle_library_summary(ctx: &PeerCtx, peer_id: String, summary: LibrarySummary) { + let (addr, previous_digest, previous_count, features) = { let db = ctx.peer_game_db.read().await; + let Some(addr) = db.peer_addr(&peer_id) else { + log::debug!("Ignoring library summary from unknown peer {peer_id}"); + return; + }; let (_, digest) = db.peer_library_state(&peer_id).unwrap_or((0, 0)); ( + addr, digest, db.peer_game_count(&peer_id), db.peer_features(&peer_id), @@ -193,12 +188,14 @@ async fn handle_library_summary( if summary.library_digest != previous_digest || previous_count == 0 { ctx.task_tracker.spawn({ let peer_id_arc = ctx.peer_id.clone(); + let local_peer_addr = ctx.local_peer_addr.clone(); let local_library = ctx.local_library.clone(); let peer_game_db = ctx.peer_game_db.clone(); let tx_notify_ui = ctx.tx_notify_ui.clone(); async move { if let Err(err) = perform_handshake_with_peer( peer_id_arc, + local_peer_addr, local_library, peer_game_db, tx_notify_ui, @@ -214,28 +211,32 @@ async fn handle_library_summary( } } -async fn handle_library_snapshot( - ctx: &PeerCtx, - remote_addr: Option, - snapshot: LibrarySnapshot, -) { - if let Some(addr) = remote_addr { - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; - { - let mut db = ctx.peer_game_db.write().await; +async fn handle_library_snapshot(ctx: &PeerCtx, peer_id: String, snapshot: LibrarySnapshot) { + let applied = { + let mut db = ctx.peer_game_db.write().await; + if db.peer_addr(&peer_id).is_some() { db.apply_library_snapshot(&peer_id, snapshot); + true + } else { + log::debug!("Ignoring library snapshot from unknown peer {peer_id}"); + false } + }; + if applied { events::emit_peer_game_list(&ctx.peer_game_db, &ctx.tx_notify_ui).await; } } -async fn handle_library_delta(ctx: &PeerCtx, remote_addr: Option, delta: LibraryDelta) { - let Some(addr) = remote_addr else { +async fn handle_library_delta(ctx: &PeerCtx, peer_id: String, delta: LibraryDelta) { + let Some(addr) = ({ + let db = ctx.peer_game_db.read().await; + db.peer_addr(&peer_id) + }) else { + log::debug!("Ignoring library delta from unknown peer {peer_id}"); return; }; - let peer_id = ensure_peer_id_for_addr(&ctx.peer_game_db, addr).await; let applied = { let mut db = ctx.peer_game_db.write().await; db.apply_library_delta(&peer_id, delta) @@ -246,6 +247,7 @@ async fn handle_library_delta(ctx: &PeerCtx, remote_addr: Option, de } else { spawn_library_resync( ctx.peer_id.clone(), + ctx.local_peer_addr.clone(), ctx.local_library.clone(), ctx.peer_game_db.clone(), ctx.tx_notify_ui.clone(), diff --git a/crates/lanspread-proto/src/lib.rs b/crates/lanspread-proto/src/lib.rs index 7295015..9f854aa 100644 --- a/crates/lanspread-proto/src/lib.rs +++ b/crates/lanspread-proto/src/lib.rs @@ -1,8 +1,10 @@ +use std::net::SocketAddr; + use bytes::Bytes; use lanspread_db::db::{Game, GameFileDescription}; use serde::{Deserialize, Serialize}; -pub const PROTOCOL_VERSION: u32 = 2; +pub const PROTOCOL_VERSION: u32 = 3; pub use lanspread_db::db::Availability; @@ -22,6 +24,7 @@ pub struct GameSummary { pub struct Hello { pub peer_id: String, pub proto_ver: u32, + pub listen_addr: Option, pub library_rev: u64, pub library_digest: u64, pub features: Vec, @@ -31,6 +34,7 @@ pub struct Hello { pub struct HelloAck { pub peer_id: String, pub proto_ver: u32, + pub listen_addr: Option, pub library_rev: u64, pub library_digest: u64, pub features: Vec, @@ -73,9 +77,18 @@ pub enum Request { length: u64, }, Hello(Hello), - LibrarySummary(LibrarySummary), - LibrarySnapshot(LibrarySnapshot), - LibraryDelta(LibraryDelta), + LibrarySummary { + peer_id: String, + summary: LibrarySummary, + }, + LibrarySnapshot { + peer_id: String, + snapshot: LibrarySnapshot, + }, + LibraryDelta { + peer_id: String, + delta: LibraryDelta, + }, Goodbye { peer_id: String, },