fix(audit): M1 — add version: u8 to all SignalMessage variants

Convert Hold/Unhold/Mute/Unmute/TransferAck from unit variants to struct
variants with `version: u8` (serde default = 2). Every SignalMessage
variant now carries a version field, enabling future semantic versioning
and clean rejection of deprecated variants during federation routing.

305 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 06:27:23 +04:00
parent 4ebb2dac2d
commit 4ac62d99e0
2 changed files with 40 additions and 25 deletions

View File

@@ -99,12 +99,12 @@ pub fn signal_to_call_type(signal: &SignalMessage) -> CallSignalType {
SignalMessage::LossRecoveryUpdate { .. } => CallSignalType::Offer, // reuse (telemetry) SignalMessage::LossRecoveryUpdate { .. } => CallSignalType::Offer, // reuse (telemetry)
SignalMessage::Ping { .. } | SignalMessage::Pong { .. } => CallSignalType::Offer, SignalMessage::Ping { .. } | SignalMessage::Pong { .. } => CallSignalType::Offer,
SignalMessage::AuthToken { .. } => CallSignalType::Offer, SignalMessage::AuthToken { .. } => CallSignalType::Offer,
SignalMessage::Hold => CallSignalType::Hold, SignalMessage::Hold { .. } => CallSignalType::Hold,
SignalMessage::Unhold => CallSignalType::Unhold, SignalMessage::Unhold { .. } => CallSignalType::Unhold,
SignalMessage::Mute => CallSignalType::Mute, SignalMessage::Mute { .. } => CallSignalType::Mute,
SignalMessage::Unmute => CallSignalType::Unmute, SignalMessage::Unmute { .. } => CallSignalType::Unmute,
SignalMessage::Transfer { .. } => CallSignalType::Transfer, SignalMessage::Transfer { .. } => CallSignalType::Transfer,
SignalMessage::TransferAck => CallSignalType::Offer, // reuse SignalMessage::TransferAck { .. } => CallSignalType::Offer, // reuse
SignalMessage::PresenceUpdate { .. } => CallSignalType::Offer, // reuse SignalMessage::PresenceUpdate { .. } => CallSignalType::Offer, // reuse
SignalMessage::RouteQuery { .. } => CallSignalType::Offer, // reuse SignalMessage::RouteQuery { .. } => CallSignalType::Offer, // reuse
SignalMessage::TransportFeedback { .. } => CallSignalType::Offer, // reuse (BWE) SignalMessage::TransportFeedback { .. } => CallSignalType::Offer, // reuse (BWE)
@@ -199,19 +199,19 @@ mod tests {
)); ));
assert!(matches!( assert!(matches!(
signal_to_call_type(&SignalMessage::Hold), signal_to_call_type(&SignalMessage::Hold { version: default_signal_version() }),
CallSignalType::Hold CallSignalType::Hold
)); ));
assert!(matches!( assert!(matches!(
signal_to_call_type(&SignalMessage::Unhold), signal_to_call_type(&SignalMessage::Unhold { version: default_signal_version() }),
CallSignalType::Unhold CallSignalType::Unhold
)); ));
assert!(matches!( assert!(matches!(
signal_to_call_type(&SignalMessage::Mute), signal_to_call_type(&SignalMessage::Mute { version: default_signal_version() }),
CallSignalType::Mute CallSignalType::Mute
)); ));
assert!(matches!( assert!(matches!(
signal_to_call_type(&SignalMessage::Unmute), signal_to_call_type(&SignalMessage::Unmute { version: default_signal_version() }),
CallSignalType::Unmute CallSignalType::Unmute
)); ));

View File

