fix(signal): add call_id to Hangup — prevents stale hangup killing new calls

Root cause: Hangup had no call_id field. The relay forwarded hangups to
ALL active calls for a user. When user A hung up call 1 and user B
immediately placed call 2, the relay's processing of A's hangup would
also kill call 2 (race window ~1-2s).

Fix: add optional call_id to Hangup (backwards-compatible via serde
skip_serializing_if). When present, the relay only ends the named call.
Old clients send call_id=None and get the legacy broadcast behavior.

Also: clear pending_path_report in Hangup recv handler and
internal_deregister to prevent stale oneshot channels from blocking
subsequent call setups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-12 16:39:21 +04:00
parent d89376016a
commit a798634b3d
7 changed files with 58 additions and 20 deletions

View File

@@ -608,8 +608,14 @@ pub enum SignalMessage {
Ping { timestamp_ms: u64 },
Pong { timestamp_ms: u64 },
/// End the call.
Hangup { reason: HangupReason },
/// End the call. `call_id` is optional for backwards compatibility
/// with older clients that send Hangup without it — the relay falls
/// back to ending ALL active calls for the sender in that case.
Hangup {
reason: HangupReason,
#[serde(default, skip_serializing_if = "Option::is_none")]
call_id: Option<String>,
},
/// featherChat bearer token for relay authentication.
/// Sent as the first signal message when --auth-url is configured.
@@ -1138,7 +1144,7 @@ mod tests {
callee_local_addrs: Vec::new(),
},
SignalMessage::CallRinging { call_id: "c1".into() },
SignalMessage::Hangup { reason: HangupReason::Normal },
SignalMessage::Hangup { reason: HangupReason::Normal, call_id: None },
];
for inner in cases {
let inner_disc = std::mem::discriminant(&inner);
@@ -1296,7 +1302,7 @@ mod tests {
let cases = vec![
SignalMessage::Ping { timestamp_ms: 12345 },
SignalMessage::Hold,
SignalMessage::Hangup { reason: HangupReason::Normal },
SignalMessage::Hangup { reason: HangupReason::Normal, call_id: None },
SignalMessage::CallRinging { call_id: "abcd".into() },
];
for m in cases {