feat(ctrl): report connection mode in welcome

PLAN.md calls out room modes such as relay, direct-p2p, and relay fallback so
future transport choices can fit the protocol. Add an explicit ConnectionMode
field to ServerWelcome and default it to relay for existing decoded welcomes.

The relay still operates only in relay mode today. Client and gateway startup
logs now print the selected mode, which makes the current relay path visible
without changing routing or forwarding behavior.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-ctrl server_welcome -- --nocapture
- cargo test -p lanparty-client-win -p lanparty-gateway
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
This commit is contained in:
2026-05-21 21:43:44 +02:00
parent bdb571799a
commit 6a18daac3a
4 changed files with 78 additions and 4 deletions
+72
View File
@@ -98,6 +98,29 @@ pub enum Role {
Gateway,
}
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize,
)]
pub enum ConnectionMode {
#[default]
#[serde(rename = "relay")]
Relay,
#[serde(rename = "direct-p2p")]
DirectP2p,
#[serde(rename = "direct-failed-relay-fallback")]
DirectFailedRelayFallback,
}
impl fmt::Display for ConnectionMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Relay => f.write_str("relay"),
Self::DirectP2p => f.write_str("direct-p2p"),
Self::DirectFailedRelayFallback => f.write_str("direct-failed-relay-fallback"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct EndpointHello {
protocol_version: u16,
@@ -190,6 +213,8 @@ pub struct ServerWelcome {
peer_id: u32,
effective_tap_mtu: u16,
#[serde(default)]
mode: ConnectionMode,
#[serde(default)]
gateway_connected: bool,
}
@@ -211,10 +236,17 @@ impl ServerWelcome {
room_id,
peer_id,
effective_tap_mtu,
mode: ConnectionMode::Relay,
gateway_connected: false,
})
}
#[must_use]
pub const fn with_mode(mut self, mode: ConnectionMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub const fn with_gateway_connected(mut self, gateway_connected: bool) -> Self {
self.gateway_connected = gateway_connected;
@@ -263,6 +295,11 @@ impl ServerWelcome {
self.effective_tap_mtu
}
#[must_use]
pub const fn mode(&self) -> ConnectionMode {
self.mode
}
#[must_use]
pub const fn gateway_connected(&self) -> bool {
self.gateway_connected
@@ -507,10 +544,45 @@ mod tests {
fn server_welcome_reports_gateway_presence() {
let welcome = ServerWelcome::new(1, 2, MIN_USEFUL_TAP_MTU as u16).unwrap();
assert_eq!(welcome.mode(), ConnectionMode::Relay);
assert!(!welcome.gateway_connected());
assert!(welcome.with_gateway_connected(true).gateway_connected());
}
#[test]
fn server_welcome_reports_connection_mode() {
let welcome = ServerWelcome::new(1, 2, MIN_USEFUL_TAP_MTU as u16)
.unwrap()
.with_mode(ConnectionMode::DirectFailedRelayFallback);
assert_eq!(welcome.mode(), ConnectionMode::DirectFailedRelayFallback);
assert_eq!(ConnectionMode::Relay.to_string(), "relay");
assert_eq!(ConnectionMode::DirectP2p.to_string(), "direct-p2p");
assert_eq!(
ConnectionMode::DirectFailedRelayFallback.to_string(),
"direct-failed-relay-fallback"
);
assert_eq!(
serde_json::to_string(&ConnectionMode::DirectP2p).unwrap(),
r#""direct-p2p""#
);
assert_eq!(
serde_json::from_str::<ConnectionMode>(r#""direct-failed-relay-fallback""#).unwrap(),
ConnectionMode::DirectFailedRelayFallback
);
}
#[test]
fn server_welcome_defaults_missing_mode_to_relay() {
let json = format!(
r#"{{"protocol_version":1,"room_id":1,"peer_id":2,"effective_tap_mtu":{}}}"#,
MIN_USEFUL_TAP_MTU
);
let welcome: ServerWelcome = serde_json::from_str(&json).unwrap();
assert_eq!(welcome.mode(), ConnectionMode::Relay);
}
#[test]
fn peer_info_enforces_role_mac_rules() {
let client = PeerInfo::new(7, Role::Client, Some(client_mac())).unwrap();