Files
lanspread/crates/lanspread-proto/src/lib.rs
T
ddidderr 348a02c35f fix(peer): record listener addresses during handshakes
Peers discovered over mDNS could still attribute later library sync traffic to
temporary QUIC source ports. In a real GUI LAN run this made Host B try to
push its library to Host A's outbound port instead of Host A's advertised
listener, so Host A discovered the peer but never saw its games.

Carry the stable listener address in Hello and HelloAck, and key library sync
messages by peer_id instead of inferring identity from the transport source
address. The handshake path now explicitly refreshes an empty peer library from
the known listener address, matching the reliability of the direct-connect CLI
path without overwriting richer snapshot state when it already arrived.

This changes the current wire protocol, so PROTOCOL_VERSION is bumped to 3 and
all peers must be rebuilt together. The architecture note now documents that
listener addresses come from mDNS or Hello/HelloAck, never from ephemeral QUIC
source ports.

Test Plan:
- just fmt
- just test
- just clippy
- just build
- git diff --check

Refs: Local Linux/Win11 GUI LAN test logs from 2026-05-18.
2026-05-18 17:27:15 +02:00

165 lines
3.9 KiB
Rust

use std::net::SocketAddr;
use bytes::Bytes;
use lanspread_db::db::{Game, GameFileDescription};
use serde::{Deserialize, Serialize};
pub const PROTOCOL_VERSION: u32 = 3;
pub use lanspread_db::db::Availability;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct GameSummary {
pub id: String,
pub name: String,
pub size: u64,
pub downloaded: bool,
pub installed: bool,
pub eti_version: Option<String>,
pub manifest_hash: u64,
pub availability: Availability,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Hello {
pub peer_id: String,
pub proto_ver: u32,
pub listen_addr: Option<SocketAddr>,
pub library_rev: u64,
pub library_digest: u64,
pub features: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HelloAck {
pub peer_id: String,
pub proto_ver: u32,
pub listen_addr: Option<SocketAddr>,
pub library_rev: u64,
pub library_digest: u64,
pub features: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibrarySummary {
pub library_rev: u64,
pub library_digest: u64,
pub game_count: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibrarySnapshot {
pub library_rev: u64,
pub games: Vec<GameSummary>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LibraryDelta {
pub from_rev: u64,
pub to_rev: u64,
pub added: Vec<GameSummary>,
pub updated: Vec<GameSummary>,
pub removed: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Request {
Ping,
ListGames,
GetGame {
id: String,
},
GetGameFileData(GameFileDescription),
GetGameFileChunk {
game_id: String,
relative_path: String,
offset: u64,
length: u64,
},
Hello(Hello),
LibrarySummary {
peer_id: String,
summary: LibrarySummary,
},
LibrarySnapshot {
peer_id: String,
snapshot: LibrarySnapshot,
},
LibraryDelta {
peer_id: String,
delta: LibraryDelta,
},
Goodbye {
peer_id: String,
},
Invalid(Bytes, String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Response {
Pong,
ListGames(Vec<Game>),
GetGame {
id: String,
file_descriptions: Vec<GameFileDescription>,
},
HelloAck(HelloAck),
GameNotFound(String),
InvalidRequest(Bytes, String),
EncodingError(String),
DecodingError(Bytes, String),
InternalPeerError(String),
}
// Add Message trait
pub trait Message {
fn decode(bytes: Bytes) -> Self;
fn encode(&self) -> Bytes;
}
// Implement for Request
impl Message for Request {
fn decode(bytes: Bytes) -> Self {
match serde_json::from_slice(&bytes) {
Ok(t) => t,
Err(e) => {
tracing::error!(?e, "Request decoding error");
Request::Invalid(bytes, e.to_string())
}
}
}
fn encode(&self) -> Bytes {
match serde_json::to_vec(self) {
Ok(s) => Bytes::from(s),
Err(e) => {
tracing::error!(?e, "Request encoding error");
Bytes::from(format!(r#"{{"error": "encoding error: {e}"}}"#))
}
}
}
}
// Implement for Response
impl Message for Response {
fn decode(bytes: Bytes) -> Self {
match serde_json::from_slice(&bytes) {
Ok(t) => t,
Err(e) => {
tracing::error!(?e, "Response decoding error");
Response::DecodingError(bytes, e.to_string())
}
}
}
fn encode(&self) -> Bytes {
match serde_json::to_vec(self) {
Ok(s) => Bytes::from(s),
Err(e) => {
tracing::error!(?e, "Response encoding error");
Bytes::from(format!(r#"{{"error": "encoding error: {e}"}}"#))
}
}
}
}