@@ -669,13 +669,25 @@ pub enum SignalMessage {
}, },
/// Put the call on hold (stop sending media, keep session alive). /// Put the call on hold (stop sending media, keep session alive).
Hold, Hold {
#[serde(default = "default_signal_version")]
version: u8,
},
/// Resume a held call. /// Resume a held call.
Unhold, Unhold {
#[serde(default = "default_signal_version")]
version: u8,
},
/// Mute request from the remote side (server-initiated mute, like IAX2 QUELCH). /// Mute request from the remote side (server-initiated mute, like IAX2 QUELCH).
Mute, Mute {
#[serde(default = "default_signal_version")]
version: u8,
},
/// Unmute request from the remote side (like IAX2 UNQUELCH). /// Unmute request from the remote side (like IAX2 UNQUELCH).
Unmute, Unmute {
#[serde(default = "default_signal_version")]
version: u8,
},
/// Transfer the call to another peer. /// Transfer the call to another peer.
Transfer { Transfer {
#[serde(default = "default_signal_version")] #[serde(default = "default_signal_version")]
@@ -685,7 +697,10 @@ pub enum SignalMessage {
relay_addr: Option<String>, relay_addr: Option<String>,
}, },
/// Acknowledge a transfer request. /// Acknowledge a transfer request.
TransferAck, TransferAck {
#[serde(default = "default_signal_version")]
version: u8,
},
/// Presence update from a peer relay (gossip protocol). /// Presence update from a peer relay (gossip protocol).
/// Sent periodically over probe connections to share which fingerprints /// Sent periodically over probe connections to share which fingerprints
@@ -1729,7 +1744,7 @@ mod tests {
version: default_signal_version(), version: default_signal_version(),
timestamp_ms: 12345, timestamp_ms: 12345,
}, },
SignalMessage::Hold, SignalMessage::Hold { version: default_signal_version() },
SignalMessage::Hangup { SignalMessage::Hangup {
version: default_signal_version(), version: default_signal_version(),
reason: HangupReason::Normal, reason: HangupReason::Normal,
@@ -1750,28 +1765,28 @@ mod tests {
#[test] #[test]
fn hold_unhold_serialize() { fn hold_unhold_serialize() {
let hold = SignalMessage::Hold; let hold = SignalMessage::Hold { version: default_signal_version() };
let json = serde_json::to_string(&hold).unwrap(); let json = serde_json::to_string(&hold).unwrap();
let decoded: SignalMessage = serde_json::from_str(&json).unwrap(); let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(decoded, SignalMessage::Hold)); assert!(matches!(decoded, SignalMessage::Hold { .. }));
let unhold = SignalMessage::Unhold; let unhold = SignalMessage::Unhold { version: default_signal_version() };
let json = serde_json::to_string(&unhold).unwrap(); let json = serde_json::to_string(&unhold).unwrap();
let decoded: SignalMessage = serde_json::from_str(&json).unwrap(); let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(decoded, SignalMessage::Unhold)); assert!(matches!(decoded, SignalMessage::Unhold { .. }));
} }
#[test] #[test]
fn mute_unmute_serialize() { fn mute_unmute_serialize() {
let mute = SignalMessage::Mute; let mute = SignalMessage::Mute { version: default_signal_version() };
let json = serde_json::to_string(&mute).unwrap(); let json = serde_json::to_string(&mute).unwrap();
let decoded: SignalMessage = serde_json::from_str(&json).unwrap(); let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(decoded, SignalMessage::Mute)); assert!(matches!(decoded, SignalMessage::Mute { .. }));
let unmute = SignalMessage::Unmute; let unmute = SignalMessage::Unmute { version: default_signal_version() };
let json = serde_json::to_string(&unmute).unwrap(); let json = serde_json::to_string(&unmute).unwrap();
let decoded: SignalMessage = serde_json::from_str(&json).unwrap(); let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(decoded, SignalMessage::Unmute)); assert!(matches!(decoded, SignalMessage::Unmute { .. }));
} }
#[test] #[test]
@@ -1818,10 +1833,10 @@ mod tests {
#[test] #[test]
fn transfer_ack_serialize() { fn transfer_ack_serialize() {
let ack = SignalMessage::TransferAck; let ack = SignalMessage::TransferAck { version: default_signal_version() };
let json = serde_json::to_string(&ack).unwrap(); let json = serde_json::to_string(&ack).unwrap();
let decoded: SignalMessage = serde_json::from_str(&json).unwrap(); let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(decoded, SignalMessage::TransferAck)); assert!(matches!(decoded, SignalMessage::TransferAck { .. }));
} }
#[test] #[test]