T3.3: SignalMessage version field
This commit is contained in:
@@ -23,7 +23,7 @@ use wzp_fec::{RaptorQFecDecoder, RaptorQFecEncoder};
|
||||
use wzp_proto::{
|
||||
AdaptiveQualityController, AudioDecoder, AudioEncoder, CodecId, FecDecoder, FecEncoder,
|
||||
MediaHeader, MediaPacket, MediaTransport, MediaType, QualityController, QualityProfile,
|
||||
SignalMessage,
|
||||
SignalMessage, default_signal_version,
|
||||
};
|
||||
|
||||
use crate::audio_ring::AudioRing;
|
||||
@@ -321,11 +321,12 @@ impl WzpEngine {
|
||||
|
||||
// Auth if token provided
|
||||
if let Some(ref tok) = token {
|
||||
let _ = transport.send_signal(&SignalMessage::AuthToken { token: tok.clone() }).await;
|
||||
let _ = transport.send_signal(&SignalMessage::AuthToken { version: default_signal_version(), token: tok.clone() }).await;
|
||||
}
|
||||
|
||||
// Register presence
|
||||
let _ = transport.send_signal(&SignalMessage::RegisterPresence {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
signature: vec![],
|
||||
alias: alias.clone(),
|
||||
@@ -350,7 +351,7 @@ impl WzpEngine {
|
||||
break;
|
||||
}
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::CallRinging { call_id })) => {
|
||||
Ok(Some(SignalMessage::CallRinging { call_id, ..})) => {
|
||||
info!(call_id = %call_id, "signal: ringing");
|
||||
let mut stats = signal_state.stats.lock().unwrap();
|
||||
stats.state = crate::stats::CallState::Ringing;
|
||||
@@ -522,6 +523,7 @@ async fn run_call(
|
||||
let signature = kx.sign(&sign_data);
|
||||
|
||||
let offer = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
@@ -1223,6 +1225,7 @@ async fn run_call(
|
||||
Ok(Some(SignalMessage::RoomUpdate {
|
||||
count,
|
||||
participants,
|
||||
..
|
||||
})) => {
|
||||
info!(count, "RoomUpdate received");
|
||||
let members: Vec<crate::stats::RoomMember> = participants
|
||||
@@ -1240,6 +1243,7 @@ async fn run_call(
|
||||
Ok(Some(SignalMessage::QualityDirective {
|
||||
recommended_profile,
|
||||
reason,
|
||||
..
|
||||
})) => {
|
||||
let idx = profile_to_index(&recommended_profile);
|
||||
info!(
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::time::{Duration, Instant};
|
||||
use clap::Parser;
|
||||
use tracing::info;
|
||||
|
||||
use wzp_proto::{CodecId, MediaPacket, MediaTransport};
|
||||
use wzp_proto::{CodecId, MediaPacket, MediaTransport, default_signal_version};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI
|
||||
@@ -919,6 +919,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Auth if token provided
|
||||
if let Some(ref token) = args.token {
|
||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: token.clone(),
|
||||
};
|
||||
transport.send_signal(&auth).await?;
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::sync::Arc;
|
||||
use tracing::{error, info};
|
||||
|
||||
use wzp_client::call::{CallConfig, CallDecoder, CallEncoder};
|
||||
use wzp_proto::MediaTransport;
|
||||
use wzp_proto::{MediaTransport, default_signal_version};
|
||||
|
||||
const FRAME_SAMPLES: usize = 960; // 20ms @ 48kHz
|
||||
|
||||
@@ -380,6 +380,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Send auth token if provided (relay with --auth-url expects this first)
|
||||
if let Some(ref token) = cli.token {
|
||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: token.clone(),
|
||||
};
|
||||
transport.send_signal(&auth).await?;
|
||||
@@ -473,6 +474,7 @@ async fn run_silence(transport: Arc<wzp_transport::QuinnTransport>) -> anyhow::R
|
||||
|
||||
info!(total_source, total_repair, total_bytes, "done — closing");
|
||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
};
|
||||
@@ -632,6 +634,7 @@ async fn run_file_mode(
|
||||
|
||||
// Send Hangup signal so the relay knows we're done
|
||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
};
|
||||
@@ -769,7 +772,7 @@ async fn run_signal_mode(
|
||||
token: Option<String>,
|
||||
call_target: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
let identity = seed.derive_identity();
|
||||
let pub_id = identity.public_identity();
|
||||
@@ -792,13 +795,17 @@ async fn run_signal_mode(
|
||||
// Auth if token provided
|
||||
if let Some(ref tok) = token {
|
||||
transport
|
||||
.send_signal(&SignalMessage::AuthToken { token: tok.clone() })
|
||||
.send_signal(&SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: tok.clone(),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Register presence (signature not verified in Phase 1)
|
||||
transport
|
||||
.send_signal(&SignalMessage::RegisterPresence {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
signature: vec![], // Phase 1: not verified
|
||||
alias: None,
|
||||
@@ -835,6 +842,7 @@ async fn run_signal_mode(
|
||||
|
||||
transport
|
||||
.send_signal(&SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: fp.clone(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: target.clone(),
|
||||
@@ -861,7 +869,7 @@ async fn run_signal_mode(
|
||||
loop {
|
||||
match signal_transport.recv_signal().await {
|
||||
Ok(Some(msg)) => match msg {
|
||||
SignalMessage::CallRinging { call_id } => {
|
||||
SignalMessage::CallRinging { call_id, .. } => {
|
||||
info!(call_id = %call_id, "ringing...");
|
||||
}
|
||||
SignalMessage::DirectCallOffer {
|
||||
@@ -879,6 +887,7 @@ async fn run_signal_mode(
|
||||
// Auto-accept for CLI testing
|
||||
let _ = signal_transport
|
||||
.send_signal(&SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id,
|
||||
accept_mode: wzp_proto::CallAcceptMode::AcceptGeneric,
|
||||
identity_pub: Some(identity_pub),
|
||||
@@ -908,6 +917,7 @@ async fn run_signal_mode(
|
||||
peer_direct_addr: _,
|
||||
peer_local_addrs: _,
|
||||
peer_mapped_addr: _,
|
||||
..
|
||||
} => {
|
||||
info!(call_id = %call_id, room = %room, relay = %setup_relay, "call setup — connecting to media room");
|
||||
|
||||
@@ -970,6 +980,7 @@ async fn run_signal_mode(
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("hanging up...");
|
||||
let _ = signal_transport.send_signal(&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
}).await;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! 5. Connects QUIC to relay for media
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wzp_proto::packet::SignalMessage;
|
||||
use wzp_proto::packet::{SignalMessage, default_signal_version};
|
||||
|
||||
/// featherChat CallSignal types (mirrors warzone-protocol::message::CallSignalType).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -152,6 +152,7 @@ mod tests {
|
||||
#[test]
|
||||
fn payload_roundtrip() {
|
||||
let signal = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [1u8; 32],
|
||||
ephemeral_pub: [2u8; 32],
|
||||
signature: vec![3u8; 64],
|
||||
@@ -172,6 +173,7 @@ mod tests {
|
||||
#[test]
|
||||
fn signal_type_mapping() {
|
||||
let offer = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [0; 32],
|
||||
ephemeral_pub: [0; 32],
|
||||
signature: vec![],
|
||||
@@ -183,6 +185,7 @@ mod tests {
|
||||
assert!(matches!(signal_to_call_type(&offer), CallSignalType::Offer));
|
||||
|
||||
let hangup = SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
};
|
||||
@@ -209,6 +212,7 @@ mod tests {
|
||||
));
|
||||
|
||||
let transfer = SignalMessage::Transfer {
|
||||
version: default_signal_version(),
|
||||
target_fingerprint: "abc".to_string(),
|
||||
relay_addr: None,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
//! send `CallOffer` → recv `CallAnswer` → derive shared `CryptoSession`.
|
||||
|
||||
use wzp_crypto::{CryptoSession, KeyExchange, WarzoneKeyExchange};
|
||||
use wzp_proto::{HangupReason, MediaTransport, QualityProfile, SignalMessage};
|
||||
use wzp_proto::{
|
||||
HangupReason, MediaTransport, QualityProfile, SignalMessage, default_signal_version,
|
||||
};
|
||||
|
||||
/// Errors that can occur during the client-side cryptographic handshake.
|
||||
#[derive(Debug)]
|
||||
@@ -78,6 +80,7 @@ pub async fn perform_handshake(
|
||||
|
||||
// 4. Send CallOffer
|
||||
let offer = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
@@ -112,6 +115,7 @@ pub async fn perform_handshake(
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
chosen_profile,
|
||||
..
|
||||
} => (identity_pub, ephemeral_pub, signature, chosen_profile),
|
||||
SignalMessage::Hangup {
|
||||
reason: HangupReason::ProtocolVersionMismatch { server_supported },
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
use crate::dual_path::PeerCandidates;
|
||||
use crate::portmap;
|
||||
@@ -133,6 +133,7 @@ impl IceAgent {
|
||||
let candidates = self.gather().await;
|
||||
|
||||
let update = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: self.call_id.clone(),
|
||||
reflexive_addr: candidates.reflexive.map(|a| a.to_string()),
|
||||
local_addrs: candidates.local.iter().map(|a| a.to_string()).collect(),
|
||||
@@ -206,6 +207,7 @@ mod tests {
|
||||
|
||||
// First update (gen=1) should succeed.
|
||||
let update1 = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-call".into(),
|
||||
reflexive_addr: Some("203.0.113.5:4433".into()),
|
||||
local_addrs: vec!["192.168.1.10:4433".into()],
|
||||
@@ -223,6 +225,7 @@ mod tests {
|
||||
|
||||
// Same generation (gen=1) should be rejected.
|
||||
let update1b = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-call".into(),
|
||||
reflexive_addr: Some("198.51.100.9:4433".into()),
|
||||
local_addrs: vec![],
|
||||
@@ -233,6 +236,7 @@ mod tests {
|
||||
|
||||
// Older generation (gen=0) should be rejected.
|
||||
let update0 = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-call".into(),
|
||||
reflexive_addr: Some("10.0.0.1:4433".into()),
|
||||
local_addrs: vec![],
|
||||
@@ -243,6 +247,7 @@ mod tests {
|
||||
|
||||
// Newer generation (gen=2) should succeed.
|
||||
let update2 = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-call".into(),
|
||||
reflexive_addr: Some("198.51.100.9:5555".into()),
|
||||
local_addrs: vec![],
|
||||
@@ -287,6 +292,7 @@ mod tests {
|
||||
let agent = IceAgent::new("test-call".into(), IceAgentConfig::default());
|
||||
|
||||
let update = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-call".into(),
|
||||
reflexive_addr: Some("203.0.113.5:4433".into()),
|
||||
local_addrs: vec!["192.168.1.10:4433".into(), "10.0.0.5:4433".into()],
|
||||
@@ -315,6 +321,7 @@ mod tests {
|
||||
let agent = IceAgent::new("test".into(), IceAgentConfig::default());
|
||||
|
||||
let update = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test".into(),
|
||||
reflexive_addr: None,
|
||||
local_addrs: vec![],
|
||||
@@ -333,6 +340,7 @@ mod tests {
|
||||
let agent = IceAgent::new("test".into(), IceAgentConfig::default());
|
||||
|
||||
let update = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test".into(),
|
||||
reflexive_addr: Some("not-an-addr".into()),
|
||||
local_addrs: vec![
|
||||
|
||||
@@ -30,7 +30,7 @@ use std::net::SocketAddr;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde::Serialize;
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_transport::{QuinnTransport, client_config, create_endpoint};
|
||||
|
||||
/// Result of one probe against one relay. Always returned so the
|
||||
@@ -123,6 +123,7 @@ pub async fn probe_reflect_addr(
|
||||
// path does in desktop/src-tauri/src/lib.rs register_signal.
|
||||
transport
|
||||
.send_signal(&SignalMessage::RegisterPresence {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [0u8; 32],
|
||||
signature: vec![],
|
||||
alias: None,
|
||||
@@ -150,7 +151,7 @@ pub async fn probe_reflect_addr(
|
||||
.map_err(|e| format!("send Reflect: {e}"))?;
|
||||
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::ReflectResponse { observed_addr })) => {
|
||||
Ok(Some(SignalMessage::ReflectResponse { observed_addr, .. })) => {
|
||||
let parsed: SocketAddr = observed_addr
|
||||
.parse()
|
||||
.map_err(|e| format!("parse observed_addr {observed_addr:?}: {e}"))?;
|
||||
|
||||
@@ -11,7 +11,7 @@ use tokio::sync::mpsc;
|
||||
|
||||
use wzp_proto::packet::MediaPacket;
|
||||
use wzp_proto::traits::{MediaTransport, PathQuality};
|
||||
use wzp_proto::{SignalMessage, TransportError};
|
||||
use wzp_proto::{SignalMessage, TransportError, default_signal_version};
|
||||
|
||||
/// A mock transport backed by two mpsc channels (one per direction).
|
||||
///
|
||||
@@ -151,6 +151,7 @@ async fn handshake_rejects_tampered_signature() {
|
||||
let bad_signature = kx.sign(b"wrong-data-intentionally");
|
||||
|
||||
let offer = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature: bad_signature,
|
||||
@@ -197,6 +198,7 @@ async fn client_receives_protocol_version_mismatch() {
|
||||
|
||||
// Respond with ProtocolVersionMismatch.
|
||||
let mismatch = SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
||||
server_supported: vec![3],
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! 3. Auth: WZP auth module request/response matches FC's /v1/auth/validate contract
|
||||
//! 4. Mnemonic: BIP39 interop between both implementations
|
||||
|
||||
use wzp_proto::KeyExchange;
|
||||
use wzp_proto::{KeyExchange, default_signal_version};
|
||||
|
||||
// ─── Identity Compatibility (WZP-FC-8) ──────────────────────────────────────
|
||||
|
||||
@@ -114,6 +114,7 @@ fn mnemonic_strings_identical() {
|
||||
fn wzp_signal_serializes_into_fc_callsignal_payload() {
|
||||
// WZP creates a CallOffer SignalMessage
|
||||
let offer = wzp_proto::SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [1u8; 32],
|
||||
ephemeral_pub: [2u8; 32],
|
||||
signature: vec![3u8; 64],
|
||||
@@ -180,6 +181,7 @@ fn wzp_signal_serializes_into_fc_callsignal_payload() {
|
||||
#[test]
|
||||
fn wzp_answer_round_trips_through_fc_callsignal() {
|
||||
let answer = wzp_proto::SignalMessage::CallAnswer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [10u8; 32],
|
||||
ephemeral_pub: [20u8; 32],
|
||||
signature: vec![30u8; 64],
|
||||
@@ -212,6 +214,7 @@ fn wzp_answer_round_trips_through_fc_callsignal() {
|
||||
#[test]
|
||||
fn wzp_hangup_round_trips_through_fc_callsignal() {
|
||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
};
|
||||
@@ -298,6 +301,7 @@ fn all_signal_types_map_correctly() {
|
||||
let cases: Vec<(wzp_proto::SignalMessage, &str)> = vec![
|
||||
(
|
||||
wzp_proto::SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [0; 32],
|
||||
ephemeral_pub: [0; 32],
|
||||
signature: vec![],
|
||||
@@ -310,6 +314,7 @@ fn all_signal_types_map_correctly() {
|
||||
),
|
||||
(
|
||||
wzp_proto::SignalMessage::CallAnswer {
|
||||
version: default_signal_version(),
|
||||
identity_pub: [0; 32],
|
||||
ephemeral_pub: [0; 32],
|
||||
signature: vec![],
|
||||
@@ -319,12 +324,14 @@ fn all_signal_types_map_correctly() {
|
||||
),
|
||||
(
|
||||
wzp_proto::SignalMessage::IceCandidate {
|
||||
version: default_signal_version(),
|
||||
candidate: "candidate:1".to_string(),
|
||||
},
|
||||
"IceCandidate",
|
||||
),
|
||||
(
|
||||
wzp_proto::SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
},
|
||||
@@ -482,10 +489,11 @@ fn auth_response_with_eth_address() {
|
||||
assert!(!resp3.valid);
|
||||
}
|
||||
|
||||
/// WZP-S-7: SignalMessage::AuthToken { token } exists and round-trips via serde.
|
||||
/// WZP-S-7: SignalMessage::AuthToken { version: default_signal_version(), token } exists and round-trips via serde.
|
||||
#[test]
|
||||
fn wzp_proto_has_auth_token_variant() {
|
||||
let msg = wzp_proto::SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: "fc-bearer-token-xyz".to_string(),
|
||||
};
|
||||
|
||||
@@ -496,7 +504,7 @@ fn wzp_proto_has_auth_token_variant() {
|
||||
|
||||
// Deserialize back
|
||||
let decoded: wzp_proto::SignalMessage = serde_json::from_str(&json).unwrap();
|
||||
if let wzp_proto::SignalMessage::AuthToken { token } = decoded {
|
||||
if let wzp_proto::SignalMessage::AuthToken { token, .. } = decoded {
|
||||
assert_eq!(token, "fc-bearer-token-xyz");
|
||||
} else {
|
||||
panic!("expected AuthToken variant, got: {decoded:?}");
|
||||
|
||||
@@ -32,7 +32,7 @@ pub use media_type::MediaType;
|
||||
pub use packet::{
|
||||
CallAcceptMode, FRAME_TYPE_FULL, FRAME_TYPE_MINI, HangupReason, MediaHeader, MediaHeaderV2,
|
||||
MediaPacket, MiniFrameContext, MiniFrameContextV2, MiniHeader, MiniHeaderV2, PresenceUser,
|
||||
QualityReport, RoomParticipant, SignalMessage, TrunkEntry, TrunkFrame,
|
||||
QualityReport, RoomParticipant, SignalMessage, TrunkEntry, TrunkFrame, default_signal_version,
|
||||
};
|
||||
pub use quality::{AdaptiveQualityController, NetworkContext, Tier};
|
||||
pub use session::{Session, SessionEvent, SessionState};
|
||||
|
||||
@@ -540,10 +540,23 @@ impl MiniFrameContextV2 {
|
||||
/// Compatible with Warzone messenger's identity model:
|
||||
/// - Identity keys are Ed25519 (signing) + X25519 (encryption) derived from a 32-byte seed via HKDF
|
||||
/// - Fingerprint = SHA-256(Ed25519 public key)[:16]
|
||||
///
|
||||
/// **Version field:** every struct variant carries `version: u8` (default 1).
|
||||
/// Old payloads that omit `version` deserialize cleanly thanks to `#[serde(default)]`.
|
||||
///
|
||||
/// **Unknown variant handling:** `#[serde(other)]` is designed for
|
||||
/// string/integer enums with adjacent tagging, not for externally tagged enum
|
||||
/// variants. With externally tagged representation (the default for Rust enums),
|
||||
/// the variant name IS the tag, so there is no other value to catch. `bincode`
|
||||
/// in particular does not support `#[serde(other)]`. Unknown variants will
|
||||
/// naturally cause a deserialization error, which is the correct behavior for
|
||||
/// the signal protocol.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum SignalMessage {
|
||||
/// Call initiation (analogous to Warzone's WireMessage::CallOffer).
|
||||
CallOffer {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Caller's Ed25519 identity public key (32 bytes).
|
||||
identity_pub: [u8; 32],
|
||||
/// Ephemeral X25519 public key for this call.
|
||||
@@ -565,6 +578,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Call acceptance (analogous to Warzone's WireMessage::CallAnswer).
|
||||
CallAnswer {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Callee's Ed25519 identity public key (32 bytes).
|
||||
identity_pub: [u8; 32],
|
||||
/// Callee's ephemeral X25519 public key.
|
||||
@@ -577,11 +592,15 @@ pub enum SignalMessage {
|
||||
|
||||
/// ICE candidate for NAT traversal.
|
||||
IceCandidate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
candidate: String,
|
||||
},
|
||||
|
||||
/// Periodic rekeying (forward secrecy).
|
||||
Rekey {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// New ephemeral X25519 public key.
|
||||
new_ephemeral_pub: [u8; 32],
|
||||
/// Ed25519 signature over (new_ephemeral_pub || session_id).
|
||||
@@ -590,6 +609,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Quality/profile change request.
|
||||
QualityUpdate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
report: QualityReport,
|
||||
recommended_profile: crate::QualityProfile,
|
||||
},
|
||||
@@ -601,6 +622,8 @@ pub enum SignalMessage {
|
||||
/// introducing this variant is backward-compatible with pre-Phase-4
|
||||
/// relays — they'll just log "unknown signal variant" on receipt.
|
||||
LossRecoveryUpdate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Total frames reconstructed via DRED since call start (monotonic).
|
||||
#[serde(default)]
|
||||
dred_reconstructions: u64,
|
||||
@@ -616,9 +639,13 @@ pub enum SignalMessage {
|
||||
|
||||
/// Connection keepalive / RTT measurement.
|
||||
Ping {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
timestamp_ms: u64,
|
||||
},
|
||||
Pong {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
timestamp_ms: u64,
|
||||
},
|
||||
|
||||
@@ -626,6 +653,8 @@ pub enum SignalMessage {
|
||||
/// with older clients that send Hangup without it — the relay falls
|
||||
/// back to ending ALL active calls for the sender in that case.
|
||||
Hangup {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
reason: HangupReason,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
call_id: Option<String>,
|
||||
@@ -634,6 +663,8 @@ pub enum SignalMessage {
|
||||
/// featherChat bearer token for relay authentication.
|
||||
/// Sent as the first signal message when --auth-url is configured.
|
||||
AuthToken {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
token: String,
|
||||
},
|
||||
|
||||
@@ -647,6 +678,8 @@ pub enum SignalMessage {
|
||||
Unmute,
|
||||
/// Transfer the call to another peer.
|
||||
Transfer {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
target_fingerprint: String,
|
||||
/// Optional relay address for the transfer target.
|
||||
relay_addr: Option<String>,
|
||||
@@ -658,6 +691,8 @@ pub enum SignalMessage {
|
||||
/// Sent periodically over probe connections to share which fingerprints
|
||||
/// are connected to the sending relay.
|
||||
PresenceUpdate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Fingerprints currently connected to the sending relay.
|
||||
fingerprints: Vec<String>,
|
||||
/// Address of the sending relay (e.g., "192.168.1.10:4433").
|
||||
@@ -666,11 +701,15 @@ pub enum SignalMessage {
|
||||
|
||||
/// Ask a peer relay to look up a fingerprint in its registry.
|
||||
RouteQuery {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
fingerprint: String,
|
||||
ttl: u8,
|
||||
},
|
||||
/// Response to a route query.
|
||||
RouteResponse {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
fingerprint: String,
|
||||
found: bool,
|
||||
relay_chain: Vec<String>,
|
||||
@@ -680,6 +719,8 @@ pub enum SignalMessage {
|
||||
/// Sent over a relay link (`_relay` SNI) to ask the peer relay to
|
||||
/// create a room and forward media for the given session.
|
||||
SessionForward {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
session_id: String,
|
||||
target_fingerprint: String,
|
||||
source_relay: String,
|
||||
@@ -687,12 +728,16 @@ pub enum SignalMessage {
|
||||
/// Confirm that the forwarding session has been set up on the peer relay.
|
||||
/// The `room_name` tells the source relay which room to address media to.
|
||||
SessionForwardAck {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
session_id: String,
|
||||
room_name: String,
|
||||
},
|
||||
|
||||
/// Room membership update — sent by relay to all participants when someone joins or leaves.
|
||||
RoomUpdate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Current participant count.
|
||||
count: u32,
|
||||
/// List of participants currently in the room.
|
||||
@@ -702,12 +747,16 @@ pub enum SignalMessage {
|
||||
// ── Federation signals (relay-to-relay) ──
|
||||
/// Federation: initial handshake — the connecting relay identifies itself.
|
||||
FederationHello {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// TLS certificate fingerprint of the connecting relay.
|
||||
tls_fingerprint: String,
|
||||
},
|
||||
|
||||
/// Federation: this relay now has local participants in a global room.
|
||||
GlobalRoomActive {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
room: String,
|
||||
/// Participants on the announcing relay (for federated presence).
|
||||
#[serde(default)]
|
||||
@@ -716,6 +765,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Federation: this relay's last local participant left a global room.
|
||||
GlobalRoomInactive {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
room: String,
|
||||
},
|
||||
|
||||
@@ -723,6 +774,8 @@ pub enum SignalMessage {
|
||||
/// Register on relay for direct calls. Sent on `_signal` connections
|
||||
/// after optional AuthToken.
|
||||
RegisterPresence {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Client's Ed25519 identity public key.
|
||||
identity_pub: [u8; 32],
|
||||
/// Signature over ("register-presence" || identity_pub).
|
||||
@@ -733,6 +786,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Relay confirms presence registration.
|
||||
RegisterPresenceAck {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<String>,
|
||||
@@ -750,6 +805,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Direct call offer routed through the relay to a specific peer.
|
||||
DirectCallOffer {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Caller's fingerprint.
|
||||
caller_fingerprint: String,
|
||||
/// Caller's display name.
|
||||
@@ -798,6 +855,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Callee's response to a direct call.
|
||||
DirectCallAnswer {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// How the callee accepts (or rejects).
|
||||
accept_mode: CallAcceptMode,
|
||||
@@ -838,6 +897,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Relay tells both parties: media room is ready.
|
||||
CallSetup {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// Room name on the relay for the media session (e.g., "_call:a1b2c3d4").
|
||||
room: String,
|
||||
@@ -871,6 +932,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Ringing notification (relay → caller, callee received the offer).
|
||||
CallRinging {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
},
|
||||
|
||||
@@ -893,6 +956,8 @@ pub enum SignalMessage {
|
||||
/// for IPv4, "[::1]:p" for IPv6. Clients parse it with
|
||||
/// `SocketAddr::from_str`.
|
||||
ReflectResponse {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
observed_addr: String,
|
||||
},
|
||||
|
||||
@@ -910,6 +975,8 @@ pub enum SignalMessage {
|
||||
/// and the other picks Relay — they now agree on the path
|
||||
/// before any media flows.
|
||||
MediaPathReport {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// Did the direct QUIC connection (P2P dial or accept)
|
||||
/// complete successfully on this side?
|
||||
@@ -931,6 +998,8 @@ pub enum SignalMessage {
|
||||
/// — peers ignore updates with a generation <= their last-seen
|
||||
/// generation to handle reordering.
|
||||
CandidateUpdate {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// New server-reflexive address (STUN-discovered or relay-reflected).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -951,6 +1020,8 @@ pub enum SignalMessage {
|
||||
/// and recent port sequence so the peer can predict which port
|
||||
/// to dial.
|
||||
HardNatProbe {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// Last observed external ports (most recent first).
|
||||
/// Typically 3-5 entries from sequential STUN probes.
|
||||
@@ -968,6 +1039,8 @@ pub enum SignalMessage {
|
||||
/// ports it has open. The Dialer then sprays QUIC connects to
|
||||
/// these ports (and optionally random ports) on the Acceptor's IP.
|
||||
HardNatBirthdayStart {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// Number of sockets the Acceptor opened.
|
||||
acceptor_port_count: u16,
|
||||
@@ -995,6 +1068,8 @@ pub enum SignalMessage {
|
||||
/// A→B→A echo loops; proper TTL + dedup will land when
|
||||
/// multi-hop federation is added (Phase 4.2).
|
||||
FederatedSignalForward {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// The signal message being forwarded
|
||||
/// (`DirectCallOffer`, `DirectCallAnswer`, `CallRinging`,
|
||||
/// `Hangup`, ...). Boxed because `SignalMessage` is
|
||||
@@ -1011,6 +1086,8 @@ pub enum SignalMessage {
|
||||
/// Relay-initiated quality directive: all participants should switch
|
||||
/// to the recommended profile to match the weakest link.
|
||||
QualityDirective {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
recommended_profile: crate::QualityProfile,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
reason: Option<String>,
|
||||
@@ -1021,6 +1098,8 @@ pub enum SignalMessage {
|
||||
/// users to all connected clients. Sent on every register/
|
||||
/// deregister so clients can maintain a live lobby user list.
|
||||
PresenceList {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// List of online users. Each entry is { fingerprint, alias }.
|
||||
users: Vec<PresenceUser>,
|
||||
},
|
||||
@@ -1031,6 +1110,8 @@ pub enum SignalMessage {
|
||||
/// conditions. Used for consensual upgrades that require both
|
||||
/// sides to agree (e.g., switching from Opus24k to Studio48k).
|
||||
UpgradeProposal {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// Unique ID for this proposal (to match response).
|
||||
proposal_id: String,
|
||||
@@ -1045,6 +1126,8 @@ pub enum SignalMessage {
|
||||
|
||||
/// Response to an UpgradeProposal.
|
||||
UpgradeResponse {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
proposal_id: String,
|
||||
/// true = accepted, both sides switch. false = rejected.
|
||||
@@ -1057,6 +1140,8 @@ pub enum SignalMessage {
|
||||
/// Confirmation that the upgrade is committed — both sides
|
||||
/// should switch encoder at the next frame boundary.
|
||||
UpgradeConfirm {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
proposal_id: String,
|
||||
confirmed_profile: crate::QualityProfile,
|
||||
@@ -1067,6 +1152,8 @@ pub enum SignalMessage {
|
||||
/// encoding where each side uses the best quality its connection
|
||||
/// supports, rather than forcing all to the weakest link.
|
||||
QualityCapability {
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
call_id: String,
|
||||
/// The best profile this peer can sustain based on its
|
||||
/// current network conditions.
|
||||
@@ -1083,7 +1170,7 @@ pub enum SignalMessage {
|
||||
/// carrying ACK/NACK vectors and a REMB-style bandwidth estimate.
|
||||
TransportFeedback {
|
||||
/// Feedback format version (default 1).
|
||||
#[serde(default)]
|
||||
#[serde(default = "default_signal_version")]
|
||||
version: u8,
|
||||
/// Which media stream this feedback applies to.
|
||||
stream_id: u8,
|
||||
@@ -1132,6 +1219,10 @@ pub fn default_proto_version() -> u8 {
|
||||
pub fn default_supported_versions() -> Vec<u8> {
|
||||
vec![2]
|
||||
}
|
||||
/// Default signal message version (v1).
|
||||
pub fn default_signal_version() -> u8 {
|
||||
1
|
||||
}
|
||||
|
||||
/// Reasons for ending a call.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -1304,12 +1395,13 @@ mod tests {
|
||||
// for v6 and the client side has to parse that back.
|
||||
for addr in ["192.0.2.17:4433", "[2001:db8::1]:4433", "127.0.0.1:54321"] {
|
||||
let resp = SignalMessage::ReflectResponse {
|
||||
version: default_signal_version(),
|
||||
observed_addr: addr.to_string(),
|
||||
};
|
||||
let json = serde_json::to_string(&resp).unwrap();
|
||||
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::ReflectResponse { observed_addr } => {
|
||||
SignalMessage::ReflectResponse { observed_addr, .. } => {
|
||||
assert_eq!(observed_addr, addr);
|
||||
// Must parse back to a SocketAddr cleanly.
|
||||
let _parsed: std::net::SocketAddr = observed_addr
|
||||
@@ -1326,6 +1418,7 @@ mod tests {
|
||||
// Wrap a DirectCallOffer inside FederatedSignalForward and
|
||||
// prove both directions of serde preserve every field.
|
||||
let inner = SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: Some("Alice".into()),
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -1340,6 +1433,7 @@ mod tests {
|
||||
caller_build_version: None,
|
||||
};
|
||||
let forward = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(inner),
|
||||
origin_relay_fp: "relay-a-tls-fp".into(),
|
||||
};
|
||||
@@ -1349,6 +1443,7 @@ mod tests {
|
||||
SignalMessage::FederatedSignalForward {
|
||||
inner,
|
||||
origin_relay_fp,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(origin_relay_fp, "relay-a-tls-fp");
|
||||
match *inner {
|
||||
@@ -1375,6 +1470,7 @@ mod tests {
|
||||
// we intend to forward survives being boxed + re-serialized.
|
||||
let cases: Vec<SignalMessage> = vec![
|
||||
SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||
identity_pub: None,
|
||||
@@ -1387,9 +1483,11 @@ mod tests {
|
||||
callee_build_version: None,
|
||||
},
|
||||
SignalMessage::CallRinging {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
},
|
||||
SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: HangupReason::Normal,
|
||||
call_id: None,
|
||||
},
|
||||
@@ -1397,6 +1495,7 @@ mod tests {
|
||||
for inner in cases {
|
||||
let inner_disc = std::mem::discriminant(&inner);
|
||||
let forward = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(inner),
|
||||
origin_relay_fp: "r".into(),
|
||||
};
|
||||
@@ -1415,6 +1514,7 @@ mod tests {
|
||||
fn hole_punching_optional_fields_roundtrip() {
|
||||
// DirectCallOffer with Some(caller_reflexive_addr)
|
||||
let offer = SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -1448,6 +1548,7 @@ mod tests {
|
||||
// OMIT the field from the JSON so older relays that don't
|
||||
// know about caller_reflexive_addr don't see it.
|
||||
let offer_none = SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -1469,6 +1570,7 @@ mod tests {
|
||||
|
||||
// DirectCallAnswer with callee_reflexive_addr.
|
||||
let answer = SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||
identity_pub: None,
|
||||
@@ -1494,6 +1596,7 @@ mod tests {
|
||||
|
||||
// CallSetup with peer_direct_addr.
|
||||
let setup = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
room: "call-c1".into(),
|
||||
relay_addr: "203.0.113.5:4433".into(),
|
||||
@@ -1566,14 +1669,17 @@ mod tests {
|
||||
// test a sample of the pre-existing ones.
|
||||
let cases = vec![
|
||||
SignalMessage::Ping {
|
||||
version: default_signal_version(),
|
||||
timestamp_ms: 12345,
|
||||
},
|
||||
SignalMessage::Hold,
|
||||
SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: HangupReason::Normal,
|
||||
call_id: None,
|
||||
},
|
||||
SignalMessage::CallRinging {
|
||||
version: default_signal_version(),
|
||||
call_id: "abcd".into(),
|
||||
},
|
||||
];
|
||||
@@ -1614,6 +1720,7 @@ mod tests {
|
||||
#[test]
|
||||
fn transfer_serialize() {
|
||||
let transfer = SignalMessage::Transfer {
|
||||
version: default_signal_version(),
|
||||
target_fingerprint: "abc123".to_string(),
|
||||
relay_addr: Some("relay.example.com:4433".to_string()),
|
||||
};
|
||||
@@ -1623,6 +1730,7 @@ mod tests {
|
||||
SignalMessage::Transfer {
|
||||
target_fingerprint,
|
||||
relay_addr,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(target_fingerprint, "abc123");
|
||||
assert_eq!(relay_addr.unwrap(), "relay.example.com:4433");
|
||||
@@ -1632,6 +1740,7 @@ mod tests {
|
||||
|
||||
// Also test with relay_addr = None
|
||||
let transfer_no_relay = SignalMessage::Transfer {
|
||||
version: default_signal_version(),
|
||||
target_fingerprint: "def456".to_string(),
|
||||
relay_addr: None,
|
||||
};
|
||||
@@ -1641,6 +1750,7 @@ mod tests {
|
||||
SignalMessage::Transfer {
|
||||
target_fingerprint,
|
||||
relay_addr,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(target_fingerprint, "def456");
|
||||
assert!(relay_addr.is_none());
|
||||
@@ -1660,6 +1770,7 @@ mod tests {
|
||||
#[test]
|
||||
fn presence_update_signal_roundtrip() {
|
||||
let msg = SignalMessage::PresenceUpdate {
|
||||
version: default_signal_version(),
|
||||
fingerprints: vec!["aabb".to_string(), "ccdd".to_string()],
|
||||
relay_addr: "10.0.0.1:4433".to_string(),
|
||||
};
|
||||
@@ -1669,6 +1780,7 @@ mod tests {
|
||||
SignalMessage::PresenceUpdate {
|
||||
fingerprints,
|
||||
relay_addr,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(fingerprints.len(), 2);
|
||||
assert!(fingerprints.contains(&"aabb".to_string()));
|
||||
@@ -1680,6 +1792,7 @@ mod tests {
|
||||
|
||||
// Empty fingerprints list
|
||||
let msg_empty = SignalMessage::PresenceUpdate {
|
||||
version: default_signal_version(),
|
||||
fingerprints: vec![],
|
||||
relay_addr: "10.0.0.2:4433".to_string(),
|
||||
};
|
||||
@@ -1689,6 +1802,7 @@ mod tests {
|
||||
SignalMessage::PresenceUpdate {
|
||||
fingerprints,
|
||||
relay_addr,
|
||||
..
|
||||
} => {
|
||||
assert!(fingerprints.is_empty());
|
||||
assert_eq!(relay_addr, "10.0.0.2:4433");
|
||||
@@ -1999,6 +2113,7 @@ mod tests {
|
||||
#[test]
|
||||
fn quality_directive_roundtrip() {
|
||||
let msg = SignalMessage::QualityDirective {
|
||||
version: default_signal_version(),
|
||||
recommended_profile: crate::QualityProfile::DEGRADED,
|
||||
reason: Some("weakest link degraded".into()),
|
||||
};
|
||||
@@ -2008,6 +2123,7 @@ mod tests {
|
||||
SignalMessage::QualityDirective {
|
||||
recommended_profile,
|
||||
reason,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(recommended_profile.codec, CodecId::Opus6k);
|
||||
assert_eq!(reason.as_deref(), Some("weakest link degraded"));
|
||||
@@ -2019,6 +2135,7 @@ mod tests {
|
||||
#[test]
|
||||
fn quality_directive_without_reason_roundtrip() {
|
||||
let msg = SignalMessage::QualityDirective {
|
||||
version: default_signal_version(),
|
||||
recommended_profile: crate::QualityProfile::GOOD,
|
||||
reason: None,
|
||||
};
|
||||
@@ -2078,6 +2195,7 @@ mod tests {
|
||||
#[test]
|
||||
fn upgrade_proposal_roundtrip() {
|
||||
let msg = SignalMessage::UpgradeProposal {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
proposal_id: "p1".into(),
|
||||
proposed_profile: crate::QualityProfile::STUDIO_48K,
|
||||
@@ -2102,6 +2220,7 @@ mod tests {
|
||||
#[test]
|
||||
fn upgrade_response_roundtrip() {
|
||||
let msg = SignalMessage::UpgradeResponse {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
proposal_id: "p1".into(),
|
||||
accepted: true,
|
||||
@@ -2118,6 +2237,7 @@ mod tests {
|
||||
#[test]
|
||||
fn upgrade_confirm_roundtrip() {
|
||||
let msg = SignalMessage::UpgradeConfirm {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
proposal_id: "p1".into(),
|
||||
confirmed_profile: crate::QualityProfile::STUDIO_64K,
|
||||
@@ -2137,6 +2257,7 @@ mod tests {
|
||||
#[test]
|
||||
fn quality_capability_roundtrip() {
|
||||
let msg = SignalMessage::QualityCapability {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
max_profile: crate::QualityProfile::GOOD,
|
||||
loss_pct: Some(2.5),
|
||||
@@ -2162,6 +2283,7 @@ mod tests {
|
||||
#[test]
|
||||
fn candidate_update_roundtrip() {
|
||||
let msg = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "test-123".into(),
|
||||
reflexive_addr: Some("203.0.113.5:4433".into()),
|
||||
local_addrs: vec!["192.168.1.10:4433".into(), "10.0.0.5:4433".into()],
|
||||
@@ -2177,6 +2299,7 @@ mod tests {
|
||||
local_addrs,
|
||||
mapped_addr,
|
||||
generation,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(call_id, "test-123");
|
||||
assert_eq!(reflexive_addr.as_deref(), Some("203.0.113.5:4433"));
|
||||
@@ -2191,6 +2314,7 @@ mod tests {
|
||||
#[test]
|
||||
fn candidate_update_minimal_roundtrip() {
|
||||
let msg = SignalMessage::CandidateUpdate {
|
||||
version: default_signal_version(),
|
||||
call_id: "c".into(),
|
||||
reflexive_addr: None,
|
||||
local_addrs: vec![],
|
||||
@@ -2215,6 +2339,7 @@ mod tests {
|
||||
#[test]
|
||||
fn offer_with_mapped_addr_roundtrip() {
|
||||
let msg = SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -2246,6 +2371,7 @@ mod tests {
|
||||
#[test]
|
||||
fn offer_without_mapped_addr_omits_field() {
|
||||
let msg = SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -2266,6 +2392,7 @@ mod tests {
|
||||
#[test]
|
||||
fn answer_with_mapped_addr_roundtrip() {
|
||||
let msg = SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||
identity_pub: None,
|
||||
@@ -2292,6 +2419,7 @@ mod tests {
|
||||
#[test]
|
||||
fn setup_with_mapped_addr_roundtrip() {
|
||||
let msg = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: "c1".into(),
|
||||
room: "room".into(),
|
||||
relay_addr: "1.2.3.4:5".into(),
|
||||
@@ -2368,6 +2496,7 @@ mod tests {
|
||||
#[test]
|
||||
fn register_presence_ack_with_new_fields_roundtrip() {
|
||||
let msg = SignalMessage::RegisterPresenceAck {
|
||||
version: default_signal_version(),
|
||||
success: true,
|
||||
error: None,
|
||||
relay_build: Some("abc123".into()),
|
||||
@@ -2443,6 +2572,7 @@ mod tests {
|
||||
nacked_seqs,
|
||||
remb_bps,
|
||||
recv_time_us,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(version, 1);
|
||||
assert_eq!(stream_id, 0);
|
||||
@@ -2475,7 +2605,76 @@ mod tests {
|
||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::TransportFeedback { version, .. } => {
|
||||
assert_eq!(version, 0, "serde default makes omitted version 0");
|
||||
assert_eq!(version, 1, "serde default makes omitted version 1");
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_payload_without_version_deserializes() {
|
||||
// CallOffer without version field — old client sending to new receiver.
|
||||
let json = r#"{
|
||||
"CallOffer": {
|
||||
"identity_pub": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||||
"ephemeral_pub": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
|
||||
"signature": [],
|
||||
"supported_profiles": [],
|
||||
"alias": null,
|
||||
"protocol_version": 2,
|
||||
"supported_versions": [2]
|
||||
}
|
||||
}"#;
|
||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::CallOffer {
|
||||
version,
|
||||
protocol_version,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(version, 1, "missing version defaults to 1");
|
||||
assert_eq!(protocol_version, 2);
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
|
||||
// Ping without version field.
|
||||
let json = r#"{"Ping": {"timestamp_ms": 1234}}"#;
|
||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::Ping {
|
||||
version,
|
||||
timestamp_ms,
|
||||
} => {
|
||||
assert_eq!(version, 1, "missing version defaults to 1");
|
||||
assert_eq!(timestamp_ms, 1234);
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
|
||||
// Hangup without version field.
|
||||
let json = r#"{"Hangup": {"reason": "Normal", "call_id": null}}"#;
|
||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::Hangup { version, .. } => {
|
||||
assert_eq!(version, 1, "missing version defaults to 1");
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_payload_with_version_deserializes() {
|
||||
// Payload that explicitly includes version = 2.
|
||||
let json = r#"{"Ping": {"version": 2, "timestamp_ms": 5678}}"#;
|
||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||
match decoded {
|
||||
SignalMessage::Ping {
|
||||
version,
|
||||
timestamp_ms,
|
||||
} => {
|
||||
assert_eq!(version, 2, "explicit version is preserved");
|
||||
assert_eq!(timestamp_ms, 5678);
|
||||
}
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use sha2::{Digest, Sha256};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_transport::QuinnTransport;
|
||||
|
||||
use crate::config::{PeerConfig, TrustedConfig};
|
||||
@@ -520,7 +520,11 @@ async fn run_room_event_dispatcher(
|
||||
if fm.is_global_room(&room) {
|
||||
let participants = fm.room_mgr.local_participant_list(&room);
|
||||
info!(room = %room, count = participants.len(), "global room now active, announcing to peers");
|
||||
let msg = SignalMessage::GlobalRoomActive { room, participants };
|
||||
let msg = SignalMessage::GlobalRoomActive {
|
||||
version: default_signal_version(),
|
||||
room,
|
||||
participants,
|
||||
};
|
||||
let transports: Vec<Arc<QuinnTransport>> = {
|
||||
let links = fm.peer_links.lock().await;
|
||||
links.values().map(|l| l.transport.clone()).collect()
|
||||
@@ -533,7 +537,10 @@ async fn run_room_event_dispatcher(
|
||||
Ok(RoomEvent::LocalLeave { room }) => {
|
||||
if fm.is_global_room(&room) {
|
||||
info!(room = %room, "global room now inactive, announcing to peers");
|
||||
let msg = SignalMessage::GlobalRoomInactive { room };
|
||||
let msg = SignalMessage::GlobalRoomInactive {
|
||||
version: default_signal_version(),
|
||||
room,
|
||||
};
|
||||
let transports: Vec<Arc<QuinnTransport>> = {
|
||||
let links = fm.peer_links.lock().await;
|
||||
links.values().map(|l| l.transport.clone()).collect()
|
||||
@@ -609,6 +616,7 @@ async fn run_stale_presence_sweeper(fm: Arc<FederationManager>) {
|
||||
let mut seen = HashSet::new();
|
||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||
let update = SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: all_participants.len() as u32,
|
||||
participants: all_participants,
|
||||
};
|
||||
@@ -659,6 +667,7 @@ async fn connect_to_peer(
|
||||
|
||||
// Send hello with our TLS fingerprint
|
||||
let hello = SignalMessage::FederationHello {
|
||||
version: default_signal_version(),
|
||||
tls_fingerprint: fm.local_tls_fp.clone(),
|
||||
};
|
||||
transport
|
||||
@@ -710,6 +719,7 @@ async fn run_federation_link(
|
||||
let participants = fm.room_mgr.local_participant_list(room_name);
|
||||
info!(peer = %peer_label, room = %room_name, participants = participants.len(), "announcing local global room to new peer");
|
||||
msgs.push(SignalMessage::GlobalRoomActive {
|
||||
version: default_signal_version(),
|
||||
room: room_name.clone(),
|
||||
participants,
|
||||
});
|
||||
@@ -724,6 +734,7 @@ async fn run_federation_link(
|
||||
if fm.is_global_room(room) {
|
||||
info!(peer = %peer_label, room = %room, via = %link.label, "propagating remote room to new peer");
|
||||
msgs.push(SignalMessage::GlobalRoomActive {
|
||||
version: default_signal_version(),
|
||||
room: room.clone(),
|
||||
participants: participants.clone(),
|
||||
});
|
||||
@@ -837,7 +848,9 @@ async fn handle_signal(
|
||||
}
|
||||
|
||||
match msg {
|
||||
SignalMessage::GlobalRoomActive { room, participants } => {
|
||||
SignalMessage::GlobalRoomActive {
|
||||
room, participants, ..
|
||||
} => {
|
||||
if fm.is_global_room(&room) {
|
||||
info!(peer = %peer_label, room = %room, remote_participants = participants.len(), "peer has global room active");
|
||||
let mut links = fm.peer_links.lock().await;
|
||||
@@ -882,6 +895,7 @@ async fn handle_signal(
|
||||
let _ = link
|
||||
.transport
|
||||
.send_signal(&SignalMessage::GlobalRoomActive {
|
||||
version: default_signal_version(),
|
||||
room: room.clone(),
|
||||
participants: tagged_for_propagation.clone(),
|
||||
})
|
||||
@@ -923,6 +937,7 @@ async fn handle_signal(
|
||||
let mut seen = HashSet::new();
|
||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||
let update = SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: all_participants.len() as u32,
|
||||
participants: all_participants,
|
||||
};
|
||||
@@ -933,7 +948,7 @@ async fn handle_signal(
|
||||
}
|
||||
}
|
||||
}
|
||||
SignalMessage::GlobalRoomInactive { room } => {
|
||||
SignalMessage::GlobalRoomInactive { room, .. } => {
|
||||
info!(peer = %peer_label, room = %room, "peer global room now inactive");
|
||||
let mut links = fm.peer_links.lock().await;
|
||||
if let Some(link) = links.get_mut(peer_fp) {
|
||||
@@ -999,6 +1014,7 @@ async fn handle_signal(
|
||||
}
|
||||
}
|
||||
let msg = SignalMessage::GlobalRoomActive {
|
||||
version: default_signal_version(),
|
||||
room: room.clone(),
|
||||
participants: updated_participants,
|
||||
};
|
||||
@@ -1007,7 +1023,10 @@ async fn handle_signal(
|
||||
}
|
||||
} else {
|
||||
// No participants left anywhere — propagate inactive
|
||||
let msg = SignalMessage::GlobalRoomInactive { room: room.clone() };
|
||||
let msg = SignalMessage::GlobalRoomInactive {
|
||||
version: default_signal_version(),
|
||||
room: room.clone(),
|
||||
};
|
||||
for transport in &peer_sends {
|
||||
let _ = transport.send_signal(&msg).await;
|
||||
}
|
||||
@@ -1025,6 +1044,7 @@ async fn handle_signal(
|
||||
let mut seen = HashSet::new();
|
||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||
let update = SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: all_participants.len() as u32,
|
||||
participants: all_participants,
|
||||
};
|
||||
@@ -1050,6 +1070,7 @@ async fn handle_signal(
|
||||
SignalMessage::FederatedSignalForward {
|
||||
inner,
|
||||
origin_relay_fp,
|
||||
..
|
||||
} => {
|
||||
if origin_relay_fp == fm.local_tls_fp {
|
||||
tracing::debug!(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! recv `CallOffer` → verify → generate ephemeral → derive session → send `CallAnswer`.
|
||||
|
||||
use wzp_crypto::{CryptoSession, KeyExchange, WarzoneKeyExchange};
|
||||
use wzp_proto::{MediaTransport, QualityProfile, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, QualityProfile, SignalMessage, default_signal_version};
|
||||
|
||||
/// Accept the relay (callee) side of the cryptographic handshake.
|
||||
///
|
||||
@@ -51,6 +51,7 @@ pub async fn accept_handshake(
|
||||
alias,
|
||||
protocol_version,
|
||||
supported_versions: _,
|
||||
..
|
||||
} => (
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
@@ -70,6 +71,7 @@ pub async fn accept_handshake(
|
||||
// 1a. Protocol version check — we only speak v2.
|
||||
if protocol_version != 2 {
|
||||
let mismatch = SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
||||
server_supported: vec![2],
|
||||
},
|
||||
@@ -108,6 +110,7 @@ pub async fn accept_handshake(
|
||||
|
||||
// 6. Send CallAnswer
|
||||
let answer = SignalMessage::CallAnswer {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
|
||||
@@ -16,7 +16,7 @@ use clap::Parser;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_relay::config::RelayConfig;
|
||||
use wzp_relay::metrics::RelayMetrics;
|
||||
use wzp_relay::pipeline::{PipelineConfig, RelayPipeline};
|
||||
@@ -640,6 +640,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.send_to(
|
||||
&caller_fp,
|
||||
&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
},
|
||||
@@ -685,6 +686,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
// Emit the LOCAL CallSetup to our local caller.
|
||||
let setup = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: room_name.clone(),
|
||||
relay_addr: advertised_addr_d.clone(),
|
||||
@@ -703,7 +705,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
SignalMessage::CallRinging { ref call_id } => {
|
||||
SignalMessage::CallRinging { ref call_id, .. } => {
|
||||
// Forward to local caller for "ringing..." UX.
|
||||
let caller_fp = {
|
||||
let reg = call_registry_d.lock().await;
|
||||
@@ -866,9 +868,12 @@ async fn main() -> anyhow::Result<()> {
|
||||
info!(%addr, "probe connection detected, entering Ping/Pong + presence responder");
|
||||
loop {
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(wzp_proto::SignalMessage::Ping { timestamp_ms })) => {
|
||||
Ok(Some(wzp_proto::SignalMessage::Ping { timestamp_ms, .. })) => {
|
||||
if let Err(e) = transport
|
||||
.send_signal(&wzp_proto::SignalMessage::Pong { timestamp_ms })
|
||||
.send_signal(&wzp_proto::SignalMessage::Pong {
|
||||
version: default_signal_version(),
|
||||
timestamp_ms,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!(%addr, "probe pong send error: {e}");
|
||||
@@ -878,6 +883,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
Ok(Some(wzp_proto::SignalMessage::PresenceUpdate {
|
||||
fingerprints,
|
||||
relay_addr,
|
||||
..
|
||||
})) => {
|
||||
// A peer relay is telling us which fingerprints it has
|
||||
let peer_addr: std::net::SocketAddr =
|
||||
@@ -894,6 +900,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
reg.local_fingerprints().into_iter().collect()
|
||||
};
|
||||
let reply = wzp_proto::SignalMessage::PresenceUpdate {
|
||||
version: default_signal_version(),
|
||||
fingerprints: local_fps,
|
||||
relay_addr: addr.to_string(),
|
||||
};
|
||||
@@ -902,7 +909,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Some(wzp_proto::SignalMessage::RouteQuery { fingerprint, ttl })) => {
|
||||
Ok(Some(wzp_proto::SignalMessage::RouteQuery {
|
||||
fingerprint, ttl, ..
|
||||
})) => {
|
||||
// Look up the fingerprint in our local registry
|
||||
let reg = presence.lock().await;
|
||||
let route = route_resolver.resolve(®, &fingerprint);
|
||||
@@ -930,6 +939,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
let reply = wzp_proto::SignalMessage::RouteResponse {
|
||||
version: default_signal_version(),
|
||||
fingerprint,
|
||||
found,
|
||||
relay_chain,
|
||||
@@ -968,6 +978,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
{
|
||||
Ok(Ok(Some(wzp_proto::SignalMessage::FederationHello {
|
||||
tls_fingerprint,
|
||||
..
|
||||
}))) => tls_fingerprint,
|
||||
_ => {
|
||||
warn!(%addr, "federation: no hello received, closing");
|
||||
@@ -1004,7 +1015,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Optional auth
|
||||
let auth_fp: Option<String> = if let Some(ref url) = auth_url {
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::AuthToken { token })) => {
|
||||
Ok(Some(SignalMessage::AuthToken { token, .. })) => {
|
||||
match wzp_relay::auth::validate_token(url, &token).await {
|
||||
Ok(client) => Some(client.fingerprint),
|
||||
Err(e) => {
|
||||
@@ -1033,6 +1044,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
identity_pub,
|
||||
signature: _,
|
||||
alias,
|
||||
..
|
||||
}))) => {
|
||||
// Compute fingerprint: SHA-256(Ed25519 pub key)[:16], same as Fingerprint type
|
||||
let fp = {
|
||||
@@ -1067,6 +1079,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Send ack
|
||||
let _ = transport
|
||||
.send_signal(&SignalMessage::RegisterPresenceAck {
|
||||
version: default_signal_version(),
|
||||
success: true,
|
||||
error: None,
|
||||
relay_build: Some(BUILD_GIT_HASH.to_string()),
|
||||
@@ -1126,6 +1139,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// federation has a matching entry.
|
||||
let forwarded = if let Some(ref fm) = federation_mgr {
|
||||
let forward = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(msg.clone()),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1149,6 +1163,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
info!(%addr, target = %target_fp, "call target not online (no federation route)");
|
||||
let _ = transport
|
||||
.send_signal(&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
})
|
||||
@@ -1193,6 +1208,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// federated delivery is in flight.
|
||||
let _ = transport
|
||||
.send_signal(&SignalMessage::CallRinging {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
})
|
||||
.await;
|
||||
@@ -1236,6 +1252,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
drop(hub);
|
||||
let _ = transport
|
||||
.send_signal(&SignalMessage::CallRinging {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
})
|
||||
.await;
|
||||
@@ -1293,11 +1310,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Some(ref origin_fp) = peer_relay_fp {
|
||||
if let Some(ref fm) = federation_mgr {
|
||||
let hangup = SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: Some(call_id.clone()),
|
||||
};
|
||||
let forward =
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(hangup),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1314,6 +1333,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.send_to(
|
||||
&peer_fp,
|
||||
&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: Some(call_id.clone()),
|
||||
},
|
||||
@@ -1390,6 +1410,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Some(ref fm) = federation_mgr {
|
||||
let forward =
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(msg.clone()),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1407,6 +1428,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
let setup_for_callee = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: room.clone(),
|
||||
relay_addr: relay_addr_for_setup,
|
||||
@@ -1429,6 +1451,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// cross-wired candidates (Phase 5.5 ICE
|
||||
// + Phase 8 port-mapped addrs).
|
||||
let setup_for_caller = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: room.clone(),
|
||||
relay_addr: relay_addr_for_setup.clone(),
|
||||
@@ -1437,6 +1460,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
peer_mapped_addr: callee_mapped,
|
||||
};
|
||||
let setup_for_callee = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: room.clone(),
|
||||
relay_addr: relay_addr_for_setup,
|
||||
@@ -1524,6 +1548,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Some(ref fm) = federation_mgr {
|
||||
let forward =
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(msg.clone()),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1568,6 +1593,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Some(ref fm) = federation_mgr {
|
||||
let forward =
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(msg.clone()),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1615,6 +1641,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let Some(ref fm) = federation_mgr {
|
||||
let forward =
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(msg.clone()),
|
||||
origin_relay_fp: tls_fp.clone(),
|
||||
};
|
||||
@@ -1629,9 +1656,12 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
SignalMessage::Ping { timestamp_ms } => {
|
||||
SignalMessage::Ping { timestamp_ms, .. } => {
|
||||
let _ = transport
|
||||
.send_signal(&SignalMessage::Pong { timestamp_ms })
|
||||
.send_signal(&SignalMessage::Pong {
|
||||
version: default_signal_version(),
|
||||
timestamp_ms,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -1651,6 +1681,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let observed_addr = addr.to_string();
|
||||
if let Err(e) = transport
|
||||
.send_signal(&SignalMessage::ReflectResponse {
|
||||
version: default_signal_version(),
|
||||
observed_addr: observed_addr.clone(),
|
||||
})
|
||||
.await
|
||||
@@ -1710,6 +1741,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.send_to(
|
||||
peer_fp,
|
||||
&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: Some(call_id.clone()),
|
||||
},
|
||||
@@ -1741,7 +1773,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let authenticated_fp: Option<String> = if let Some(ref url) = auth_url {
|
||||
info!(%addr, "waiting for auth token...");
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(wzp_proto::SignalMessage::AuthToken { token })) => {
|
||||
Ok(Some(wzp_proto::SignalMessage::AuthToken { token, .. })) => {
|
||||
match wzp_relay::auth::validate_token(url, &token).await {
|
||||
Ok(client) => {
|
||||
metrics.auth_attempts.with_label_values(&["ok"]).inc();
|
||||
@@ -1913,6 +1945,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
if let SignalMessage::RoomUpdate {
|
||||
count: _,
|
||||
participants: mut local_parts,
|
||||
..
|
||||
} = update
|
||||
{
|
||||
let remote = fm.get_remote_participants(&room_name).await;
|
||||
@@ -1921,6 +1954,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
local_parts.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||
SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: local_parts.len() as u32,
|
||||
participants: local_parts,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use prometheus::{Gauge, IntGauge, Opts, Registry};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
|
||||
/// Configuration for a single probe target.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -229,7 +229,7 @@ impl ProbeRunner {
|
||||
let recv_handle = tokio::spawn(async move {
|
||||
loop {
|
||||
match recv_transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::Pong { timestamp_ms })) => {
|
||||
Ok(Some(SignalMessage::Pong { timestamp_ms, .. })) => {
|
||||
let now_ms = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@@ -244,6 +244,7 @@ impl ProbeRunner {
|
||||
Ok(Some(SignalMessage::PresenceUpdate {
|
||||
fingerprints,
|
||||
relay_addr,
|
||||
..
|
||||
})) => {
|
||||
if let Some(ref reg) = recv_presence {
|
||||
// Parse the relay_addr; fall back to the connection target
|
||||
@@ -293,7 +294,10 @@ impl ProbeRunner {
|
||||
}
|
||||
|
||||
if let Err(e) = transport
|
||||
.send_signal(&SignalMessage::Ping { timestamp_ms })
|
||||
.send_signal(&SignalMessage::Ping {
|
||||
version: default_signal_version(),
|
||||
timestamp_ms,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!(target = %self.config.target, "probe ping send error: {e}");
|
||||
@@ -310,6 +314,7 @@ impl ProbeRunner {
|
||||
r.local_fingerprints().into_iter().collect()
|
||||
};
|
||||
let msg = SignalMessage::PresenceUpdate {
|
||||
version: default_signal_version(),
|
||||
fingerprints: fps,
|
||||
relay_addr: self.config.target.to_string(),
|
||||
};
|
||||
@@ -426,9 +431,10 @@ pub fn mesh_summary(registry: &Registry) -> String {
|
||||
/// Handle an incoming Ping signal by replying with a Pong carrying the same timestamp.
|
||||
/// Returns true if the message was a Ping and was handled, false otherwise.
|
||||
pub async fn handle_ping(transport: &wzp_transport::QuinnTransport, msg: &SignalMessage) -> bool {
|
||||
if let SignalMessage::Ping { timestamp_ms } = msg {
|
||||
if let SignalMessage::Ping { timestamp_ms, .. } = msg {
|
||||
if let Err(e) = transport
|
||||
.send_signal(&SignalMessage::Pong {
|
||||
version: default_signal_version(),
|
||||
timestamp_ms: *timestamp_ms,
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -333,10 +333,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn session_forward_signal_roundtrip() {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
// SessionForward roundtrip
|
||||
let msg = SignalMessage::SessionForward {
|
||||
version: default_signal_version(),
|
||||
session_id: "abcd1234".to_string(),
|
||||
target_fingerprint: "deadbeef".to_string(),
|
||||
source_relay: "10.0.0.1:4433".to_string(),
|
||||
@@ -348,6 +349,7 @@ mod tests {
|
||||
session_id,
|
||||
target_fingerprint,
|
||||
source_relay,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(session_id, "abcd1234");
|
||||
assert_eq!(target_fingerprint, "deadbeef");
|
||||
@@ -358,6 +360,7 @@ mod tests {
|
||||
|
||||
// SessionForwardAck roundtrip
|
||||
let ack = SignalMessage::SessionForwardAck {
|
||||
version: default_signal_version(),
|
||||
session_id: "abcd1234".to_string(),
|
||||
room_name: "relay-room-42".to_string(),
|
||||
};
|
||||
@@ -367,6 +370,7 @@ mod tests {
|
||||
SignalMessage::SessionForwardAck {
|
||||
session_id,
|
||||
room_name,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(session_id, "abcd1234");
|
||||
assert_eq!(room_name, "relay-room-42");
|
||||
|
||||
@@ -13,10 +13,10 @@ use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use wzp_proto::MediaTransport;
|
||||
use wzp_proto::packet::TrunkFrame;
|
||||
use wzp_proto::quality::{AdaptiveQualityController, Tier};
|
||||
use wzp_proto::traits::QualityController;
|
||||
use wzp_proto::{MediaTransport, default_signal_version};
|
||||
|
||||
use crate::conformance::ConformanceMeter;
|
||||
use crate::metrics::RelayMetrics;
|
||||
@@ -64,6 +64,7 @@ impl DebugTap {
|
||||
wzp_proto::SignalMessage::RoomUpdate {
|
||||
count,
|
||||
participants,
|
||||
..
|
||||
} => {
|
||||
let names: Vec<&str> = participants
|
||||
.iter()
|
||||
@@ -81,6 +82,7 @@ impl DebugTap {
|
||||
wzp_proto::SignalMessage::QualityDirective {
|
||||
recommended_profile,
|
||||
reason,
|
||||
..
|
||||
} => {
|
||||
info!(
|
||||
target: "debug_tap",
|
||||
@@ -493,6 +495,7 @@ impl RoomManager {
|
||||
);
|
||||
room.qualities.insert(id, ParticipantQuality::new());
|
||||
let update = wzp_proto::SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: room.len() as u32,
|
||||
participants: room.participant_list(),
|
||||
};
|
||||
@@ -570,6 +573,7 @@ impl RoomManager {
|
||||
return None;
|
||||
}
|
||||
let update = wzp_proto::SignalMessage::RoomUpdate {
|
||||
version: default_signal_version(),
|
||||
count: room.len() as u32,
|
||||
participants: room.participant_list(),
|
||||
};
|
||||
@@ -654,6 +658,7 @@ impl RoomManager {
|
||||
);
|
||||
|
||||
let directive = wzp_proto::SignalMessage::QualityDirective {
|
||||
version: default_signal_version(),
|
||||
recommended_profile: profile,
|
||||
reason: Some(format!("weakest link: {weakest:?}")),
|
||||
};
|
||||
|
||||
@@ -201,9 +201,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn route_query_signal_roundtrip() {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
let query = SignalMessage::RouteQuery {
|
||||
version: default_signal_version(),
|
||||
fingerprint: "aabbccdd".to_string(),
|
||||
ttl: 3,
|
||||
};
|
||||
@@ -211,11 +212,12 @@ mod tests {
|
||||
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||
assert!(matches!(
|
||||
decoded,
|
||||
SignalMessage::RouteQuery { ref fingerprint, ttl }
|
||||
SignalMessage::RouteQuery { ref fingerprint, ttl, ..}
|
||||
if fingerprint == "aabbccdd" && ttl == 3
|
||||
));
|
||||
|
||||
let response = SignalMessage::RouteResponse {
|
||||
version: default_signal_version(),
|
||||
fingerprint: "aabbccdd".to_string(),
|
||||
found: true,
|
||||
relay_chain: vec!["10.0.0.1:4433".to_string(), "10.0.0.2:4433".to_string()],
|
||||
@@ -224,7 +226,7 @@ mod tests {
|
||||
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||
assert!(matches!(
|
||||
decoded,
|
||||
SignalMessage::RouteResponse { ref fingerprint, found, ref relay_chain }
|
||||
SignalMessage::RouteResponse { ref fingerprint, found, ref relay_chain, ..}
|
||||
if fingerprint == "aabbccdd" && found && relay_chain.len() == 2
|
||||
));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use tracing::info;
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_transport::QuinnTransport;
|
||||
|
||||
/// A client connected via `_signal` for direct calling.
|
||||
@@ -101,7 +101,10 @@ impl SignalHub {
|
||||
alias: c.alias.clone(),
|
||||
})
|
||||
.collect();
|
||||
SignalMessage::PresenceList { users }
|
||||
SignalMessage::PresenceList {
|
||||
version: default_signal_version(),
|
||||
users,
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast a message to ALL connected signal clients.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
//! Bob's CallSetup carries Alice's reflex addr — cross-wired
|
||||
//! through two relays + a federation link.
|
||||
|
||||
use wzp_proto::{CallAcceptMode, SignalMessage};
|
||||
use wzp_proto::{CallAcceptMode, SignalMessage, default_signal_version};
|
||||
use wzp_relay::call_registry::CallRegistry;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -42,6 +42,7 @@ const RELAY_B_ADDR: &str = "203.0.113.10:4433";
|
||||
/// Helper that Alice's place_call sends.
|
||||
fn alice_offer(call_id: &str) -> SignalMessage {
|
||||
SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -84,6 +85,7 @@ fn relay_a_handle_offer(reg_a: &mut CallRegistry, offer: &SignalMessage) -> Sign
|
||||
// Build the federation envelope the main loop would
|
||||
// broadcast.
|
||||
SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(offer.clone()),
|
||||
origin_relay_fp: RELAY_A_TLS_FP.into(),
|
||||
}
|
||||
@@ -97,6 +99,7 @@ fn relay_b_handle_forwarded_offer(reg_b: &mut CallRegistry, forward: &SignalMess
|
||||
SignalMessage::FederatedSignalForward {
|
||||
inner,
|
||||
origin_relay_fp,
|
||||
..
|
||||
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
||||
_ => panic!("not a forward"),
|
||||
};
|
||||
@@ -123,6 +126,7 @@ fn relay_b_handle_forwarded_offer(reg_b: &mut CallRegistry, forward: &SignalMess
|
||||
/// Bob's answer — AcceptTrusted with his reflex addr.
|
||||
fn bob_answer(call_id: &str) -> SignalMessage {
|
||||
SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.into(),
|
||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||
identity_pub: None,
|
||||
@@ -166,12 +170,14 @@ fn relay_b_handle_local_answer(
|
||||
|
||||
// Forward the answer back over federation.
|
||||
let forward = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(answer.clone()),
|
||||
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
||||
};
|
||||
|
||||
// Local CallSetup for Bob — peer_direct_addr = Alice's addr.
|
||||
let setup_for_bob = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: format!("call-{call_id}"),
|
||||
relay_addr: RELAY_B_ADDR.into(),
|
||||
@@ -194,6 +200,7 @@ fn relay_a_handle_forwarded_answer(
|
||||
SignalMessage::FederatedSignalForward {
|
||||
inner,
|
||||
origin_relay_fp,
|
||||
..
|
||||
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
||||
_ => panic!("not a forward"),
|
||||
};
|
||||
@@ -215,6 +222,7 @@ fn relay_a_handle_forwarded_answer(
|
||||
|
||||
// Alice's CallSetup — peer_direct_addr = Bob's addr.
|
||||
SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: format!("call-{call_id}"),
|
||||
relay_addr: RELAY_A_ADDR.into(),
|
||||
@@ -312,6 +320,7 @@ fn cross_relay_loop_prevention_drops_self_sourced_forward() {
|
||||
// A FederatedSignalForward that circles back to the origin
|
||||
// relay should be dropped before it hits the call registry.
|
||||
let forward = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(alice_offer("c-loop")),
|
||||
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_relay::config::{PeerConfig, TrustedConfig};
|
||||
use wzp_relay::event_log::EventLogger;
|
||||
use wzp_relay::federation::{FederationManager, room_hash};
|
||||
@@ -348,7 +348,9 @@ async fn broadcast_signal_sends_to_all_peers() {
|
||||
.expect("some message");
|
||||
|
||||
match hello {
|
||||
SignalMessage::FederationHello { tls_fingerprint } => {
|
||||
SignalMessage::FederationHello {
|
||||
tls_fingerprint, ..
|
||||
} => {
|
||||
assert_eq!(tls_fingerprint, "test-relay-fp-abc123");
|
||||
}
|
||||
other => panic!(
|
||||
@@ -367,6 +369,7 @@ async fn broadcast_signal_sends_to_all_peers() {
|
||||
|
||||
// Now call broadcast_signal on the FM
|
||||
let test_msg = SignalMessage::FederatedSignalForward {
|
||||
version: default_signal_version(),
|
||||
inner: Box::new(SignalMessage::Reflect),
|
||||
origin_relay_fp: "other-relay-fp".into(),
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||
|
||||
use wzp_client::perform_handshake;
|
||||
use wzp_crypto::{KeyExchange, WarzoneKeyExchange};
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_relay::handshake::accept_handshake;
|
||||
use wzp_transport::{QuinnTransport, client_config, create_endpoint, server_config};
|
||||
|
||||
@@ -129,6 +129,7 @@ async fn handshake_rejects_v1_protocol_version() {
|
||||
let signature = kx.sign(&sign_data);
|
||||
|
||||
let v1_offer = SignalMessage::CallOffer {
|
||||
version: 1,
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
@@ -255,7 +256,7 @@ async fn auth_then_handshake() {
|
||||
.expect("should receive a message");
|
||||
|
||||
let token = match auth_msg {
|
||||
SignalMessage::AuthToken { token } => token,
|
||||
SignalMessage::AuthToken { token, .. } => token,
|
||||
other => panic!(
|
||||
"expected AuthToken, got {:?}",
|
||||
std::mem::discriminant(&other)
|
||||
@@ -273,6 +274,7 @@ async fn auth_then_handshake() {
|
||||
|
||||
// Caller side: send AuthToken first, then perform_handshake.
|
||||
let auth = SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: "bearer-test-token-12345".to_string(),
|
||||
};
|
||||
client_transport
|
||||
@@ -344,6 +346,7 @@ async fn handshake_rejects_bad_signature() {
|
||||
}
|
||||
|
||||
let bad_offer = SignalMessage::CallOffer {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
ephemeral_pub,
|
||||
signature,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
//! to reason about, no real network, and what we actually want to
|
||||
//! test is the cross-wiring logic, not the whole signal stack.
|
||||
|
||||
use wzp_proto::{CallAcceptMode, SignalMessage};
|
||||
use wzp_proto::{CallAcceptMode, SignalMessage, default_signal_version};
|
||||
use wzp_relay::call_registry::CallRegistry;
|
||||
|
||||
/// Helper: simulate the relay's handling of a DirectCallOffer. In
|
||||
@@ -77,6 +77,7 @@ fn handle_answer_and_build_setups(
|
||||
};
|
||||
|
||||
let setup_for_caller = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
room: room.clone(),
|
||||
relay_addr: "203.0.113.5:4433".into(),
|
||||
@@ -85,6 +86,7 @@ fn handle_answer_and_build_setups(
|
||||
peer_mapped_addr: None,
|
||||
};
|
||||
let setup_for_callee = SignalMessage::CallSetup {
|
||||
version: default_signal_version(),
|
||||
call_id,
|
||||
room,
|
||||
relay_addr: "203.0.113.5:4433".into(),
|
||||
@@ -97,6 +99,7 @@ fn handle_answer_and_build_setups(
|
||||
|
||||
fn mk_offer(call_id: &str, caller_reflexive_addr: Option<&str>) -> SignalMessage {
|
||||
SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: "alice".into(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: "bob".into(),
|
||||
@@ -118,6 +121,7 @@ fn mk_answer(
|
||||
callee_reflexive_addr: Option<&str>,
|
||||
) -> SignalMessage {
|
||||
SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.into(),
|
||||
accept_mode: mode,
|
||||
identity_pub: None,
|
||||
|
||||
@@ -63,6 +63,7 @@ async fn spawn_mock_relay() -> (SocketAddr, tokio::task::JoinHandle<()>) {
|
||||
Ok(Some(SignalMessage::RegisterPresence { .. })) => {
|
||||
let _ = t
|
||||
.send_signal(&SignalMessage::RegisterPresenceAck {
|
||||
version: 1,
|
||||
success: true,
|
||||
error: None,
|
||||
relay_build: None,
|
||||
@@ -74,6 +75,7 @@ async fn spawn_mock_relay() -> (SocketAddr, tokio::task::JoinHandle<()>) {
|
||||
Ok(Some(SignalMessage::Reflect)) => {
|
||||
let _ = t
|
||||
.send_signal(&SignalMessage::ReflectResponse {
|
||||
version: 1,
|
||||
observed_addr: observed_addr.to_string(),
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -30,7 +30,7 @@ use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use wzp_proto::{MediaTransport, SignalMessage};
|
||||
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||
use wzp_transport::{QuinnTransport, client_config, create_endpoint, server_config};
|
||||
|
||||
/// Spawn a minimal mock relay that loops over `recv_signal`,
|
||||
@@ -49,6 +49,7 @@ async fn spawn_mock_relay_with_reflect(
|
||||
match server_transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::Reflect)) => {
|
||||
let resp = SignalMessage::ReflectResponse {
|
||||
version: default_signal_version(),
|
||||
observed_addr: observed.to_string(),
|
||||
};
|
||||
// If the send fails the client has gone; just exit.
|
||||
@@ -164,7 +165,7 @@ async fn reflect_happy_path() {
|
||||
.expect("some message");
|
||||
|
||||
let observed_addr = match resp {
|
||||
SignalMessage::ReflectResponse { observed_addr } => observed_addr,
|
||||
SignalMessage::ReflectResponse { observed_addr, .. } => observed_addr,
|
||||
other => panic!(
|
||||
"expected ReflectResponse, got {:?}",
|
||||
std::mem::discriminant(&other)
|
||||
@@ -251,7 +252,7 @@ async fn reflect_two_clients_distinct_ports() {
|
||||
.expect("ok")
|
||||
.expect("some");
|
||||
match resp {
|
||||
SignalMessage::ReflectResponse { observed_addr } => observed_addr,
|
||||
SignalMessage::ReflectResponse { observed_addr, .. } => observed_addr,
|
||||
_ => panic!("wrong variant"),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ use tower_http::services::ServeDir;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use wzp_client::call::{CallConfig, CallDecoder, CallEncoder};
|
||||
use wzp_proto::MediaTransport;
|
||||
use wzp_proto::{MediaTransport, default_signal_version};
|
||||
|
||||
mod metrics;
|
||||
use metrics::WebMetrics;
|
||||
@@ -297,6 +297,7 @@ async fn handle_ws(socket: WebSocket, room: String, state: AppState) {
|
||||
// Send auth token to relay (if auth is enabled)
|
||||
if let Some(ref token) = browser_token {
|
||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||
version: default_signal_version(),
|
||||
token: token.clone(),
|
||||
};
|
||||
if let Err(e) = transport.send_signal(&auth).await {
|
||||
|
||||
@@ -168,6 +168,7 @@ async fn run_signal_task(
|
||||
Ok(Ok(Some(wzp_proto::SignalMessage::QualityDirective {
|
||||
recommended_profile,
|
||||
reason,
|
||||
..
|
||||
}))) => {
|
||||
let idx = profile_to_index(&recommended_profile);
|
||||
info!(
|
||||
|
||||
@@ -35,7 +35,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tauri::{Emitter, Manager};
|
||||
use tokio::sync::Mutex;
|
||||
use wzp_proto::MediaTransport;
|
||||
use wzp_proto::{MediaTransport, default_signal_version};
|
||||
|
||||
// ─── Call-flow debug logs (GUI-gated) ────────────────────────────────
|
||||
//
|
||||
@@ -615,6 +615,7 @@ async fn connect(
|
||||
// Send our report
|
||||
if let Some(ref t) = transport_for_report {
|
||||
let report = wzp_proto::SignalMessage::MediaPathReport {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id_for_report.clone(),
|
||||
direct_ok: local_direct_ok,
|
||||
race_winner: format!("{:?}", local_winner),
|
||||
@@ -1247,7 +1248,7 @@ fn do_register_signal(
|
||||
relay: String,
|
||||
) -> impl std::future::Future<Output = Result<String, String>> + Send {
|
||||
async move {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
emit_call_debug(
|
||||
&app,
|
||||
@@ -1316,6 +1317,7 @@ fn do_register_signal(
|
||||
let alias = derive_alias(&seed);
|
||||
transport
|
||||
.send_signal(&SignalMessage::RegisterPresence {
|
||||
version: default_signal_version(),
|
||||
identity_pub,
|
||||
signature: vec![],
|
||||
alias: Some(alias),
|
||||
@@ -1375,7 +1377,7 @@ fn do_register_signal(
|
||||
let signal_state = signal_state_loop.clone();
|
||||
loop {
|
||||
match transport.recv_signal().await {
|
||||
Ok(Some(SignalMessage::CallRinging { call_id })) => {
|
||||
Ok(Some(SignalMessage::CallRinging { call_id, .. })) => {
|
||||
tracing::info!(%call_id, "signal: CallRinging");
|
||||
emit_call_debug(
|
||||
&app_clone,
|
||||
@@ -1453,6 +1455,7 @@ fn do_register_signal(
|
||||
peer_direct_addr,
|
||||
peer_local_addrs,
|
||||
peer_mapped_addr,
|
||||
..
|
||||
})) => {
|
||||
// Phase 3: peer_direct_addr carries the OTHER party's
|
||||
// reflex addr. Phase 5.5: peer_local_addrs carries
|
||||
@@ -1512,6 +1515,7 @@ fn do_register_signal(
|
||||
call_id,
|
||||
direct_ok,
|
||||
race_winner,
|
||||
..
|
||||
})) => {
|
||||
// Phase 6: the peer is telling us whether
|
||||
// their direct path succeeded. Fire the
|
||||
@@ -1543,6 +1547,7 @@ fn do_register_signal(
|
||||
local_addrs,
|
||||
mapped_addr,
|
||||
generation,
|
||||
..
|
||||
})) => {
|
||||
// Phase 8: peer re-gathered candidates after a
|
||||
// network change. Emit to JS for UI notification
|
||||
@@ -1586,6 +1591,7 @@ fn do_register_signal(
|
||||
allocation,
|
||||
probe_time_ms,
|
||||
external_ip,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(
|
||||
%call_id,
|
||||
@@ -1647,6 +1653,7 @@ fn do_register_signal(
|
||||
let _ = t
|
||||
.send_signal(
|
||||
&wzp_proto::SignalMessage::HardNatBirthdayStart {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id_bg,
|
||||
acceptor_port_count: result.succeeded,
|
||||
acceptor_ports: ext_ports,
|
||||
@@ -1661,7 +1668,7 @@ fn do_register_signal(
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(Some(SignalMessage::PresenceList { users })) => {
|
||||
Ok(Some(SignalMessage::PresenceList { users, .. })) => {
|
||||
tracing::info!(count = users.len(), "signal: PresenceList received");
|
||||
// Emit to JS frontend for lobby user list
|
||||
let user_list: Vec<serde_json::Value> = users
|
||||
@@ -1687,6 +1694,7 @@ fn do_register_signal(
|
||||
proposed_profile,
|
||||
local_loss_pct,
|
||||
local_rtt_ms,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(%call_id, %proposal_id, ?proposed_profile, "signal: UpgradeProposal from peer");
|
||||
emit_call_debug(
|
||||
@@ -1706,6 +1714,7 @@ fn do_register_signal(
|
||||
proposal_id,
|
||||
accepted,
|
||||
reason,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(%call_id, %proposal_id, accepted, ?reason, "signal: UpgradeResponse from peer");
|
||||
emit_call_debug(
|
||||
@@ -1722,6 +1731,7 @@ fn do_register_signal(
|
||||
call_id,
|
||||
proposal_id,
|
||||
confirmed_profile,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(%call_id, %proposal_id, ?confirmed_profile, "signal: UpgradeConfirm");
|
||||
emit_call_debug(
|
||||
@@ -1739,6 +1749,7 @@ fn do_register_signal(
|
||||
max_profile,
|
||||
loss_pct,
|
||||
rtt_ms,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(%call_id, ?max_profile, "signal: QualityCapability from peer");
|
||||
emit_call_debug(
|
||||
@@ -1758,6 +1769,7 @@ fn do_register_signal(
|
||||
acceptor_port_count,
|
||||
acceptor_ports,
|
||||
external_ip,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(
|
||||
%call_id,
|
||||
@@ -1786,7 +1798,7 @@ fn do_register_signal(
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(Some(SignalMessage::ReflectResponse { observed_addr })) => {
|
||||
Ok(Some(SignalMessage::ReflectResponse { observed_addr, .. })) => {
|
||||
// "STUN for QUIC" response — the relay told us our
|
||||
// own server-reflexive address. If a Tauri command
|
||||
// is currently awaiting this, fire the oneshot;
|
||||
@@ -2009,7 +2021,7 @@ async fn place_call(
|
||||
app: tauri::AppHandle,
|
||||
target_fp: String,
|
||||
) -> Result<(), String> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
emit_call_debug(
|
||||
&app,
|
||||
@@ -2140,6 +2152,7 @@ async fn place_call(
|
||||
tracing::info!(%call_id, %target_fp, reflex = ?own_reflex, mapped = ?caller_mapped_addr, "place_call: sending DirectCallOffer");
|
||||
transport
|
||||
.send_signal(&SignalMessage::DirectCallOffer {
|
||||
version: default_signal_version(),
|
||||
caller_fingerprint: sig.fingerprint.clone(),
|
||||
caller_alias: None,
|
||||
target_fingerprint: target_fp.clone(),
|
||||
@@ -2199,6 +2212,7 @@ async fn place_call(
|
||||
if let Some(ref t) = sig.transport {
|
||||
let _ = t
|
||||
.send_signal(&SignalMessage::HardNatProbe {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id_bg,
|
||||
port_sequence: result.observed_ports,
|
||||
allocation: alloc_str,
|
||||
@@ -2228,7 +2242,7 @@ async fn answer_call(
|
||||
call_id: String,
|
||||
mode: i32,
|
||||
) -> Result<(), String> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
let accept_mode = match mode {
|
||||
0 => wzp_proto::CallAcceptMode::Reject,
|
||||
1 => wzp_proto::CallAcceptMode::AcceptTrusted,
|
||||
@@ -2375,6 +2389,7 @@ async fn answer_call(
|
||||
tracing::info!(%call_id, ?accept_mode, reflex = ?own_reflex, mapped = ?callee_mapped_addr, "answer_call: sending DirectCallAnswer");
|
||||
transport
|
||||
.send_signal(&SignalMessage::DirectCallAnswer {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id.clone(),
|
||||
accept_mode,
|
||||
identity_pub: None,
|
||||
@@ -2437,6 +2452,7 @@ async fn answer_call(
|
||||
if let Some(ref t) = sig.transport {
|
||||
let _ = t
|
||||
.send_signal(&wzp_proto::SignalMessage::HardNatProbe {
|
||||
version: default_signal_version(),
|
||||
call_id: call_id_bg,
|
||||
port_sequence: result.observed_ports,
|
||||
allocation: alloc_str,
|
||||
@@ -2475,7 +2491,7 @@ async fn answer_call(
|
||||
/// or temporarily unreachable for reflect but the call can still
|
||||
/// proceed with STUN-discovered addresses.
|
||||
async fn try_reflect_own_addr(state: &Arc<AppState>) -> Result<Option<String>, String> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<std::net::SocketAddr>();
|
||||
let transport = {
|
||||
let mut sig = state.signal.lock().await;
|
||||
@@ -2562,7 +2578,7 @@ async fn try_stun_fallback(state: &Arc<AppState>) -> Result<Option<String>, Stri
|
||||
/// with `new URL(...)` / a regex if needed.
|
||||
#[tauri::command]
|
||||
async fn get_reflected_address(state: tauri::State<'_, Arc<AppState>>) -> Result<String, String> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<std::net::SocketAddr>();
|
||||
let transport = {
|
||||
let mut sig = state.signal.lock().await;
|
||||
@@ -2766,7 +2782,7 @@ async fn hangup_call(
|
||||
state: tauri::State<'_, Arc<AppState>>,
|
||||
app: tauri::AppHandle,
|
||||
) -> Result<(), String> {
|
||||
use wzp_proto::SignalMessage;
|
||||
use wzp_proto::{SignalMessage, default_signal_version};
|
||||
|
||||
emit_call_debug(&app, "hangup_call:start", serde_json::json!({}));
|
||||
|
||||
@@ -2778,6 +2794,7 @@ async fn hangup_call(
|
||||
if let Some(ref transport) = sig.transport {
|
||||
match transport
|
||||
.send_signal(&SignalMessage::Hangup {
|
||||
version: default_signal_version(),
|
||||
reason: wzp_proto::HangupReason::Normal,
|
||||
call_id: None,
|
||||
})
|
||||
|
||||
@@ -1321,7 +1321,7 @@ Statuses (in order of progression):
|
||||
| T2.6 | Approved | Kimi Code CLI | 2026-05-11T17:45Z | 2026-05-11T17:55Z | [report](reports/T2.6-report.md) | Substance good (Prom metrics); bundled in 54c1a35. Consolidated reviewer notes here. |
|
||||
| T3.1 | Approved | Kimi Code CLI | 2026-05-11T20:55Z | 2026-05-11T21:05Z | [report](reports/T3.1-report.md) | Approved. DashMap<String, Arc<RwLock<Room>>>; W13 resolved. One commit per task this time — good. Two minor process notes in report. |
|
||||
| T3.2 | Committed | Kimi Code CLI | 2026-05-11T21:15Z | 2026-05-11T21:25Z | [report](reports/T3.2-report.md) | timestamp_ms monotonic across rekey; doc + test. |
|
||||
| T3.3 | Open | — | — | — | — | — |
|
||||
| T3.3 | Pending Review | Kimi Code CLI | 2026-05-11T16:29Z | 2026-05-11T16:29Z | [report](reports/T3.3-report.md) | — |
|
||||
| T3.4 | Open | — | — | — | — | — |
|
||||
| T3.5 | Open | — | — | — | — | — |
|
||||
| T4.1 | Open | — | — | — | — | Skeleton — expand before claiming |
|
||||
@@ -1350,5 +1350,6 @@ Items currently waiting on the reviewer:
|
||||
- T1.8 — Per-stream anti-replay window with configurable size — report: reports/T1.8-report.md
|
||||
- T2.1 — Add `SignalMessage::TransportFeedback` — report: reports/T2.1-report.md
|
||||
- T2.2 — `BandwidthEstimator` in `wzp-proto::bandwidth` — report: reports/T2.2-report.md
|
||||
- T3.3 — SignalMessage version field — report: reports/T3.3-report.md
|
||||
|
||||
Once a task moves to `Pending Review`, add a line here so the reviewer sees it: `- T<id> — <one-line summary> — report: reports/T<id>-report.md`. The reviewer removes the line when they mark it `Approved` (or moves it back to the agent on `Changes Requested`).
|
||||
|
||||
100
docs/PRD/reports/T3.3-report.md
Normal file
100
docs/PRD/reports/T3.3-report.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# T3.3 — SignalMessage version field (W12)
|
||||
|
||||
**Status:** Pending Review
|
||||
**Agent:** Kimi Code CLI
|
||||
**Started:** 2026-05-11T16:29Z
|
||||
**Completed:** 2026-05-11T16:29Z
|
||||
**Commit:** (see git log)
|
||||
**PRD:** ../PRD-protocol-hardening.md
|
||||
|
||||
## What I changed
|
||||
|
||||
- `crates/wzp-proto/src/packet.rs:540-551` — Added rustdoc explaining `#[serde(other)]` feasibility research and version-field semantics.
|
||||
- `crates/wzp-proto/src/packet.rs:556-1209` — Added `#[serde(default = "default_signal_version")] version: u8` as the first field to all 38 non-unit `SignalMessage` variants.
|
||||
- `crates/wzp-proto/src/packet.rs:1217-1220` — Added `pub fn default_signal_version() -> u8 { 1 }`.
|
||||
- `crates/wzp-proto/src/packet.rs:2590-2669` — Added backward-compat tests: `old_payload_without_version_deserializes` and `new_payload_with_version_deserializes`.
|
||||
- `crates/wzp-proto/src/lib.rs:32-37` — Re-exported `default_signal_version`.
|
||||
- `crates/wzp-client/src/handshake.rs`, `crates/wzp-client/src/cli.rs`, `crates/wzp-client/src/ice_agent.rs`, `crates/wzp-client/src/reflect.rs`, `crates/wzp-client/src/analyzer.rs`, `crates/wzp-client/src/featherchat.rs`, `crates/wzp-client/tests/handshake_integration.rs` — Updated constructors and patterns for `SignalMessage` variants to include `version` field.
|
||||
- `crates/wzp-relay/src/main.rs`, `crates/wzp-relay/src/federation.rs`, `crates/wzp-relay/src/handshake.rs`, `crates/wzp-relay/src/probe.rs`, `crates/wzp-relay/src/relay_link.rs`, `crates/wzp-relay/src/room.rs`, `crates/wzp-relay/src/route.rs`, `crates/wzp-relay/src/signal_hub.rs` — Updated constructors and patterns for `SignalMessage` variants.
|
||||
- `crates/wzp-relay/tests/cross_relay_direct_call.rs`, `crates/wzp-relay/tests/federation.rs`, `crates/wzp-relay/tests/handshake_integration.rs`, `crates/wzp-relay/tests/hole_punching.rs`, `crates/wzp-relay/tests/multi_reflect.rs`, `crates/wzp-relay/tests/reflect.rs` — Updated test constructors and patterns.
|
||||
- `crates/wzp-android/src/engine.rs` — Updated constructors and patterns.
|
||||
- `crates/wzp-web/src/main.rs` — Updated import ordering (cargo fmt).
|
||||
- `crates/wzp-crypto/tests/featherchat_compat.rs` — Updated import ordering (cargo fmt).
|
||||
- `desktop/src-tauri/src/engine.rs`, `desktop/src-tauri/src/lib.rs` — Updated patterns and constructors.
|
||||
|
||||
## Why these choices
|
||||
|
||||
- Used `#[serde(default = "default_signal_version")]` instead of plain `#[serde(default)]` because the spec explicitly required a named helper `fn default_signal_version() -> u8 { 1 }`. The explicit function is also clearer for readers and makes the default value discoverable via rustdoc.
|
||||
- Unit variants (`Hold`, `Unhold`, `Mute`, `Unmute`, `Reflect`, `TransferAck`) were intentionally left without a `version` field because they carry no struct fields to attach metadata to. Adding a phantom `version` to a unit variant would change its JSON representation from `"Hold"` to `{"Hold": {"version": 1}}`, which is a wire-format break.
|
||||
- The `Unknown` variant with `#[serde(other)]` was researched and skipped per the spec's own fallback instruction: `#[serde(other)]` only works for internally/externally tagged enums where the tag is a string or integer value. With externally tagged representation (Rust's default), the variant name IS the tag, so there is no "other" value to catch. `bincode` also does not support `#[serde(other)]`. This limitation is documented in the `SignalMessage` rustdoc.
|
||||
- Removed the unused `is_default_version` helper that the previous session had added; it was dead code after `skip_serializing_if` was dropped (bincode does not support `skip_serializing_if`).
|
||||
|
||||
## Deviations from the task spec
|
||||
|
||||
- **Step 2:** Did not add `#[serde(other)] Unknown` variant. The spec explicitly allows skipping this if "not feasible" after research. Research confirmed it is not feasible with externally tagged enums + bincode. The limitation is documented in the `SignalMessage` rustdoc.
|
||||
- **Step 3:** No decode-path warning for `Unknown` because the `Unknown` variant does not exist. Unknown variants naturally produce a serde deserialization error, which is the correct behavior for the signal protocol.
|
||||
|
||||
## Verification output
|
||||
|
||||
```
|
||||
$ cargo test -p wzp-proto --lib
|
||||
running 121 tests
|
||||
...
|
||||
test result: ok. 121 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
|
||||
```
|
||||
|
||||
```
|
||||
$ cargo test -p wzp-proto -- transport_feedback
|
||||
running 2 tests
|
||||
test packet::tests::transport_feedback_default_version ... ok
|
||||
test packet::tests::transport_feedback_roundtrip ... ok
|
||||
|
||||
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 119 filtered out; finished in 0.00s
|
||||
```
|
||||
|
||||
```
|
||||
$ cargo test -p wzp-proto -- old_payload
|
||||
running 1 test
|
||||
test packet::tests::old_payload_without_version_deserializes ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 120 filtered out; finished in 0.00s
|
||||
```
|
||||
|
||||
```
|
||||
$ cargo test -p wzp-proto -- new_payload
|
||||
running 1 test
|
||||
test packet::tests::new_payload_with_version_deserializes ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 120 filtered out; finished in 0.00s
|
||||
```
|
||||
|
||||
```
|
||||
$ cargo test --workspace --exclude wzp-android --no-fail-fast
|
||||
... (all crates pass)
|
||||
Total: 610 passed; 0 failed
|
||||
```
|
||||
|
||||
## Test summary
|
||||
|
||||
- Tests added: 2
|
||||
- `old_payload_without_version_deserializes` — proves old `CallOffer`, `Ping`, and `Hangup` JSON without `version` deserialize with default `1`
|
||||
- `new_payload_with_version_deserializes` — proves explicit `version: 2` in JSON is preserved on deserialize
|
||||
- Tests modified: 1
|
||||
- `transport_feedback_default_version` — updated expected version from `0` to `1` to match new default semantic
|
||||
- Workspace test count before: ~571 (per TASKS.md env setup) / after: 610
|
||||
- `cargo clippy --workspace --all-targets -- -D warnings`: fails in pre-existing debt only (`warzone-protocol` 3 errors, `wzp-codec` 9 errors; see PROTOCOL-AUDIT.md). Crate touched by this task (`wzp-proto`) is clean.
|
||||
- `cargo fmt --all -- --check`: pass
|
||||
|
||||
## Risks / follow-ups
|
||||
|
||||
- **T3.2 status corruption:** The status board shows T3.2 as `Committed`, which is not a valid workflow status. Per the agent instructions, I did not touch already-reviewed tasks. The reviewer should flip T3.2 to `Approved` (its actual status from prior review).
|
||||
- Unit variants (`Hold`, `Unhold`, `Mute`, `Unmute`, `Reflect`, `TransferAck`) have no `version` field. If future protocol evolution requires versioning these, they will need to be converted to struct variants, which is a wire-format change.
|
||||
- The `cargo test -p wzp-proto signal_message` filter pattern from the task spec matches 0 tests because no test names contain "signal_message". The actual tests (`transport_feedback_default_version`, `old_payload_without_version_deserializes`, `new_payload_with_version_deserializes`) verify the behavior.
|
||||
|
||||
## Reviewer checklist (filled in by reviewer)
|
||||
|
||||
- [ ] Code matches PRD intent
|
||||
- [ ] Verification output is real (re-run if suspicious)
|
||||
- [ ] No backward-incompat surprises
|
||||
- [ ] Tests cover the new behavior
|
||||
- [ ] Approved
|
||||
Reference in New Issue
Block a user