feat(presence): pure friends presence protocol + auth gate (W7 P4 core)

The logic half of the friends-only idle listener, built as pure unit-tested
seams so the security-critical decisions are provable without live networking.

src/presence.rs:
- ControlMsg { Ping, Pong { room: Option<RoomPresence> } } — self-describing
  tagged JSON; unknown tags rejected (forward-compat).
- should_answer(from, friends, mode): the authorization gate — answer pings
  from FRIENDS ONLY and never while invisible. This whitelist is what keeps
  the always-on-while-open endpoint from being a stranger-facing spam/DoS
  surface;  must be the authenticated remote_id, never payload data.
- PresenceMode { Invisible, Normal(default), Discoverable } + helpers; persisted
  in AppConfig (backward-compat default = Normal = friends-only, no beacon).
- interpret_pong: defensive reply handling — sanitizes the peer-supplied room
  name and only surfaces a joinable room if its ticket actually parses, else
  downgrades to plain Online (no dead/hostile Join button). Never auto-joins.

Deferred to a 2-machine session (the I/O edges): binding the live control
endpoint, its accept loop, and the ping scheduler. +8 presence tests, 256 lib
tests green, clippy clean, release builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 04:28:08 -04:00
co-authored by Claude Opus 4.8
parent 3899a1db44
commit a897f5c5d7
3 changed files with 240 additions and 0 deletions
+7
View File
@@ -153,6 +153,10 @@ pub struct AppConfig {
pub output_volume: f32,
#[serde(default)]
pub network_mode: NetworkMode,
/// Presence posture for the friends idle listener (W7): invisible / normal /
/// discoverable. Default `Normal` = answer friends only, no DNS beacon.
#[serde(default)]
pub presence_mode: crate::presence::PresenceMode,
/// Route audio through PipeWire's echo-cancel module (AEC + noise suppression).
/// Takes effect on the next room join. Off by default.
#[serde(default)]
@@ -249,6 +253,7 @@ impl Default for AppConfig {
input_volume: 1.0,
output_volume: 1.0,
network_mode: NetworkMode::default(),
presence_mode: crate::presence::PresenceMode::default(),
echo_cancellation_enabled: false,
notifications_enabled: true,
participants_width: default_participants_width(),
@@ -361,6 +366,8 @@ mod tests {
let deserialized: AppConfig = serde_json::from_str(minimal_json).unwrap();
assert_eq!(deserialized.network_mode, NetworkMode::RelayNoDiscovery);
// Configs predating the presence posture load as friends-only (no beacon).
assert_eq!(deserialized.presence_mode, crate::presence::PresenceMode::Normal);
assert!(!deserialized.echo_cancellation_enabled);
assert!(deserialized.notifications_enabled);
// Configs predating the volume sliders must load at unity gain.