T3.3: SignalMessage version field
This commit is contained in:
@@ -23,7 +23,7 @@ use wzp_fec::{RaptorQFecDecoder, RaptorQFecEncoder};
|
|||||||
use wzp_proto::{
|
use wzp_proto::{
|
||||||
AdaptiveQualityController, AudioDecoder, AudioEncoder, CodecId, FecDecoder, FecEncoder,
|
AdaptiveQualityController, AudioDecoder, AudioEncoder, CodecId, FecDecoder, FecEncoder,
|
||||||
MediaHeader, MediaPacket, MediaTransport, MediaType, QualityController, QualityProfile,
|
MediaHeader, MediaPacket, MediaTransport, MediaType, QualityController, QualityProfile,
|
||||||
SignalMessage,
|
SignalMessage, default_signal_version,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::audio_ring::AudioRing;
|
use crate::audio_ring::AudioRing;
|
||||||
@@ -321,11 +321,12 @@ impl WzpEngine {
|
|||||||
|
|
||||||
// Auth if token provided
|
// Auth if token provided
|
||||||
if let Some(ref tok) = token {
|
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
|
// Register presence
|
||||||
let _ = transport.send_signal(&SignalMessage::RegisterPresence {
|
let _ = transport.send_signal(&SignalMessage::RegisterPresence {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
alias: alias.clone(),
|
alias: alias.clone(),
|
||||||
@@ -350,7 +351,7 @@ impl WzpEngine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match transport.recv_signal().await {
|
match transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::CallRinging { call_id })) => {
|
Ok(Some(SignalMessage::CallRinging { call_id, ..})) => {
|
||||||
info!(call_id = %call_id, "signal: ringing");
|
info!(call_id = %call_id, "signal: ringing");
|
||||||
let mut stats = signal_state.stats.lock().unwrap();
|
let mut stats = signal_state.stats.lock().unwrap();
|
||||||
stats.state = crate::stats::CallState::Ringing;
|
stats.state = crate::stats::CallState::Ringing;
|
||||||
@@ -522,6 +523,7 @@ async fn run_call(
|
|||||||
let signature = kx.sign(&sign_data);
|
let signature = kx.sign(&sign_data);
|
||||||
|
|
||||||
let offer = SignalMessage::CallOffer {
|
let offer = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
@@ -1223,6 +1225,7 @@ async fn run_call(
|
|||||||
Ok(Some(SignalMessage::RoomUpdate {
|
Ok(Some(SignalMessage::RoomUpdate {
|
||||||
count,
|
count,
|
||||||
participants,
|
participants,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
info!(count, "RoomUpdate received");
|
info!(count, "RoomUpdate received");
|
||||||
let members: Vec<crate::stats::RoomMember> = participants
|
let members: Vec<crate::stats::RoomMember> = participants
|
||||||
@@ -1240,6 +1243,7 @@ async fn run_call(
|
|||||||
Ok(Some(SignalMessage::QualityDirective {
|
Ok(Some(SignalMessage::QualityDirective {
|
||||||
recommended_profile,
|
recommended_profile,
|
||||||
reason,
|
reason,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
let idx = profile_to_index(&recommended_profile);
|
let idx = profile_to_index(&recommended_profile);
|
||||||
info!(
|
info!(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use std::time::{Duration, Instant};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use wzp_proto::{CodecId, MediaPacket, MediaTransport};
|
use wzp_proto::{CodecId, MediaPacket, MediaTransport, default_signal_version};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// CLI
|
// CLI
|
||||||
@@ -919,6 +919,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// Auth if token provided
|
// Auth if token provided
|
||||||
if let Some(ref token) = args.token {
|
if let Some(ref token) = args.token {
|
||||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
};
|
};
|
||||||
transport.send_signal(&auth).await?;
|
transport.send_signal(&auth).await?;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::sync::Arc;
|
|||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use wzp_client::call::{CallConfig, CallDecoder, CallEncoder};
|
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
|
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)
|
// Send auth token if provided (relay with --auth-url expects this first)
|
||||||
if let Some(ref token) = cli.token {
|
if let Some(ref token) = cli.token {
|
||||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
};
|
};
|
||||||
transport.send_signal(&auth).await?;
|
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");
|
info!(total_source, total_repair, total_bytes, "done — closing");
|
||||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
};
|
};
|
||||||
@@ -632,6 +634,7 @@ async fn run_file_mode(
|
|||||||
|
|
||||||
// Send Hangup signal so the relay knows we're done
|
// Send Hangup signal so the relay knows we're done
|
||||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
};
|
};
|
||||||
@@ -769,7 +772,7 @@ async fn run_signal_mode(
|
|||||||
token: Option<String>,
|
token: Option<String>,
|
||||||
call_target: Option<String>,
|
call_target: Option<String>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
let identity = seed.derive_identity();
|
let identity = seed.derive_identity();
|
||||||
let pub_id = identity.public_identity();
|
let pub_id = identity.public_identity();
|
||||||
@@ -792,13 +795,17 @@ async fn run_signal_mode(
|
|||||||
// Auth if token provided
|
// Auth if token provided
|
||||||
if let Some(ref tok) = token {
|
if let Some(ref tok) = token {
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::AuthToken { token: tok.clone() })
|
.send_signal(&SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
|
token: tok.clone(),
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register presence (signature not verified in Phase 1)
|
// Register presence (signature not verified in Phase 1)
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::RegisterPresence {
|
.send_signal(&SignalMessage::RegisterPresence {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
signature: vec![], // Phase 1: not verified
|
signature: vec![], // Phase 1: not verified
|
||||||
alias: None,
|
alias: None,
|
||||||
@@ -835,6 +842,7 @@ async fn run_signal_mode(
|
|||||||
|
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::DirectCallOffer {
|
.send_signal(&SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: fp.clone(),
|
caller_fingerprint: fp.clone(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: target.clone(),
|
target_fingerprint: target.clone(),
|
||||||
@@ -861,7 +869,7 @@ async fn run_signal_mode(
|
|||||||
loop {
|
loop {
|
||||||
match signal_transport.recv_signal().await {
|
match signal_transport.recv_signal().await {
|
||||||
Ok(Some(msg)) => match msg {
|
Ok(Some(msg)) => match msg {
|
||||||
SignalMessage::CallRinging { call_id } => {
|
SignalMessage::CallRinging { call_id, .. } => {
|
||||||
info!(call_id = %call_id, "ringing...");
|
info!(call_id = %call_id, "ringing...");
|
||||||
}
|
}
|
||||||
SignalMessage::DirectCallOffer {
|
SignalMessage::DirectCallOffer {
|
||||||
@@ -879,6 +887,7 @@ async fn run_signal_mode(
|
|||||||
// Auto-accept for CLI testing
|
// Auto-accept for CLI testing
|
||||||
let _ = signal_transport
|
let _ = signal_transport
|
||||||
.send_signal(&SignalMessage::DirectCallAnswer {
|
.send_signal(&SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id,
|
call_id,
|
||||||
accept_mode: wzp_proto::CallAcceptMode::AcceptGeneric,
|
accept_mode: wzp_proto::CallAcceptMode::AcceptGeneric,
|
||||||
identity_pub: Some(identity_pub),
|
identity_pub: Some(identity_pub),
|
||||||
@@ -908,6 +917,7 @@ async fn run_signal_mode(
|
|||||||
peer_direct_addr: _,
|
peer_direct_addr: _,
|
||||||
peer_local_addrs: _,
|
peer_local_addrs: _,
|
||||||
peer_mapped_addr: _,
|
peer_mapped_addr: _,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
info!(call_id = %call_id, room = %room, relay = %setup_relay, "call setup — connecting to media room");
|
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() => {
|
_ = tokio::signal::ctrl_c() => {
|
||||||
info!("hanging up...");
|
info!("hanging up...");
|
||||||
let _ = signal_transport.send_signal(&SignalMessage::Hangup {
|
let _ = signal_transport.send_signal(&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
}).await;
|
}).await;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
//! 5. Connects QUIC to relay for media
|
//! 5. Connects QUIC to relay for media
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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).
|
/// featherChat CallSignal types (mirrors warzone-protocol::message::CallSignalType).
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -152,6 +152,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn payload_roundtrip() {
|
fn payload_roundtrip() {
|
||||||
let signal = SignalMessage::CallOffer {
|
let signal = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [1u8; 32],
|
identity_pub: [1u8; 32],
|
||||||
ephemeral_pub: [2u8; 32],
|
ephemeral_pub: [2u8; 32],
|
||||||
signature: vec![3u8; 64],
|
signature: vec![3u8; 64],
|
||||||
@@ -172,6 +173,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn signal_type_mapping() {
|
fn signal_type_mapping() {
|
||||||
let offer = SignalMessage::CallOffer {
|
let offer = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [0; 32],
|
identity_pub: [0; 32],
|
||||||
ephemeral_pub: [0; 32],
|
ephemeral_pub: [0; 32],
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
@@ -183,6 +185,7 @@ mod tests {
|
|||||||
assert!(matches!(signal_to_call_type(&offer), CallSignalType::Offer));
|
assert!(matches!(signal_to_call_type(&offer), CallSignalType::Offer));
|
||||||
|
|
||||||
let hangup = SignalMessage::Hangup {
|
let hangup = SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
};
|
};
|
||||||
@@ -209,6 +212,7 @@ mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
let transfer = SignalMessage::Transfer {
|
let transfer = SignalMessage::Transfer {
|
||||||
|
version: default_signal_version(),
|
||||||
target_fingerprint: "abc".to_string(),
|
target_fingerprint: "abc".to_string(),
|
||||||
relay_addr: None,
|
relay_addr: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
//! send `CallOffer` → recv `CallAnswer` → derive shared `CryptoSession`.
|
//! send `CallOffer` → recv `CallAnswer` → derive shared `CryptoSession`.
|
||||||
|
|
||||||
use wzp_crypto::{CryptoSession, KeyExchange, WarzoneKeyExchange};
|
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.
|
/// Errors that can occur during the client-side cryptographic handshake.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -78,6 +80,7 @@ pub async fn perform_handshake(
|
|||||||
|
|
||||||
// 4. Send CallOffer
|
// 4. Send CallOffer
|
||||||
let offer = SignalMessage::CallOffer {
|
let offer = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
@@ -112,6 +115,7 @@ pub async fn perform_handshake(
|
|||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
chosen_profile,
|
chosen_profile,
|
||||||
|
..
|
||||||
} => (identity_pub, ephemeral_pub, signature, chosen_profile),
|
} => (identity_pub, ephemeral_pub, signature, chosen_profile),
|
||||||
SignalMessage::Hangup {
|
SignalMessage::Hangup {
|
||||||
reason: HangupReason::ProtocolVersionMismatch { server_supported },
|
reason: HangupReason::ProtocolVersionMismatch { server_supported },
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::net::SocketAddr;
|
|||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
use crate::dual_path::PeerCandidates;
|
use crate::dual_path::PeerCandidates;
|
||||||
use crate::portmap;
|
use crate::portmap;
|
||||||
@@ -133,6 +133,7 @@ impl IceAgent {
|
|||||||
let candidates = self.gather().await;
|
let candidates = self.gather().await;
|
||||||
|
|
||||||
let update = SignalMessage::CandidateUpdate {
|
let update = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: self.call_id.clone(),
|
call_id: self.call_id.clone(),
|
||||||
reflexive_addr: candidates.reflexive.map(|a| a.to_string()),
|
reflexive_addr: candidates.reflexive.map(|a| a.to_string()),
|
||||||
local_addrs: candidates.local.iter().map(|a| a.to_string()).collect(),
|
local_addrs: candidates.local.iter().map(|a| a.to_string()).collect(),
|
||||||
@@ -206,6 +207,7 @@ mod tests {
|
|||||||
|
|
||||||
// First update (gen=1) should succeed.
|
// First update (gen=1) should succeed.
|
||||||
let update1 = SignalMessage::CandidateUpdate {
|
let update1 = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-call".into(),
|
call_id: "test-call".into(),
|
||||||
reflexive_addr: Some("203.0.113.5:4433".into()),
|
reflexive_addr: Some("203.0.113.5:4433".into()),
|
||||||
local_addrs: vec!["192.168.1.10:4433".into()],
|
local_addrs: vec!["192.168.1.10:4433".into()],
|
||||||
@@ -223,6 +225,7 @@ mod tests {
|
|||||||
|
|
||||||
// Same generation (gen=1) should be rejected.
|
// Same generation (gen=1) should be rejected.
|
||||||
let update1b = SignalMessage::CandidateUpdate {
|
let update1b = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-call".into(),
|
call_id: "test-call".into(),
|
||||||
reflexive_addr: Some("198.51.100.9:4433".into()),
|
reflexive_addr: Some("198.51.100.9:4433".into()),
|
||||||
local_addrs: vec![],
|
local_addrs: vec![],
|
||||||
@@ -233,6 +236,7 @@ mod tests {
|
|||||||
|
|
||||||
// Older generation (gen=0) should be rejected.
|
// Older generation (gen=0) should be rejected.
|
||||||
let update0 = SignalMessage::CandidateUpdate {
|
let update0 = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-call".into(),
|
call_id: "test-call".into(),
|
||||||
reflexive_addr: Some("10.0.0.1:4433".into()),
|
reflexive_addr: Some("10.0.0.1:4433".into()),
|
||||||
local_addrs: vec![],
|
local_addrs: vec![],
|
||||||
@@ -243,6 +247,7 @@ mod tests {
|
|||||||
|
|
||||||
// Newer generation (gen=2) should succeed.
|
// Newer generation (gen=2) should succeed.
|
||||||
let update2 = SignalMessage::CandidateUpdate {
|
let update2 = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-call".into(),
|
call_id: "test-call".into(),
|
||||||
reflexive_addr: Some("198.51.100.9:5555".into()),
|
reflexive_addr: Some("198.51.100.9:5555".into()),
|
||||||
local_addrs: vec![],
|
local_addrs: vec![],
|
||||||
@@ -287,6 +292,7 @@ mod tests {
|
|||||||
let agent = IceAgent::new("test-call".into(), IceAgentConfig::default());
|
let agent = IceAgent::new("test-call".into(), IceAgentConfig::default());
|
||||||
|
|
||||||
let update = SignalMessage::CandidateUpdate {
|
let update = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-call".into(),
|
call_id: "test-call".into(),
|
||||||
reflexive_addr: Some("203.0.113.5:4433".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()],
|
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 agent = IceAgent::new("test".into(), IceAgentConfig::default());
|
||||||
|
|
||||||
let update = SignalMessage::CandidateUpdate {
|
let update = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test".into(),
|
call_id: "test".into(),
|
||||||
reflexive_addr: None,
|
reflexive_addr: None,
|
||||||
local_addrs: vec![],
|
local_addrs: vec![],
|
||||||
@@ -333,6 +340,7 @@ mod tests {
|
|||||||
let agent = IceAgent::new("test".into(), IceAgentConfig::default());
|
let agent = IceAgent::new("test".into(), IceAgentConfig::default());
|
||||||
|
|
||||||
let update = SignalMessage::CandidateUpdate {
|
let update = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test".into(),
|
call_id: "test".into(),
|
||||||
reflexive_addr: Some("not-an-addr".into()),
|
reflexive_addr: Some("not-an-addr".into()),
|
||||||
local_addrs: vec![
|
local_addrs: vec![
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use std::net::SocketAddr;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use wzp_proto::{MediaTransport, SignalMessage};
|
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||||
use wzp_transport::{QuinnTransport, client_config, create_endpoint};
|
use wzp_transport::{QuinnTransport, client_config, create_endpoint};
|
||||||
|
|
||||||
/// Result of one probe against one relay. Always returned so the
|
/// 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.
|
// path does in desktop/src-tauri/src/lib.rs register_signal.
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::RegisterPresence {
|
.send_signal(&SignalMessage::RegisterPresence {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [0u8; 32],
|
identity_pub: [0u8; 32],
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
@@ -150,7 +151,7 @@ pub async fn probe_reflect_addr(
|
|||||||
.map_err(|e| format!("send Reflect: {e}"))?;
|
.map_err(|e| format!("send Reflect: {e}"))?;
|
||||||
|
|
||||||
match transport.recv_signal().await {
|
match transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::ReflectResponse { observed_addr })) => {
|
Ok(Some(SignalMessage::ReflectResponse { observed_addr, .. })) => {
|
||||||
let parsed: SocketAddr = observed_addr
|
let parsed: SocketAddr = observed_addr
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| format!("parse observed_addr {observed_addr:?}: {e}"))?;
|
.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::packet::MediaPacket;
|
||||||
use wzp_proto::traits::{MediaTransport, PathQuality};
|
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).
|
/// 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 bad_signature = kx.sign(b"wrong-data-intentionally");
|
||||||
|
|
||||||
let offer = SignalMessage::CallOffer {
|
let offer = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature: bad_signature,
|
signature: bad_signature,
|
||||||
@@ -197,6 +198,7 @@ async fn client_receives_protocol_version_mismatch() {
|
|||||||
|
|
||||||
// Respond with ProtocolVersionMismatch.
|
// Respond with ProtocolVersionMismatch.
|
||||||
let mismatch = SignalMessage::Hangup {
|
let mismatch = SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
||||||
server_supported: vec![3],
|
server_supported: vec![3],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! 3. Auth: WZP auth module request/response matches FC's /v1/auth/validate contract
|
//! 3. Auth: WZP auth module request/response matches FC's /v1/auth/validate contract
|
||||||
//! 4. Mnemonic: BIP39 interop between both implementations
|
//! 4. Mnemonic: BIP39 interop between both implementations
|
||||||
|
|
||||||
use wzp_proto::KeyExchange;
|
use wzp_proto::{KeyExchange, default_signal_version};
|
||||||
|
|
||||||
// ─── Identity Compatibility (WZP-FC-8) ──────────────────────────────────────
|
// ─── Identity Compatibility (WZP-FC-8) ──────────────────────────────────────
|
||||||
|
|
||||||
@@ -114,6 +114,7 @@ fn mnemonic_strings_identical() {
|
|||||||
fn wzp_signal_serializes_into_fc_callsignal_payload() {
|
fn wzp_signal_serializes_into_fc_callsignal_payload() {
|
||||||
// WZP creates a CallOffer SignalMessage
|
// WZP creates a CallOffer SignalMessage
|
||||||
let offer = wzp_proto::SignalMessage::CallOffer {
|
let offer = wzp_proto::SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [1u8; 32],
|
identity_pub: [1u8; 32],
|
||||||
ephemeral_pub: [2u8; 32],
|
ephemeral_pub: [2u8; 32],
|
||||||
signature: vec![3u8; 64],
|
signature: vec![3u8; 64],
|
||||||
@@ -180,6 +181,7 @@ fn wzp_signal_serializes_into_fc_callsignal_payload() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn wzp_answer_round_trips_through_fc_callsignal() {
|
fn wzp_answer_round_trips_through_fc_callsignal() {
|
||||||
let answer = wzp_proto::SignalMessage::CallAnswer {
|
let answer = wzp_proto::SignalMessage::CallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [10u8; 32],
|
identity_pub: [10u8; 32],
|
||||||
ephemeral_pub: [20u8; 32],
|
ephemeral_pub: [20u8; 32],
|
||||||
signature: vec![30u8; 64],
|
signature: vec![30u8; 64],
|
||||||
@@ -212,6 +214,7 @@ fn wzp_answer_round_trips_through_fc_callsignal() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn wzp_hangup_round_trips_through_fc_callsignal() {
|
fn wzp_hangup_round_trips_through_fc_callsignal() {
|
||||||
let hangup = wzp_proto::SignalMessage::Hangup {
|
let hangup = wzp_proto::SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
};
|
};
|
||||||
@@ -298,6 +301,7 @@ fn all_signal_types_map_correctly() {
|
|||||||
let cases: Vec<(wzp_proto::SignalMessage, &str)> = vec![
|
let cases: Vec<(wzp_proto::SignalMessage, &str)> = vec![
|
||||||
(
|
(
|
||||||
wzp_proto::SignalMessage::CallOffer {
|
wzp_proto::SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [0; 32],
|
identity_pub: [0; 32],
|
||||||
ephemeral_pub: [0; 32],
|
ephemeral_pub: [0; 32],
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
@@ -310,6 +314,7 @@ fn all_signal_types_map_correctly() {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
wzp_proto::SignalMessage::CallAnswer {
|
wzp_proto::SignalMessage::CallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub: [0; 32],
|
identity_pub: [0; 32],
|
||||||
ephemeral_pub: [0; 32],
|
ephemeral_pub: [0; 32],
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
@@ -319,12 +324,14 @@ fn all_signal_types_map_correctly() {
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
wzp_proto::SignalMessage::IceCandidate {
|
wzp_proto::SignalMessage::IceCandidate {
|
||||||
|
version: default_signal_version(),
|
||||||
candidate: "candidate:1".to_string(),
|
candidate: "candidate:1".to_string(),
|
||||||
},
|
},
|
||||||
"IceCandidate",
|
"IceCandidate",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
wzp_proto::SignalMessage::Hangup {
|
wzp_proto::SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
},
|
},
|
||||||
@@ -482,10 +489,11 @@ fn auth_response_with_eth_address() {
|
|||||||
assert!(!resp3.valid);
|
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]
|
#[test]
|
||||||
fn wzp_proto_has_auth_token_variant() {
|
fn wzp_proto_has_auth_token_variant() {
|
||||||
let msg = wzp_proto::SignalMessage::AuthToken {
|
let msg = wzp_proto::SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
token: "fc-bearer-token-xyz".to_string(),
|
token: "fc-bearer-token-xyz".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -496,7 +504,7 @@ fn wzp_proto_has_auth_token_variant() {
|
|||||||
|
|
||||||
// Deserialize back
|
// Deserialize back
|
||||||
let decoded: wzp_proto::SignalMessage = serde_json::from_str(&json).unwrap();
|
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");
|
assert_eq!(token, "fc-bearer-token-xyz");
|
||||||
} else {
|
} else {
|
||||||
panic!("expected AuthToken variant, got: {decoded:?}");
|
panic!("expected AuthToken variant, got: {decoded:?}");
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub use media_type::MediaType;
|
|||||||
pub use packet::{
|
pub use packet::{
|
||||||
CallAcceptMode, FRAME_TYPE_FULL, FRAME_TYPE_MINI, HangupReason, MediaHeader, MediaHeaderV2,
|
CallAcceptMode, FRAME_TYPE_FULL, FRAME_TYPE_MINI, HangupReason, MediaHeader, MediaHeaderV2,
|
||||||
MediaPacket, MiniFrameContext, MiniFrameContextV2, MiniHeader, MiniHeaderV2, PresenceUser,
|
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 quality::{AdaptiveQualityController, NetworkContext, Tier};
|
||||||
pub use session::{Session, SessionEvent, SessionState};
|
pub use session::{Session, SessionEvent, SessionState};
|
||||||
|
|||||||
@@ -540,10 +540,23 @@ impl MiniFrameContextV2 {
|
|||||||
/// Compatible with Warzone messenger's identity model:
|
/// Compatible with Warzone messenger's identity model:
|
||||||
/// - Identity keys are Ed25519 (signing) + X25519 (encryption) derived from a 32-byte seed via HKDF
|
/// - Identity keys are Ed25519 (signing) + X25519 (encryption) derived from a 32-byte seed via HKDF
|
||||||
/// - Fingerprint = SHA-256(Ed25519 public key)[:16]
|
/// - 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)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum SignalMessage {
|
pub enum SignalMessage {
|
||||||
/// Call initiation (analogous to Warzone's WireMessage::CallOffer).
|
/// Call initiation (analogous to Warzone's WireMessage::CallOffer).
|
||||||
CallOffer {
|
CallOffer {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Caller's Ed25519 identity public key (32 bytes).
|
/// Caller's Ed25519 identity public key (32 bytes).
|
||||||
identity_pub: [u8; 32],
|
identity_pub: [u8; 32],
|
||||||
/// Ephemeral X25519 public key for this call.
|
/// Ephemeral X25519 public key for this call.
|
||||||
@@ -565,6 +578,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Call acceptance (analogous to Warzone's WireMessage::CallAnswer).
|
/// Call acceptance (analogous to Warzone's WireMessage::CallAnswer).
|
||||||
CallAnswer {
|
CallAnswer {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Callee's Ed25519 identity public key (32 bytes).
|
/// Callee's Ed25519 identity public key (32 bytes).
|
||||||
identity_pub: [u8; 32],
|
identity_pub: [u8; 32],
|
||||||
/// Callee's ephemeral X25519 public key.
|
/// Callee's ephemeral X25519 public key.
|
||||||
@@ -577,11 +592,15 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// ICE candidate for NAT traversal.
|
/// ICE candidate for NAT traversal.
|
||||||
IceCandidate {
|
IceCandidate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
candidate: String,
|
candidate: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Periodic rekeying (forward secrecy).
|
/// Periodic rekeying (forward secrecy).
|
||||||
Rekey {
|
Rekey {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// New ephemeral X25519 public key.
|
/// New ephemeral X25519 public key.
|
||||||
new_ephemeral_pub: [u8; 32],
|
new_ephemeral_pub: [u8; 32],
|
||||||
/// Ed25519 signature over (new_ephemeral_pub || session_id).
|
/// Ed25519 signature over (new_ephemeral_pub || session_id).
|
||||||
@@ -590,6 +609,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Quality/profile change request.
|
/// Quality/profile change request.
|
||||||
QualityUpdate {
|
QualityUpdate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
report: QualityReport,
|
report: QualityReport,
|
||||||
recommended_profile: crate::QualityProfile,
|
recommended_profile: crate::QualityProfile,
|
||||||
},
|
},
|
||||||
@@ -601,6 +622,8 @@ pub enum SignalMessage {
|
|||||||
/// introducing this variant is backward-compatible with pre-Phase-4
|
/// introducing this variant is backward-compatible with pre-Phase-4
|
||||||
/// relays — they'll just log "unknown signal variant" on receipt.
|
/// relays — they'll just log "unknown signal variant" on receipt.
|
||||||
LossRecoveryUpdate {
|
LossRecoveryUpdate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Total frames reconstructed via DRED since call start (monotonic).
|
/// Total frames reconstructed via DRED since call start (monotonic).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
dred_reconstructions: u64,
|
dred_reconstructions: u64,
|
||||||
@@ -616,9 +639,13 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Connection keepalive / RTT measurement.
|
/// Connection keepalive / RTT measurement.
|
||||||
Ping {
|
Ping {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
timestamp_ms: u64,
|
timestamp_ms: u64,
|
||||||
},
|
},
|
||||||
Pong {
|
Pong {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
timestamp_ms: u64,
|
timestamp_ms: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -626,6 +653,8 @@ pub enum SignalMessage {
|
|||||||
/// with older clients that send Hangup without it — the relay falls
|
/// with older clients that send Hangup without it — the relay falls
|
||||||
/// back to ending ALL active calls for the sender in that case.
|
/// back to ending ALL active calls for the sender in that case.
|
||||||
Hangup {
|
Hangup {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
reason: HangupReason,
|
reason: HangupReason,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
call_id: Option<String>,
|
call_id: Option<String>,
|
||||||
@@ -634,6 +663,8 @@ pub enum SignalMessage {
|
|||||||
/// featherChat bearer token for relay authentication.
|
/// featherChat bearer token for relay authentication.
|
||||||
/// Sent as the first signal message when --auth-url is configured.
|
/// Sent as the first signal message when --auth-url is configured.
|
||||||
AuthToken {
|
AuthToken {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
token: String,
|
token: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -647,6 +678,8 @@ pub enum SignalMessage {
|
|||||||
Unmute,
|
Unmute,
|
||||||
/// Transfer the call to another peer.
|
/// Transfer the call to another peer.
|
||||||
Transfer {
|
Transfer {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
target_fingerprint: String,
|
target_fingerprint: String,
|
||||||
/// Optional relay address for the transfer target.
|
/// Optional relay address for the transfer target.
|
||||||
relay_addr: Option<String>,
|
relay_addr: Option<String>,
|
||||||
@@ -658,6 +691,8 @@ pub enum SignalMessage {
|
|||||||
/// Sent periodically over probe connections to share which fingerprints
|
/// Sent periodically over probe connections to share which fingerprints
|
||||||
/// are connected to the sending relay.
|
/// are connected to the sending relay.
|
||||||
PresenceUpdate {
|
PresenceUpdate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Fingerprints currently connected to the sending relay.
|
/// Fingerprints currently connected to the sending relay.
|
||||||
fingerprints: Vec<String>,
|
fingerprints: Vec<String>,
|
||||||
/// Address of the sending relay (e.g., "192.168.1.10:4433").
|
/// 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.
|
/// Ask a peer relay to look up a fingerprint in its registry.
|
||||||
RouteQuery {
|
RouteQuery {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
ttl: u8,
|
ttl: u8,
|
||||||
},
|
},
|
||||||
/// Response to a route query.
|
/// Response to a route query.
|
||||||
RouteResponse {
|
RouteResponse {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
found: bool,
|
found: bool,
|
||||||
relay_chain: Vec<String>,
|
relay_chain: Vec<String>,
|
||||||
@@ -680,6 +719,8 @@ pub enum SignalMessage {
|
|||||||
/// Sent over a relay link (`_relay` SNI) to ask the peer relay to
|
/// Sent over a relay link (`_relay` SNI) to ask the peer relay to
|
||||||
/// create a room and forward media for the given session.
|
/// create a room and forward media for the given session.
|
||||||
SessionForward {
|
SessionForward {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
target_fingerprint: String,
|
target_fingerprint: String,
|
||||||
source_relay: String,
|
source_relay: String,
|
||||||
@@ -687,12 +728,16 @@ pub enum SignalMessage {
|
|||||||
/// Confirm that the forwarding session has been set up on the peer relay.
|
/// 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.
|
/// The `room_name` tells the source relay which room to address media to.
|
||||||
SessionForwardAck {
|
SessionForwardAck {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
room_name: String,
|
room_name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Room membership update — sent by relay to all participants when someone joins or leaves.
|
/// Room membership update — sent by relay to all participants when someone joins or leaves.
|
||||||
RoomUpdate {
|
RoomUpdate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Current participant count.
|
/// Current participant count.
|
||||||
count: u32,
|
count: u32,
|
||||||
/// List of participants currently in the room.
|
/// List of participants currently in the room.
|
||||||
@@ -702,12 +747,16 @@ pub enum SignalMessage {
|
|||||||
// ── Federation signals (relay-to-relay) ──
|
// ── Federation signals (relay-to-relay) ──
|
||||||
/// Federation: initial handshake — the connecting relay identifies itself.
|
/// Federation: initial handshake — the connecting relay identifies itself.
|
||||||
FederationHello {
|
FederationHello {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// TLS certificate fingerprint of the connecting relay.
|
/// TLS certificate fingerprint of the connecting relay.
|
||||||
tls_fingerprint: String,
|
tls_fingerprint: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Federation: this relay now has local participants in a global room.
|
/// Federation: this relay now has local participants in a global room.
|
||||||
GlobalRoomActive {
|
GlobalRoomActive {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
room: String,
|
room: String,
|
||||||
/// Participants on the announcing relay (for federated presence).
|
/// Participants on the announcing relay (for federated presence).
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -716,6 +765,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Federation: this relay's last local participant left a global room.
|
/// Federation: this relay's last local participant left a global room.
|
||||||
GlobalRoomInactive {
|
GlobalRoomInactive {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
room: String,
|
room: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -723,6 +774,8 @@ pub enum SignalMessage {
|
|||||||
/// Register on relay for direct calls. Sent on `_signal` connections
|
/// Register on relay for direct calls. Sent on `_signal` connections
|
||||||
/// after optional AuthToken.
|
/// after optional AuthToken.
|
||||||
RegisterPresence {
|
RegisterPresence {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Client's Ed25519 identity public key.
|
/// Client's Ed25519 identity public key.
|
||||||
identity_pub: [u8; 32],
|
identity_pub: [u8; 32],
|
||||||
/// Signature over ("register-presence" || identity_pub).
|
/// Signature over ("register-presence" || identity_pub).
|
||||||
@@ -733,6 +786,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Relay confirms presence registration.
|
/// Relay confirms presence registration.
|
||||||
RegisterPresenceAck {
|
RegisterPresenceAck {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
success: bool,
|
success: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
@@ -750,6 +805,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Direct call offer routed through the relay to a specific peer.
|
/// Direct call offer routed through the relay to a specific peer.
|
||||||
DirectCallOffer {
|
DirectCallOffer {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// Caller's fingerprint.
|
/// Caller's fingerprint.
|
||||||
caller_fingerprint: String,
|
caller_fingerprint: String,
|
||||||
/// Caller's display name.
|
/// Caller's display name.
|
||||||
@@ -798,6 +855,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Callee's response to a direct call.
|
/// Callee's response to a direct call.
|
||||||
DirectCallAnswer {
|
DirectCallAnswer {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// How the callee accepts (or rejects).
|
/// How the callee accepts (or rejects).
|
||||||
accept_mode: CallAcceptMode,
|
accept_mode: CallAcceptMode,
|
||||||
@@ -838,6 +897,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Relay tells both parties: media room is ready.
|
/// Relay tells both parties: media room is ready.
|
||||||
CallSetup {
|
CallSetup {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// Room name on the relay for the media session (e.g., "_call:a1b2c3d4").
|
/// Room name on the relay for the media session (e.g., "_call:a1b2c3d4").
|
||||||
room: String,
|
room: String,
|
||||||
@@ -871,6 +932,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Ringing notification (relay → caller, callee received the offer).
|
/// Ringing notification (relay → caller, callee received the offer).
|
||||||
CallRinging {
|
CallRinging {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -893,6 +956,8 @@ pub enum SignalMessage {
|
|||||||
/// for IPv4, "[::1]:p" for IPv6. Clients parse it with
|
/// for IPv4, "[::1]:p" for IPv6. Clients parse it with
|
||||||
/// `SocketAddr::from_str`.
|
/// `SocketAddr::from_str`.
|
||||||
ReflectResponse {
|
ReflectResponse {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
observed_addr: String,
|
observed_addr: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -910,6 +975,8 @@ pub enum SignalMessage {
|
|||||||
/// and the other picks Relay — they now agree on the path
|
/// and the other picks Relay — they now agree on the path
|
||||||
/// before any media flows.
|
/// before any media flows.
|
||||||
MediaPathReport {
|
MediaPathReport {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// Did the direct QUIC connection (P2P dial or accept)
|
/// Did the direct QUIC connection (P2P dial or accept)
|
||||||
/// complete successfully on this side?
|
/// complete successfully on this side?
|
||||||
@@ -931,6 +998,8 @@ pub enum SignalMessage {
|
|||||||
/// — peers ignore updates with a generation <= their last-seen
|
/// — peers ignore updates with a generation <= their last-seen
|
||||||
/// generation to handle reordering.
|
/// generation to handle reordering.
|
||||||
CandidateUpdate {
|
CandidateUpdate {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// New server-reflexive address (STUN-discovered or relay-reflected).
|
/// New server-reflexive address (STUN-discovered or relay-reflected).
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[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
|
/// and recent port sequence so the peer can predict which port
|
||||||
/// to dial.
|
/// to dial.
|
||||||
HardNatProbe {
|
HardNatProbe {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// Last observed external ports (most recent first).
|
/// Last observed external ports (most recent first).
|
||||||
/// Typically 3-5 entries from sequential STUN probes.
|
/// 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
|
/// ports it has open. The Dialer then sprays QUIC connects to
|
||||||
/// these ports (and optionally random ports) on the Acceptor's IP.
|
/// these ports (and optionally random ports) on the Acceptor's IP.
|
||||||
HardNatBirthdayStart {
|
HardNatBirthdayStart {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// Number of sockets the Acceptor opened.
|
/// Number of sockets the Acceptor opened.
|
||||||
acceptor_port_count: u16,
|
acceptor_port_count: u16,
|
||||||
@@ -995,6 +1068,8 @@ pub enum SignalMessage {
|
|||||||
/// A→B→A echo loops; proper TTL + dedup will land when
|
/// A→B→A echo loops; proper TTL + dedup will land when
|
||||||
/// multi-hop federation is added (Phase 4.2).
|
/// multi-hop federation is added (Phase 4.2).
|
||||||
FederatedSignalForward {
|
FederatedSignalForward {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// The signal message being forwarded
|
/// The signal message being forwarded
|
||||||
/// (`DirectCallOffer`, `DirectCallAnswer`, `CallRinging`,
|
/// (`DirectCallOffer`, `DirectCallAnswer`, `CallRinging`,
|
||||||
/// `Hangup`, ...). Boxed because `SignalMessage` is
|
/// `Hangup`, ...). Boxed because `SignalMessage` is
|
||||||
@@ -1011,6 +1086,8 @@ pub enum SignalMessage {
|
|||||||
/// Relay-initiated quality directive: all participants should switch
|
/// Relay-initiated quality directive: all participants should switch
|
||||||
/// to the recommended profile to match the weakest link.
|
/// to the recommended profile to match the weakest link.
|
||||||
QualityDirective {
|
QualityDirective {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
recommended_profile: crate::QualityProfile,
|
recommended_profile: crate::QualityProfile,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
@@ -1021,6 +1098,8 @@ pub enum SignalMessage {
|
|||||||
/// users to all connected clients. Sent on every register/
|
/// users to all connected clients. Sent on every register/
|
||||||
/// deregister so clients can maintain a live lobby user list.
|
/// deregister so clients can maintain a live lobby user list.
|
||||||
PresenceList {
|
PresenceList {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
/// List of online users. Each entry is { fingerprint, alias }.
|
/// List of online users. Each entry is { fingerprint, alias }.
|
||||||
users: Vec<PresenceUser>,
|
users: Vec<PresenceUser>,
|
||||||
},
|
},
|
||||||
@@ -1031,6 +1110,8 @@ pub enum SignalMessage {
|
|||||||
/// conditions. Used for consensual upgrades that require both
|
/// conditions. Used for consensual upgrades that require both
|
||||||
/// sides to agree (e.g., switching from Opus24k to Studio48k).
|
/// sides to agree (e.g., switching from Opus24k to Studio48k).
|
||||||
UpgradeProposal {
|
UpgradeProposal {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// Unique ID for this proposal (to match response).
|
/// Unique ID for this proposal (to match response).
|
||||||
proposal_id: String,
|
proposal_id: String,
|
||||||
@@ -1045,6 +1126,8 @@ pub enum SignalMessage {
|
|||||||
|
|
||||||
/// Response to an UpgradeProposal.
|
/// Response to an UpgradeProposal.
|
||||||
UpgradeResponse {
|
UpgradeResponse {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
proposal_id: String,
|
proposal_id: String,
|
||||||
/// true = accepted, both sides switch. false = rejected.
|
/// true = accepted, both sides switch. false = rejected.
|
||||||
@@ -1057,6 +1140,8 @@ pub enum SignalMessage {
|
|||||||
/// Confirmation that the upgrade is committed — both sides
|
/// Confirmation that the upgrade is committed — both sides
|
||||||
/// should switch encoder at the next frame boundary.
|
/// should switch encoder at the next frame boundary.
|
||||||
UpgradeConfirm {
|
UpgradeConfirm {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
proposal_id: String,
|
proposal_id: String,
|
||||||
confirmed_profile: crate::QualityProfile,
|
confirmed_profile: crate::QualityProfile,
|
||||||
@@ -1067,6 +1152,8 @@ pub enum SignalMessage {
|
|||||||
/// encoding where each side uses the best quality its connection
|
/// encoding where each side uses the best quality its connection
|
||||||
/// supports, rather than forcing all to the weakest link.
|
/// supports, rather than forcing all to the weakest link.
|
||||||
QualityCapability {
|
QualityCapability {
|
||||||
|
#[serde(default = "default_signal_version")]
|
||||||
|
version: u8,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
/// The best profile this peer can sustain based on its
|
/// The best profile this peer can sustain based on its
|
||||||
/// current network conditions.
|
/// current network conditions.
|
||||||
@@ -1083,7 +1170,7 @@ pub enum SignalMessage {
|
|||||||
/// carrying ACK/NACK vectors and a REMB-style bandwidth estimate.
|
/// carrying ACK/NACK vectors and a REMB-style bandwidth estimate.
|
||||||
TransportFeedback {
|
TransportFeedback {
|
||||||
/// Feedback format version (default 1).
|
/// Feedback format version (default 1).
|
||||||
#[serde(default)]
|
#[serde(default = "default_signal_version")]
|
||||||
version: u8,
|
version: u8,
|
||||||
/// Which media stream this feedback applies to.
|
/// Which media stream this feedback applies to.
|
||||||
stream_id: u8,
|
stream_id: u8,
|
||||||
@@ -1132,6 +1219,10 @@ pub fn default_proto_version() -> u8 {
|
|||||||
pub fn default_supported_versions() -> Vec<u8> {
|
pub fn default_supported_versions() -> Vec<u8> {
|
||||||
vec![2]
|
vec![2]
|
||||||
}
|
}
|
||||||
|
/// Default signal message version (v1).
|
||||||
|
pub fn default_signal_version() -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
/// Reasons for ending a call.
|
/// Reasons for ending a call.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[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 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"] {
|
for addr in ["192.0.2.17:4433", "[2001:db8::1]:4433", "127.0.0.1:54321"] {
|
||||||
let resp = SignalMessage::ReflectResponse {
|
let resp = SignalMessage::ReflectResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
observed_addr: addr.to_string(),
|
observed_addr: addr.to_string(),
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&resp).unwrap();
|
let json = serde_json::to_string(&resp).unwrap();
|
||||||
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||||
match decoded {
|
match decoded {
|
||||||
SignalMessage::ReflectResponse { observed_addr } => {
|
SignalMessage::ReflectResponse { observed_addr, .. } => {
|
||||||
assert_eq!(observed_addr, addr);
|
assert_eq!(observed_addr, addr);
|
||||||
// Must parse back to a SocketAddr cleanly.
|
// Must parse back to a SocketAddr cleanly.
|
||||||
let _parsed: std::net::SocketAddr = observed_addr
|
let _parsed: std::net::SocketAddr = observed_addr
|
||||||
@@ -1326,6 +1418,7 @@ mod tests {
|
|||||||
// Wrap a DirectCallOffer inside FederatedSignalForward and
|
// Wrap a DirectCallOffer inside FederatedSignalForward and
|
||||||
// prove both directions of serde preserve every field.
|
// prove both directions of serde preserve every field.
|
||||||
let inner = SignalMessage::DirectCallOffer {
|
let inner = SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: Some("Alice".into()),
|
caller_alias: Some("Alice".into()),
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -1340,6 +1433,7 @@ mod tests {
|
|||||||
caller_build_version: None,
|
caller_build_version: None,
|
||||||
};
|
};
|
||||||
let forward = SignalMessage::FederatedSignalForward {
|
let forward = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(inner),
|
inner: Box::new(inner),
|
||||||
origin_relay_fp: "relay-a-tls-fp".into(),
|
origin_relay_fp: "relay-a-tls-fp".into(),
|
||||||
};
|
};
|
||||||
@@ -1349,6 +1443,7 @@ mod tests {
|
|||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
inner,
|
inner,
|
||||||
origin_relay_fp,
|
origin_relay_fp,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(origin_relay_fp, "relay-a-tls-fp");
|
assert_eq!(origin_relay_fp, "relay-a-tls-fp");
|
||||||
match *inner {
|
match *inner {
|
||||||
@@ -1375,6 +1470,7 @@ mod tests {
|
|||||||
// we intend to forward survives being boxed + re-serialized.
|
// we intend to forward survives being boxed + re-serialized.
|
||||||
let cases: Vec<SignalMessage> = vec![
|
let cases: Vec<SignalMessage> = vec![
|
||||||
SignalMessage::DirectCallAnswer {
|
SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
@@ -1387,9 +1483,11 @@ mod tests {
|
|||||||
callee_build_version: None,
|
callee_build_version: None,
|
||||||
},
|
},
|
||||||
SignalMessage::CallRinging {
|
SignalMessage::CallRinging {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
},
|
},
|
||||||
SignalMessage::Hangup {
|
SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: HangupReason::Normal,
|
reason: HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
},
|
},
|
||||||
@@ -1397,6 +1495,7 @@ mod tests {
|
|||||||
for inner in cases {
|
for inner in cases {
|
||||||
let inner_disc = std::mem::discriminant(&inner);
|
let inner_disc = std::mem::discriminant(&inner);
|
||||||
let forward = SignalMessage::FederatedSignalForward {
|
let forward = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(inner),
|
inner: Box::new(inner),
|
||||||
origin_relay_fp: "r".into(),
|
origin_relay_fp: "r".into(),
|
||||||
};
|
};
|
||||||
@@ -1415,6 +1514,7 @@ mod tests {
|
|||||||
fn hole_punching_optional_fields_roundtrip() {
|
fn hole_punching_optional_fields_roundtrip() {
|
||||||
// DirectCallOffer with Some(caller_reflexive_addr)
|
// DirectCallOffer with Some(caller_reflexive_addr)
|
||||||
let offer = SignalMessage::DirectCallOffer {
|
let offer = SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -1448,6 +1548,7 @@ mod tests {
|
|||||||
// OMIT the field from the JSON so older relays that don't
|
// OMIT the field from the JSON so older relays that don't
|
||||||
// know about caller_reflexive_addr don't see it.
|
// know about caller_reflexive_addr don't see it.
|
||||||
let offer_none = SignalMessage::DirectCallOffer {
|
let offer_none = SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -1469,6 +1570,7 @@ mod tests {
|
|||||||
|
|
||||||
// DirectCallAnswer with callee_reflexive_addr.
|
// DirectCallAnswer with callee_reflexive_addr.
|
||||||
let answer = SignalMessage::DirectCallAnswer {
|
let answer = SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
@@ -1494,6 +1596,7 @@ mod tests {
|
|||||||
|
|
||||||
// CallSetup with peer_direct_addr.
|
// CallSetup with peer_direct_addr.
|
||||||
let setup = SignalMessage::CallSetup {
|
let setup = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
room: "call-c1".into(),
|
room: "call-c1".into(),
|
||||||
relay_addr: "203.0.113.5:4433".into(),
|
relay_addr: "203.0.113.5:4433".into(),
|
||||||
@@ -1566,14 +1669,17 @@ mod tests {
|
|||||||
// test a sample of the pre-existing ones.
|
// test a sample of the pre-existing ones.
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
SignalMessage::Ping {
|
SignalMessage::Ping {
|
||||||
|
version: default_signal_version(),
|
||||||
timestamp_ms: 12345,
|
timestamp_ms: 12345,
|
||||||
},
|
},
|
||||||
SignalMessage::Hold,
|
SignalMessage::Hold,
|
||||||
SignalMessage::Hangup {
|
SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: HangupReason::Normal,
|
reason: HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
},
|
},
|
||||||
SignalMessage::CallRinging {
|
SignalMessage::CallRinging {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "abcd".into(),
|
call_id: "abcd".into(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1614,6 +1720,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn transfer_serialize() {
|
fn transfer_serialize() {
|
||||||
let transfer = SignalMessage::Transfer {
|
let transfer = SignalMessage::Transfer {
|
||||||
|
version: default_signal_version(),
|
||||||
target_fingerprint: "abc123".to_string(),
|
target_fingerprint: "abc123".to_string(),
|
||||||
relay_addr: Some("relay.example.com:4433".to_string()),
|
relay_addr: Some("relay.example.com:4433".to_string()),
|
||||||
};
|
};
|
||||||
@@ -1623,6 +1730,7 @@ mod tests {
|
|||||||
SignalMessage::Transfer {
|
SignalMessage::Transfer {
|
||||||
target_fingerprint,
|
target_fingerprint,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(target_fingerprint, "abc123");
|
assert_eq!(target_fingerprint, "abc123");
|
||||||
assert_eq!(relay_addr.unwrap(), "relay.example.com:4433");
|
assert_eq!(relay_addr.unwrap(), "relay.example.com:4433");
|
||||||
@@ -1632,6 +1740,7 @@ mod tests {
|
|||||||
|
|
||||||
// Also test with relay_addr = None
|
// Also test with relay_addr = None
|
||||||
let transfer_no_relay = SignalMessage::Transfer {
|
let transfer_no_relay = SignalMessage::Transfer {
|
||||||
|
version: default_signal_version(),
|
||||||
target_fingerprint: "def456".to_string(),
|
target_fingerprint: "def456".to_string(),
|
||||||
relay_addr: None,
|
relay_addr: None,
|
||||||
};
|
};
|
||||||
@@ -1641,6 +1750,7 @@ mod tests {
|
|||||||
SignalMessage::Transfer {
|
SignalMessage::Transfer {
|
||||||
target_fingerprint,
|
target_fingerprint,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(target_fingerprint, "def456");
|
assert_eq!(target_fingerprint, "def456");
|
||||||
assert!(relay_addr.is_none());
|
assert!(relay_addr.is_none());
|
||||||
@@ -1660,6 +1770,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn presence_update_signal_roundtrip() {
|
fn presence_update_signal_roundtrip() {
|
||||||
let msg = SignalMessage::PresenceUpdate {
|
let msg = SignalMessage::PresenceUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprints: vec!["aabb".to_string(), "ccdd".to_string()],
|
fingerprints: vec!["aabb".to_string(), "ccdd".to_string()],
|
||||||
relay_addr: "10.0.0.1:4433".to_string(),
|
relay_addr: "10.0.0.1:4433".to_string(),
|
||||||
};
|
};
|
||||||
@@ -1669,6 +1780,7 @@ mod tests {
|
|||||||
SignalMessage::PresenceUpdate {
|
SignalMessage::PresenceUpdate {
|
||||||
fingerprints,
|
fingerprints,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(fingerprints.len(), 2);
|
assert_eq!(fingerprints.len(), 2);
|
||||||
assert!(fingerprints.contains(&"aabb".to_string()));
|
assert!(fingerprints.contains(&"aabb".to_string()));
|
||||||
@@ -1680,6 +1792,7 @@ mod tests {
|
|||||||
|
|
||||||
// Empty fingerprints list
|
// Empty fingerprints list
|
||||||
let msg_empty = SignalMessage::PresenceUpdate {
|
let msg_empty = SignalMessage::PresenceUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprints: vec![],
|
fingerprints: vec![],
|
||||||
relay_addr: "10.0.0.2:4433".to_string(),
|
relay_addr: "10.0.0.2:4433".to_string(),
|
||||||
};
|
};
|
||||||
@@ -1689,6 +1802,7 @@ mod tests {
|
|||||||
SignalMessage::PresenceUpdate {
|
SignalMessage::PresenceUpdate {
|
||||||
fingerprints,
|
fingerprints,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert!(fingerprints.is_empty());
|
assert!(fingerprints.is_empty());
|
||||||
assert_eq!(relay_addr, "10.0.0.2:4433");
|
assert_eq!(relay_addr, "10.0.0.2:4433");
|
||||||
@@ -1999,6 +2113,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn quality_directive_roundtrip() {
|
fn quality_directive_roundtrip() {
|
||||||
let msg = SignalMessage::QualityDirective {
|
let msg = SignalMessage::QualityDirective {
|
||||||
|
version: default_signal_version(),
|
||||||
recommended_profile: crate::QualityProfile::DEGRADED,
|
recommended_profile: crate::QualityProfile::DEGRADED,
|
||||||
reason: Some("weakest link degraded".into()),
|
reason: Some("weakest link degraded".into()),
|
||||||
};
|
};
|
||||||
@@ -2008,6 +2123,7 @@ mod tests {
|
|||||||
SignalMessage::QualityDirective {
|
SignalMessage::QualityDirective {
|
||||||
recommended_profile,
|
recommended_profile,
|
||||||
reason,
|
reason,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(recommended_profile.codec, CodecId::Opus6k);
|
assert_eq!(recommended_profile.codec, CodecId::Opus6k);
|
||||||
assert_eq!(reason.as_deref(), Some("weakest link degraded"));
|
assert_eq!(reason.as_deref(), Some("weakest link degraded"));
|
||||||
@@ -2019,6 +2135,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn quality_directive_without_reason_roundtrip() {
|
fn quality_directive_without_reason_roundtrip() {
|
||||||
let msg = SignalMessage::QualityDirective {
|
let msg = SignalMessage::QualityDirective {
|
||||||
|
version: default_signal_version(),
|
||||||
recommended_profile: crate::QualityProfile::GOOD,
|
recommended_profile: crate::QualityProfile::GOOD,
|
||||||
reason: None,
|
reason: None,
|
||||||
};
|
};
|
||||||
@@ -2078,6 +2195,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn upgrade_proposal_roundtrip() {
|
fn upgrade_proposal_roundtrip() {
|
||||||
let msg = SignalMessage::UpgradeProposal {
|
let msg = SignalMessage::UpgradeProposal {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
proposal_id: "p1".into(),
|
proposal_id: "p1".into(),
|
||||||
proposed_profile: crate::QualityProfile::STUDIO_48K,
|
proposed_profile: crate::QualityProfile::STUDIO_48K,
|
||||||
@@ -2102,6 +2220,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn upgrade_response_roundtrip() {
|
fn upgrade_response_roundtrip() {
|
||||||
let msg = SignalMessage::UpgradeResponse {
|
let msg = SignalMessage::UpgradeResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
proposal_id: "p1".into(),
|
proposal_id: "p1".into(),
|
||||||
accepted: true,
|
accepted: true,
|
||||||
@@ -2118,6 +2237,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn upgrade_confirm_roundtrip() {
|
fn upgrade_confirm_roundtrip() {
|
||||||
let msg = SignalMessage::UpgradeConfirm {
|
let msg = SignalMessage::UpgradeConfirm {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
proposal_id: "p1".into(),
|
proposal_id: "p1".into(),
|
||||||
confirmed_profile: crate::QualityProfile::STUDIO_64K,
|
confirmed_profile: crate::QualityProfile::STUDIO_64K,
|
||||||
@@ -2137,6 +2257,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn quality_capability_roundtrip() {
|
fn quality_capability_roundtrip() {
|
||||||
let msg = SignalMessage::QualityCapability {
|
let msg = SignalMessage::QualityCapability {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
max_profile: crate::QualityProfile::GOOD,
|
max_profile: crate::QualityProfile::GOOD,
|
||||||
loss_pct: Some(2.5),
|
loss_pct: Some(2.5),
|
||||||
@@ -2162,6 +2283,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn candidate_update_roundtrip() {
|
fn candidate_update_roundtrip() {
|
||||||
let msg = SignalMessage::CandidateUpdate {
|
let msg = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "test-123".into(),
|
call_id: "test-123".into(),
|
||||||
reflexive_addr: Some("203.0.113.5:4433".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()],
|
local_addrs: vec!["192.168.1.10:4433".into(), "10.0.0.5:4433".into()],
|
||||||
@@ -2177,6 +2299,7 @@ mod tests {
|
|||||||
local_addrs,
|
local_addrs,
|
||||||
mapped_addr,
|
mapped_addr,
|
||||||
generation,
|
generation,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(call_id, "test-123");
|
assert_eq!(call_id, "test-123");
|
||||||
assert_eq!(reflexive_addr.as_deref(), Some("203.0.113.5:4433"));
|
assert_eq!(reflexive_addr.as_deref(), Some("203.0.113.5:4433"));
|
||||||
@@ -2191,6 +2314,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn candidate_update_minimal_roundtrip() {
|
fn candidate_update_minimal_roundtrip() {
|
||||||
let msg = SignalMessage::CandidateUpdate {
|
let msg = SignalMessage::CandidateUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c".into(),
|
call_id: "c".into(),
|
||||||
reflexive_addr: None,
|
reflexive_addr: None,
|
||||||
local_addrs: vec![],
|
local_addrs: vec![],
|
||||||
@@ -2215,6 +2339,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn offer_with_mapped_addr_roundtrip() {
|
fn offer_with_mapped_addr_roundtrip() {
|
||||||
let msg = SignalMessage::DirectCallOffer {
|
let msg = SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -2246,6 +2371,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn offer_without_mapped_addr_omits_field() {
|
fn offer_without_mapped_addr_omits_field() {
|
||||||
let msg = SignalMessage::DirectCallOffer {
|
let msg = SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -2266,6 +2392,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn answer_with_mapped_addr_roundtrip() {
|
fn answer_with_mapped_addr_roundtrip() {
|
||||||
let msg = SignalMessage::DirectCallAnswer {
|
let msg = SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
@@ -2292,6 +2419,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn setup_with_mapped_addr_roundtrip() {
|
fn setup_with_mapped_addr_roundtrip() {
|
||||||
let msg = SignalMessage::CallSetup {
|
let msg = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: "c1".into(),
|
call_id: "c1".into(),
|
||||||
room: "room".into(),
|
room: "room".into(),
|
||||||
relay_addr: "1.2.3.4:5".into(),
|
relay_addr: "1.2.3.4:5".into(),
|
||||||
@@ -2368,6 +2496,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn register_presence_ack_with_new_fields_roundtrip() {
|
fn register_presence_ack_with_new_fields_roundtrip() {
|
||||||
let msg = SignalMessage::RegisterPresenceAck {
|
let msg = SignalMessage::RegisterPresenceAck {
|
||||||
|
version: default_signal_version(),
|
||||||
success: true,
|
success: true,
|
||||||
error: None,
|
error: None,
|
||||||
relay_build: Some("abc123".into()),
|
relay_build: Some("abc123".into()),
|
||||||
@@ -2443,6 +2572,7 @@ mod tests {
|
|||||||
nacked_seqs,
|
nacked_seqs,
|
||||||
remb_bps,
|
remb_bps,
|
||||||
recv_time_us,
|
recv_time_us,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(version, 1);
|
assert_eq!(version, 1);
|
||||||
assert_eq!(stream_id, 0);
|
assert_eq!(stream_id, 0);
|
||||||
@@ -2475,7 +2605,76 @@ mod tests {
|
|||||||
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
let decoded: SignalMessage = serde_json::from_str(json).unwrap();
|
||||||
match decoded {
|
match decoded {
|
||||||
SignalMessage::TransportFeedback { version, .. } => {
|
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"),
|
_ => panic!("wrong variant"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use sha2::{Digest, Sha256};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use wzp_proto::{MediaTransport, SignalMessage};
|
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||||
use wzp_transport::QuinnTransport;
|
use wzp_transport::QuinnTransport;
|
||||||
|
|
||||||
use crate::config::{PeerConfig, TrustedConfig};
|
use crate::config::{PeerConfig, TrustedConfig};
|
||||||
@@ -520,7 +520,11 @@ async fn run_room_event_dispatcher(
|
|||||||
if fm.is_global_room(&room) {
|
if fm.is_global_room(&room) {
|
||||||
let participants = fm.room_mgr.local_participant_list(&room);
|
let participants = fm.room_mgr.local_participant_list(&room);
|
||||||
info!(room = %room, count = participants.len(), "global room now active, announcing to peers");
|
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 transports: Vec<Arc<QuinnTransport>> = {
|
||||||
let links = fm.peer_links.lock().await;
|
let links = fm.peer_links.lock().await;
|
||||||
links.values().map(|l| l.transport.clone()).collect()
|
links.values().map(|l| l.transport.clone()).collect()
|
||||||
@@ -533,7 +537,10 @@ async fn run_room_event_dispatcher(
|
|||||||
Ok(RoomEvent::LocalLeave { room }) => {
|
Ok(RoomEvent::LocalLeave { room }) => {
|
||||||
if fm.is_global_room(&room) {
|
if fm.is_global_room(&room) {
|
||||||
info!(room = %room, "global room now inactive, announcing to peers");
|
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 transports: Vec<Arc<QuinnTransport>> = {
|
||||||
let links = fm.peer_links.lock().await;
|
let links = fm.peer_links.lock().await;
|
||||||
links.values().map(|l| l.transport.clone()).collect()
|
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();
|
let mut seen = HashSet::new();
|
||||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||||
let update = SignalMessage::RoomUpdate {
|
let update = SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: all_participants.len() as u32,
|
count: all_participants.len() as u32,
|
||||||
participants: all_participants,
|
participants: all_participants,
|
||||||
};
|
};
|
||||||
@@ -659,6 +667,7 @@ async fn connect_to_peer(
|
|||||||
|
|
||||||
// Send hello with our TLS fingerprint
|
// Send hello with our TLS fingerprint
|
||||||
let hello = SignalMessage::FederationHello {
|
let hello = SignalMessage::FederationHello {
|
||||||
|
version: default_signal_version(),
|
||||||
tls_fingerprint: fm.local_tls_fp.clone(),
|
tls_fingerprint: fm.local_tls_fp.clone(),
|
||||||
};
|
};
|
||||||
transport
|
transport
|
||||||
@@ -710,6 +719,7 @@ async fn run_federation_link(
|
|||||||
let participants = fm.room_mgr.local_participant_list(room_name);
|
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");
|
info!(peer = %peer_label, room = %room_name, participants = participants.len(), "announcing local global room to new peer");
|
||||||
msgs.push(SignalMessage::GlobalRoomActive {
|
msgs.push(SignalMessage::GlobalRoomActive {
|
||||||
|
version: default_signal_version(),
|
||||||
room: room_name.clone(),
|
room: room_name.clone(),
|
||||||
participants,
|
participants,
|
||||||
});
|
});
|
||||||
@@ -724,6 +734,7 @@ async fn run_federation_link(
|
|||||||
if fm.is_global_room(room) {
|
if fm.is_global_room(room) {
|
||||||
info!(peer = %peer_label, room = %room, via = %link.label, "propagating remote room to new peer");
|
info!(peer = %peer_label, room = %room, via = %link.label, "propagating remote room to new peer");
|
||||||
msgs.push(SignalMessage::GlobalRoomActive {
|
msgs.push(SignalMessage::GlobalRoomActive {
|
||||||
|
version: default_signal_version(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
participants: participants.clone(),
|
participants: participants.clone(),
|
||||||
});
|
});
|
||||||
@@ -837,7 +848,9 @@ async fn handle_signal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
SignalMessage::GlobalRoomActive { room, participants } => {
|
SignalMessage::GlobalRoomActive {
|
||||||
|
room, participants, ..
|
||||||
|
} => {
|
||||||
if fm.is_global_room(&room) {
|
if fm.is_global_room(&room) {
|
||||||
info!(peer = %peer_label, room = %room, remote_participants = participants.len(), "peer has global room active");
|
info!(peer = %peer_label, room = %room, remote_participants = participants.len(), "peer has global room active");
|
||||||
let mut links = fm.peer_links.lock().await;
|
let mut links = fm.peer_links.lock().await;
|
||||||
@@ -882,6 +895,7 @@ async fn handle_signal(
|
|||||||
let _ = link
|
let _ = link
|
||||||
.transport
|
.transport
|
||||||
.send_signal(&SignalMessage::GlobalRoomActive {
|
.send_signal(&SignalMessage::GlobalRoomActive {
|
||||||
|
version: default_signal_version(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
participants: tagged_for_propagation.clone(),
|
participants: tagged_for_propagation.clone(),
|
||||||
})
|
})
|
||||||
@@ -923,6 +937,7 @@ async fn handle_signal(
|
|||||||
let mut seen = HashSet::new();
|
let mut seen = HashSet::new();
|
||||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||||
let update = SignalMessage::RoomUpdate {
|
let update = SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: all_participants.len() as u32,
|
count: all_participants.len() as u32,
|
||||||
participants: all_participants,
|
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");
|
info!(peer = %peer_label, room = %room, "peer global room now inactive");
|
||||||
let mut links = fm.peer_links.lock().await;
|
let mut links = fm.peer_links.lock().await;
|
||||||
if let Some(link) = links.get_mut(peer_fp) {
|
if let Some(link) = links.get_mut(peer_fp) {
|
||||||
@@ -999,6 +1014,7 @@ async fn handle_signal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let msg = SignalMessage::GlobalRoomActive {
|
let msg = SignalMessage::GlobalRoomActive {
|
||||||
|
version: default_signal_version(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
participants: updated_participants,
|
participants: updated_participants,
|
||||||
};
|
};
|
||||||
@@ -1007,7 +1023,10 @@ async fn handle_signal(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No participants left anywhere — propagate inactive
|
// 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 {
|
for transport in &peer_sends {
|
||||||
let _ = transport.send_signal(&msg).await;
|
let _ = transport.send_signal(&msg).await;
|
||||||
}
|
}
|
||||||
@@ -1025,6 +1044,7 @@ async fn handle_signal(
|
|||||||
let mut seen = HashSet::new();
|
let mut seen = HashSet::new();
|
||||||
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
all_participants.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||||
let update = SignalMessage::RoomUpdate {
|
let update = SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: all_participants.len() as u32,
|
count: all_participants.len() as u32,
|
||||||
participants: all_participants,
|
participants: all_participants,
|
||||||
};
|
};
|
||||||
@@ -1050,6 +1070,7 @@ async fn handle_signal(
|
|||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
inner,
|
inner,
|
||||||
origin_relay_fp,
|
origin_relay_fp,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
if origin_relay_fp == fm.local_tls_fp {
|
if origin_relay_fp == fm.local_tls_fp {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! recv `CallOffer` → verify → generate ephemeral → derive session → send `CallAnswer`.
|
//! recv `CallOffer` → verify → generate ephemeral → derive session → send `CallAnswer`.
|
||||||
|
|
||||||
use wzp_crypto::{CryptoSession, KeyExchange, WarzoneKeyExchange};
|
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.
|
/// Accept the relay (callee) side of the cryptographic handshake.
|
||||||
///
|
///
|
||||||
@@ -51,6 +51,7 @@ pub async fn accept_handshake(
|
|||||||
alias,
|
alias,
|
||||||
protocol_version,
|
protocol_version,
|
||||||
supported_versions: _,
|
supported_versions: _,
|
||||||
|
..
|
||||||
} => (
|
} => (
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
@@ -70,6 +71,7 @@ pub async fn accept_handshake(
|
|||||||
// 1a. Protocol version check — we only speak v2.
|
// 1a. Protocol version check — we only speak v2.
|
||||||
if protocol_version != 2 {
|
if protocol_version != 2 {
|
||||||
let mismatch = SignalMessage::Hangup {
|
let mismatch = SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
reason: wzp_proto::HangupReason::ProtocolVersionMismatch {
|
||||||
server_supported: vec![2],
|
server_supported: vec![2],
|
||||||
},
|
},
|
||||||
@@ -108,6 +110,7 @@ pub async fn accept_handshake(
|
|||||||
|
|
||||||
// 6. Send CallAnswer
|
// 6. Send CallAnswer
|
||||||
let answer = SignalMessage::CallAnswer {
|
let answer = SignalMessage::CallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use clap::Parser;
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{debug, error, info, warn};
|
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::config::RelayConfig;
|
||||||
use wzp_relay::metrics::RelayMetrics;
|
use wzp_relay::metrics::RelayMetrics;
|
||||||
use wzp_relay::pipeline::{PipelineConfig, RelayPipeline};
|
use wzp_relay::pipeline::{PipelineConfig, RelayPipeline};
|
||||||
@@ -640,6 +640,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.send_to(
|
.send_to(
|
||||||
&caller_fp,
|
&caller_fp,
|
||||||
&SignalMessage::Hangup {
|
&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
},
|
},
|
||||||
@@ -685,6 +686,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// Emit the LOCAL CallSetup to our local caller.
|
// Emit the LOCAL CallSetup to our local caller.
|
||||||
let setup = SignalMessage::CallSetup {
|
let setup = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: room_name.clone(),
|
room: room_name.clone(),
|
||||||
relay_addr: advertised_addr_d.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.
|
// Forward to local caller for "ringing..." UX.
|
||||||
let caller_fp = {
|
let caller_fp = {
|
||||||
let reg = call_registry_d.lock().await;
|
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");
|
info!(%addr, "probe connection detected, entering Ping/Pong + presence responder");
|
||||||
loop {
|
loop {
|
||||||
match transport.recv_signal().await {
|
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
|
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
|
.await
|
||||||
{
|
{
|
||||||
error!(%addr, "probe pong send error: {e}");
|
error!(%addr, "probe pong send error: {e}");
|
||||||
@@ -878,6 +883,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Ok(Some(wzp_proto::SignalMessage::PresenceUpdate {
|
Ok(Some(wzp_proto::SignalMessage::PresenceUpdate {
|
||||||
fingerprints,
|
fingerprints,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
// A peer relay is telling us which fingerprints it has
|
// A peer relay is telling us which fingerprints it has
|
||||||
let peer_addr: std::net::SocketAddr =
|
let peer_addr: std::net::SocketAddr =
|
||||||
@@ -894,6 +900,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
reg.local_fingerprints().into_iter().collect()
|
reg.local_fingerprints().into_iter().collect()
|
||||||
};
|
};
|
||||||
let reply = wzp_proto::SignalMessage::PresenceUpdate {
|
let reply = wzp_proto::SignalMessage::PresenceUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprints: local_fps,
|
fingerprints: local_fps,
|
||||||
relay_addr: addr.to_string(),
|
relay_addr: addr.to_string(),
|
||||||
};
|
};
|
||||||
@@ -902,7 +909,9 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
break;
|
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
|
// Look up the fingerprint in our local registry
|
||||||
let reg = presence.lock().await;
|
let reg = presence.lock().await;
|
||||||
let route = route_resolver.resolve(®, &fingerprint);
|
let route = route_resolver.resolve(®, &fingerprint);
|
||||||
@@ -930,6 +939,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let reply = wzp_proto::SignalMessage::RouteResponse {
|
let reply = wzp_proto::SignalMessage::RouteResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprint,
|
fingerprint,
|
||||||
found,
|
found,
|
||||||
relay_chain,
|
relay_chain,
|
||||||
@@ -968,6 +978,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
{
|
{
|
||||||
Ok(Ok(Some(wzp_proto::SignalMessage::FederationHello {
|
Ok(Ok(Some(wzp_proto::SignalMessage::FederationHello {
|
||||||
tls_fingerprint,
|
tls_fingerprint,
|
||||||
|
..
|
||||||
}))) => tls_fingerprint,
|
}))) => tls_fingerprint,
|
||||||
_ => {
|
_ => {
|
||||||
warn!(%addr, "federation: no hello received, closing");
|
warn!(%addr, "federation: no hello received, closing");
|
||||||
@@ -1004,7 +1015,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// Optional auth
|
// Optional auth
|
||||||
let auth_fp: Option<String> = if let Some(ref url) = auth_url {
|
let auth_fp: Option<String> = if let Some(ref url) = auth_url {
|
||||||
match transport.recv_signal().await {
|
match transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::AuthToken { token })) => {
|
Ok(Some(SignalMessage::AuthToken { token, .. })) => {
|
||||||
match wzp_relay::auth::validate_token(url, &token).await {
|
match wzp_relay::auth::validate_token(url, &token).await {
|
||||||
Ok(client) => Some(client.fingerprint),
|
Ok(client) => Some(client.fingerprint),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -1033,6 +1044,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
identity_pub,
|
identity_pub,
|
||||||
signature: _,
|
signature: _,
|
||||||
alias,
|
alias,
|
||||||
|
..
|
||||||
}))) => {
|
}))) => {
|
||||||
// Compute fingerprint: SHA-256(Ed25519 pub key)[:16], same as Fingerprint type
|
// Compute fingerprint: SHA-256(Ed25519 pub key)[:16], same as Fingerprint type
|
||||||
let fp = {
|
let fp = {
|
||||||
@@ -1067,6 +1079,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// Send ack
|
// Send ack
|
||||||
let _ = transport
|
let _ = transport
|
||||||
.send_signal(&SignalMessage::RegisterPresenceAck {
|
.send_signal(&SignalMessage::RegisterPresenceAck {
|
||||||
|
version: default_signal_version(),
|
||||||
success: true,
|
success: true,
|
||||||
error: None,
|
error: None,
|
||||||
relay_build: Some(BUILD_GIT_HASH.to_string()),
|
relay_build: Some(BUILD_GIT_HASH.to_string()),
|
||||||
@@ -1126,6 +1139,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// federation has a matching entry.
|
// federation has a matching entry.
|
||||||
let forwarded = if let Some(ref fm) = federation_mgr {
|
let forwarded = if let Some(ref fm) = federation_mgr {
|
||||||
let forward = SignalMessage::FederatedSignalForward {
|
let forward = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(msg.clone()),
|
inner: Box::new(msg.clone()),
|
||||||
origin_relay_fp: tls_fp.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)");
|
info!(%addr, target = %target_fp, "call target not online (no federation route)");
|
||||||
let _ = transport
|
let _ = transport
|
||||||
.send_signal(&SignalMessage::Hangup {
|
.send_signal(&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
call_id: None,
|
||||||
})
|
})
|
||||||
@@ -1193,6 +1208,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// federated delivery is in flight.
|
// federated delivery is in flight.
|
||||||
let _ = transport
|
let _ = transport
|
||||||
.send_signal(&SignalMessage::CallRinging {
|
.send_signal(&SignalMessage::CallRinging {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@@ -1236,6 +1252,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
drop(hub);
|
drop(hub);
|
||||||
let _ = transport
|
let _ = transport
|
||||||
.send_signal(&SignalMessage::CallRinging {
|
.send_signal(&SignalMessage::CallRinging {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@@ -1293,11 +1310,13 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(ref origin_fp) = peer_relay_fp {
|
if let Some(ref origin_fp) = peer_relay_fp {
|
||||||
if let Some(ref fm) = federation_mgr {
|
if let Some(ref fm) = federation_mgr {
|
||||||
let hangup = SignalMessage::Hangup {
|
let hangup = SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: Some(call_id.clone()),
|
call_id: Some(call_id.clone()),
|
||||||
};
|
};
|
||||||
let forward =
|
let forward =
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(hangup),
|
inner: Box::new(hangup),
|
||||||
origin_relay_fp: tls_fp.clone(),
|
origin_relay_fp: tls_fp.clone(),
|
||||||
};
|
};
|
||||||
@@ -1314,6 +1333,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.send_to(
|
.send_to(
|
||||||
&peer_fp,
|
&peer_fp,
|
||||||
&SignalMessage::Hangup {
|
&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: Some(call_id.clone()),
|
call_id: Some(call_id.clone()),
|
||||||
},
|
},
|
||||||
@@ -1390,6 +1410,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(ref fm) = federation_mgr {
|
if let Some(ref fm) = federation_mgr {
|
||||||
let forward =
|
let forward =
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(msg.clone()),
|
inner: Box::new(msg.clone()),
|
||||||
origin_relay_fp: tls_fp.clone(),
|
origin_relay_fp: tls_fp.clone(),
|
||||||
};
|
};
|
||||||
@@ -1407,6 +1428,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let setup_for_callee = SignalMessage::CallSetup {
|
let setup_for_callee = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
relay_addr: relay_addr_for_setup,
|
relay_addr: relay_addr_for_setup,
|
||||||
@@ -1429,6 +1451,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
// cross-wired candidates (Phase 5.5 ICE
|
// cross-wired candidates (Phase 5.5 ICE
|
||||||
// + Phase 8 port-mapped addrs).
|
// + Phase 8 port-mapped addrs).
|
||||||
let setup_for_caller = SignalMessage::CallSetup {
|
let setup_for_caller = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
relay_addr: relay_addr_for_setup.clone(),
|
relay_addr: relay_addr_for_setup.clone(),
|
||||||
@@ -1437,6 +1460,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
peer_mapped_addr: callee_mapped,
|
peer_mapped_addr: callee_mapped,
|
||||||
};
|
};
|
||||||
let setup_for_callee = SignalMessage::CallSetup {
|
let setup_for_callee = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
relay_addr: relay_addr_for_setup,
|
relay_addr: relay_addr_for_setup,
|
||||||
@@ -1524,6 +1548,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(ref fm) = federation_mgr {
|
if let Some(ref fm) = federation_mgr {
|
||||||
let forward =
|
let forward =
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(msg.clone()),
|
inner: Box::new(msg.clone()),
|
||||||
origin_relay_fp: tls_fp.clone(),
|
origin_relay_fp: tls_fp.clone(),
|
||||||
};
|
};
|
||||||
@@ -1568,6 +1593,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(ref fm) = federation_mgr {
|
if let Some(ref fm) = federation_mgr {
|
||||||
let forward =
|
let forward =
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(msg.clone()),
|
inner: Box::new(msg.clone()),
|
||||||
origin_relay_fp: tls_fp.clone(),
|
origin_relay_fp: tls_fp.clone(),
|
||||||
};
|
};
|
||||||
@@ -1615,6 +1641,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let Some(ref fm) = federation_mgr {
|
if let Some(ref fm) = federation_mgr {
|
||||||
let forward =
|
let forward =
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(msg.clone()),
|
inner: Box::new(msg.clone()),
|
||||||
origin_relay_fp: tls_fp.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
|
let _ = transport
|
||||||
.send_signal(&SignalMessage::Pong { timestamp_ms })
|
.send_signal(&SignalMessage::Pong {
|
||||||
|
version: default_signal_version(),
|
||||||
|
timestamp_ms,
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1651,6 +1681,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let observed_addr = addr.to_string();
|
let observed_addr = addr.to_string();
|
||||||
if let Err(e) = transport
|
if let Err(e) = transport
|
||||||
.send_signal(&SignalMessage::ReflectResponse {
|
.send_signal(&SignalMessage::ReflectResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
observed_addr: observed_addr.clone(),
|
observed_addr: observed_addr.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -1710,6 +1741,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.send_to(
|
.send_to(
|
||||||
peer_fp,
|
peer_fp,
|
||||||
&SignalMessage::Hangup {
|
&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: Some(call_id.clone()),
|
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 {
|
let authenticated_fp: Option<String> = if let Some(ref url) = auth_url {
|
||||||
info!(%addr, "waiting for auth token...");
|
info!(%addr, "waiting for auth token...");
|
||||||
match transport.recv_signal().await {
|
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 {
|
match wzp_relay::auth::validate_token(url, &token).await {
|
||||||
Ok(client) => {
|
Ok(client) => {
|
||||||
metrics.auth_attempts.with_label_values(&["ok"]).inc();
|
metrics.auth_attempts.with_label_values(&["ok"]).inc();
|
||||||
@@ -1913,6 +1945,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
if let SignalMessage::RoomUpdate {
|
if let SignalMessage::RoomUpdate {
|
||||||
count: _,
|
count: _,
|
||||||
participants: mut local_parts,
|
participants: mut local_parts,
|
||||||
|
..
|
||||||
} = update
|
} = update
|
||||||
{
|
{
|
||||||
let remote = fm.get_remote_participants(&room_name).await;
|
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();
|
let mut seen = std::collections::HashSet::new();
|
||||||
local_parts.retain(|p| seen.insert(p.fingerprint.clone()));
|
local_parts.retain(|p| seen.insert(p.fingerprint.clone()));
|
||||||
SignalMessage::RoomUpdate {
|
SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: local_parts.len() as u32,
|
count: local_parts.len() as u32,
|
||||||
participants: local_parts,
|
participants: local_parts,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use prometheus::{Gauge, IntGauge, Opts, Registry};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use wzp_proto::{MediaTransport, SignalMessage};
|
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||||
|
|
||||||
/// Configuration for a single probe target.
|
/// Configuration for a single probe target.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -229,7 +229,7 @@ impl ProbeRunner {
|
|||||||
let recv_handle = tokio::spawn(async move {
|
let recv_handle = tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
match recv_transport.recv_signal().await {
|
match recv_transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::Pong { timestamp_ms })) => {
|
Ok(Some(SignalMessage::Pong { timestamp_ms, .. })) => {
|
||||||
let now_ms = SystemTime::now()
|
let now_ms = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -244,6 +244,7 @@ impl ProbeRunner {
|
|||||||
Ok(Some(SignalMessage::PresenceUpdate {
|
Ok(Some(SignalMessage::PresenceUpdate {
|
||||||
fingerprints,
|
fingerprints,
|
||||||
relay_addr,
|
relay_addr,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
if let Some(ref reg) = recv_presence {
|
if let Some(ref reg) = recv_presence {
|
||||||
// Parse the relay_addr; fall back to the connection target
|
// Parse the relay_addr; fall back to the connection target
|
||||||
@@ -293,7 +294,10 @@ impl ProbeRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = transport
|
if let Err(e) = transport
|
||||||
.send_signal(&SignalMessage::Ping { timestamp_ms })
|
.send_signal(&SignalMessage::Ping {
|
||||||
|
version: default_signal_version(),
|
||||||
|
timestamp_ms,
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
error!(target = %self.config.target, "probe ping send error: {e}");
|
error!(target = %self.config.target, "probe ping send error: {e}");
|
||||||
@@ -310,6 +314,7 @@ impl ProbeRunner {
|
|||||||
r.local_fingerprints().into_iter().collect()
|
r.local_fingerprints().into_iter().collect()
|
||||||
};
|
};
|
||||||
let msg = SignalMessage::PresenceUpdate {
|
let msg = SignalMessage::PresenceUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprints: fps,
|
fingerprints: fps,
|
||||||
relay_addr: self.config.target.to_string(),
|
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.
|
/// 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.
|
/// 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 {
|
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
|
if let Err(e) = transport
|
||||||
.send_signal(&SignalMessage::Pong {
|
.send_signal(&SignalMessage::Pong {
|
||||||
|
version: default_signal_version(),
|
||||||
timestamp_ms: *timestamp_ms,
|
timestamp_ms: *timestamp_ms,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -333,10 +333,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn session_forward_signal_roundtrip() {
|
fn session_forward_signal_roundtrip() {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
// SessionForward roundtrip
|
// SessionForward roundtrip
|
||||||
let msg = SignalMessage::SessionForward {
|
let msg = SignalMessage::SessionForward {
|
||||||
|
version: default_signal_version(),
|
||||||
session_id: "abcd1234".to_string(),
|
session_id: "abcd1234".to_string(),
|
||||||
target_fingerprint: "deadbeef".to_string(),
|
target_fingerprint: "deadbeef".to_string(),
|
||||||
source_relay: "10.0.0.1:4433".to_string(),
|
source_relay: "10.0.0.1:4433".to_string(),
|
||||||
@@ -348,6 +349,7 @@ mod tests {
|
|||||||
session_id,
|
session_id,
|
||||||
target_fingerprint,
|
target_fingerprint,
|
||||||
source_relay,
|
source_relay,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(session_id, "abcd1234");
|
assert_eq!(session_id, "abcd1234");
|
||||||
assert_eq!(target_fingerprint, "deadbeef");
|
assert_eq!(target_fingerprint, "deadbeef");
|
||||||
@@ -358,6 +360,7 @@ mod tests {
|
|||||||
|
|
||||||
// SessionForwardAck roundtrip
|
// SessionForwardAck roundtrip
|
||||||
let ack = SignalMessage::SessionForwardAck {
|
let ack = SignalMessage::SessionForwardAck {
|
||||||
|
version: default_signal_version(),
|
||||||
session_id: "abcd1234".to_string(),
|
session_id: "abcd1234".to_string(),
|
||||||
room_name: "relay-room-42".to_string(),
|
room_name: "relay-room-42".to_string(),
|
||||||
};
|
};
|
||||||
@@ -367,6 +370,7 @@ mod tests {
|
|||||||
SignalMessage::SessionForwardAck {
|
SignalMessage::SessionForwardAck {
|
||||||
session_id,
|
session_id,
|
||||||
room_name,
|
room_name,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(session_id, "abcd1234");
|
assert_eq!(session_id, "abcd1234");
|
||||||
assert_eq!(room_name, "relay-room-42");
|
assert_eq!(room_name, "relay-room-42");
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ use bytes::Bytes;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use wzp_proto::MediaTransport;
|
|
||||||
use wzp_proto::packet::TrunkFrame;
|
use wzp_proto::packet::TrunkFrame;
|
||||||
use wzp_proto::quality::{AdaptiveQualityController, Tier};
|
use wzp_proto::quality::{AdaptiveQualityController, Tier};
|
||||||
use wzp_proto::traits::QualityController;
|
use wzp_proto::traits::QualityController;
|
||||||
|
use wzp_proto::{MediaTransport, default_signal_version};
|
||||||
|
|
||||||
use crate::conformance::ConformanceMeter;
|
use crate::conformance::ConformanceMeter;
|
||||||
use crate::metrics::RelayMetrics;
|
use crate::metrics::RelayMetrics;
|
||||||
@@ -64,6 +64,7 @@ impl DebugTap {
|
|||||||
wzp_proto::SignalMessage::RoomUpdate {
|
wzp_proto::SignalMessage::RoomUpdate {
|
||||||
count,
|
count,
|
||||||
participants,
|
participants,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let names: Vec<&str> = participants
|
let names: Vec<&str> = participants
|
||||||
.iter()
|
.iter()
|
||||||
@@ -81,6 +82,7 @@ impl DebugTap {
|
|||||||
wzp_proto::SignalMessage::QualityDirective {
|
wzp_proto::SignalMessage::QualityDirective {
|
||||||
recommended_profile,
|
recommended_profile,
|
||||||
reason,
|
reason,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
info!(
|
info!(
|
||||||
target: "debug_tap",
|
target: "debug_tap",
|
||||||
@@ -493,6 +495,7 @@ impl RoomManager {
|
|||||||
);
|
);
|
||||||
room.qualities.insert(id, ParticipantQuality::new());
|
room.qualities.insert(id, ParticipantQuality::new());
|
||||||
let update = wzp_proto::SignalMessage::RoomUpdate {
|
let update = wzp_proto::SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: room.len() as u32,
|
count: room.len() as u32,
|
||||||
participants: room.participant_list(),
|
participants: room.participant_list(),
|
||||||
};
|
};
|
||||||
@@ -570,6 +573,7 @@ impl RoomManager {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let update = wzp_proto::SignalMessage::RoomUpdate {
|
let update = wzp_proto::SignalMessage::RoomUpdate {
|
||||||
|
version: default_signal_version(),
|
||||||
count: room.len() as u32,
|
count: room.len() as u32,
|
||||||
participants: room.participant_list(),
|
participants: room.participant_list(),
|
||||||
};
|
};
|
||||||
@@ -654,6 +658,7 @@ impl RoomManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let directive = wzp_proto::SignalMessage::QualityDirective {
|
let directive = wzp_proto::SignalMessage::QualityDirective {
|
||||||
|
version: default_signal_version(),
|
||||||
recommended_profile: profile,
|
recommended_profile: profile,
|
||||||
reason: Some(format!("weakest link: {weakest:?}")),
|
reason: Some(format!("weakest link: {weakest:?}")),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -201,9 +201,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn route_query_signal_roundtrip() {
|
fn route_query_signal_roundtrip() {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
let query = SignalMessage::RouteQuery {
|
let query = SignalMessage::RouteQuery {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprint: "aabbccdd".to_string(),
|
fingerprint: "aabbccdd".to_string(),
|
||||||
ttl: 3,
|
ttl: 3,
|
||||||
};
|
};
|
||||||
@@ -211,11 +212,12 @@ mod tests {
|
|||||||
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
decoded,
|
decoded,
|
||||||
SignalMessage::RouteQuery { ref fingerprint, ttl }
|
SignalMessage::RouteQuery { ref fingerprint, ttl, ..}
|
||||||
if fingerprint == "aabbccdd" && ttl == 3
|
if fingerprint == "aabbccdd" && ttl == 3
|
||||||
));
|
));
|
||||||
|
|
||||||
let response = SignalMessage::RouteResponse {
|
let response = SignalMessage::RouteResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
fingerprint: "aabbccdd".to_string(),
|
fingerprint: "aabbccdd".to_string(),
|
||||||
found: true,
|
found: true,
|
||||||
relay_chain: vec!["10.0.0.1:4433".to_string(), "10.0.0.2:4433".to_string()],
|
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();
|
let decoded: SignalMessage = serde_json::from_str(&json).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
decoded,
|
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
|
if fingerprint == "aabbccdd" && found && relay_chain.len() == 2
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use wzp_proto::{MediaTransport, SignalMessage};
|
use wzp_proto::{MediaTransport, SignalMessage, default_signal_version};
|
||||||
use wzp_transport::QuinnTransport;
|
use wzp_transport::QuinnTransport;
|
||||||
|
|
||||||
/// A client connected via `_signal` for direct calling.
|
/// A client connected via `_signal` for direct calling.
|
||||||
@@ -101,7 +101,10 @@ impl SignalHub {
|
|||||||
alias: c.alias.clone(),
|
alias: c.alias.clone(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
SignalMessage::PresenceList { users }
|
SignalMessage::PresenceList {
|
||||||
|
version: default_signal_version(),
|
||||||
|
users,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Broadcast a message to ALL connected signal clients.
|
/// Broadcast a message to ALL connected signal clients.
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
//! Bob's CallSetup carries Alice's reflex addr — cross-wired
|
//! Bob's CallSetup carries Alice's reflex addr — cross-wired
|
||||||
//! through two relays + a federation link.
|
//! 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;
|
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.
|
/// Helper that Alice's place_call sends.
|
||||||
fn alice_offer(call_id: &str) -> SignalMessage {
|
fn alice_offer(call_id: &str) -> SignalMessage {
|
||||||
SignalMessage::DirectCallOffer {
|
SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
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
|
// Build the federation envelope the main loop would
|
||||||
// broadcast.
|
// broadcast.
|
||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(offer.clone()),
|
inner: Box::new(offer.clone()),
|
||||||
origin_relay_fp: RELAY_A_TLS_FP.into(),
|
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 {
|
SignalMessage::FederatedSignalForward {
|
||||||
inner,
|
inner,
|
||||||
origin_relay_fp,
|
origin_relay_fp,
|
||||||
|
..
|
||||||
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
||||||
_ => panic!("not a forward"),
|
_ => 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.
|
/// Bob's answer — AcceptTrusted with his reflex addr.
|
||||||
fn bob_answer(call_id: &str) -> SignalMessage {
|
fn bob_answer(call_id: &str) -> SignalMessage {
|
||||||
SignalMessage::DirectCallAnswer {
|
SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.into(),
|
call_id: call_id.into(),
|
||||||
accept_mode: CallAcceptMode::AcceptTrusted,
|
accept_mode: CallAcceptMode::AcceptTrusted,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
@@ -166,12 +170,14 @@ fn relay_b_handle_local_answer(
|
|||||||
|
|
||||||
// Forward the answer back over federation.
|
// Forward the answer back over federation.
|
||||||
let forward = SignalMessage::FederatedSignalForward {
|
let forward = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(answer.clone()),
|
inner: Box::new(answer.clone()),
|
||||||
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local CallSetup for Bob — peer_direct_addr = Alice's addr.
|
// Local CallSetup for Bob — peer_direct_addr = Alice's addr.
|
||||||
let setup_for_bob = SignalMessage::CallSetup {
|
let setup_for_bob = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: format!("call-{call_id}"),
|
room: format!("call-{call_id}"),
|
||||||
relay_addr: RELAY_B_ADDR.into(),
|
relay_addr: RELAY_B_ADDR.into(),
|
||||||
@@ -194,6 +200,7 @@ fn relay_a_handle_forwarded_answer(
|
|||||||
SignalMessage::FederatedSignalForward {
|
SignalMessage::FederatedSignalForward {
|
||||||
inner,
|
inner,
|
||||||
origin_relay_fp,
|
origin_relay_fp,
|
||||||
|
..
|
||||||
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
} => (inner.as_ref().clone(), origin_relay_fp.clone()),
|
||||||
_ => panic!("not a forward"),
|
_ => panic!("not a forward"),
|
||||||
};
|
};
|
||||||
@@ -215,6 +222,7 @@ fn relay_a_handle_forwarded_answer(
|
|||||||
|
|
||||||
// Alice's CallSetup — peer_direct_addr = Bob's addr.
|
// Alice's CallSetup — peer_direct_addr = Bob's addr.
|
||||||
SignalMessage::CallSetup {
|
SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: format!("call-{call_id}"),
|
room: format!("call-{call_id}"),
|
||||||
relay_addr: RELAY_A_ADDR.into(),
|
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
|
// A FederatedSignalForward that circles back to the origin
|
||||||
// relay should be dropped before it hits the call registry.
|
// relay should be dropped before it hits the call registry.
|
||||||
let forward = SignalMessage::FederatedSignalForward {
|
let forward = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(alice_offer("c-loop")),
|
inner: Box::new(alice_offer("c-loop")),
|
||||||
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
origin_relay_fp: RELAY_B_TLS_FP.into(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bytes::Bytes;
|
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::config::{PeerConfig, TrustedConfig};
|
||||||
use wzp_relay::event_log::EventLogger;
|
use wzp_relay::event_log::EventLogger;
|
||||||
use wzp_relay::federation::{FederationManager, room_hash};
|
use wzp_relay::federation::{FederationManager, room_hash};
|
||||||
@@ -348,7 +348,9 @@ async fn broadcast_signal_sends_to_all_peers() {
|
|||||||
.expect("some message");
|
.expect("some message");
|
||||||
|
|
||||||
match hello {
|
match hello {
|
||||||
SignalMessage::FederationHello { tls_fingerprint } => {
|
SignalMessage::FederationHello {
|
||||||
|
tls_fingerprint, ..
|
||||||
|
} => {
|
||||||
assert_eq!(tls_fingerprint, "test-relay-fp-abc123");
|
assert_eq!(tls_fingerprint, "test-relay-fp-abc123");
|
||||||
}
|
}
|
||||||
other => panic!(
|
other => panic!(
|
||||||
@@ -367,6 +369,7 @@ async fn broadcast_signal_sends_to_all_peers() {
|
|||||||
|
|
||||||
// Now call broadcast_signal on the FM
|
// Now call broadcast_signal on the FM
|
||||||
let test_msg = SignalMessage::FederatedSignalForward {
|
let test_msg = SignalMessage::FederatedSignalForward {
|
||||||
|
version: default_signal_version(),
|
||||||
inner: Box::new(SignalMessage::Reflect),
|
inner: Box::new(SignalMessage::Reflect),
|
||||||
origin_relay_fp: "other-relay-fp".into(),
|
origin_relay_fp: "other-relay-fp".into(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use wzp_client::perform_handshake;
|
use wzp_client::perform_handshake;
|
||||||
use wzp_crypto::{KeyExchange, WarzoneKeyExchange};
|
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_relay::handshake::accept_handshake;
|
||||||
use wzp_transport::{QuinnTransport, client_config, create_endpoint, server_config};
|
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 signature = kx.sign(&sign_data);
|
||||||
|
|
||||||
let v1_offer = SignalMessage::CallOffer {
|
let v1_offer = SignalMessage::CallOffer {
|
||||||
|
version: 1,
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
@@ -255,7 +256,7 @@ async fn auth_then_handshake() {
|
|||||||
.expect("should receive a message");
|
.expect("should receive a message");
|
||||||
|
|
||||||
let token = match auth_msg {
|
let token = match auth_msg {
|
||||||
SignalMessage::AuthToken { token } => token,
|
SignalMessage::AuthToken { token, .. } => token,
|
||||||
other => panic!(
|
other => panic!(
|
||||||
"expected AuthToken, got {:?}",
|
"expected AuthToken, got {:?}",
|
||||||
std::mem::discriminant(&other)
|
std::mem::discriminant(&other)
|
||||||
@@ -273,6 +274,7 @@ async fn auth_then_handshake() {
|
|||||||
|
|
||||||
// Caller side: send AuthToken first, then perform_handshake.
|
// Caller side: send AuthToken first, then perform_handshake.
|
||||||
let auth = SignalMessage::AuthToken {
|
let auth = SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
token: "bearer-test-token-12345".to_string(),
|
token: "bearer-test-token-12345".to_string(),
|
||||||
};
|
};
|
||||||
client_transport
|
client_transport
|
||||||
@@ -344,6 +346,7 @@ async fn handshake_rejects_bad_signature() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bad_offer = SignalMessage::CallOffer {
|
let bad_offer = SignalMessage::CallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
ephemeral_pub,
|
ephemeral_pub,
|
||||||
signature,
|
signature,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
//! to reason about, no real network, and what we actually want to
|
//! to reason about, no real network, and what we actually want to
|
||||||
//! test is the cross-wiring logic, not the whole signal stack.
|
//! 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;
|
use wzp_relay::call_registry::CallRegistry;
|
||||||
|
|
||||||
/// Helper: simulate the relay's handling of a DirectCallOffer. In
|
/// 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 {
|
let setup_for_caller = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
room: room.clone(),
|
room: room.clone(),
|
||||||
relay_addr: "203.0.113.5:4433".into(),
|
relay_addr: "203.0.113.5:4433".into(),
|
||||||
@@ -85,6 +86,7 @@ fn handle_answer_and_build_setups(
|
|||||||
peer_mapped_addr: None,
|
peer_mapped_addr: None,
|
||||||
};
|
};
|
||||||
let setup_for_callee = SignalMessage::CallSetup {
|
let setup_for_callee = SignalMessage::CallSetup {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id,
|
call_id,
|
||||||
room,
|
room,
|
||||||
relay_addr: "203.0.113.5:4433".into(),
|
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 {
|
fn mk_offer(call_id: &str, caller_reflexive_addr: Option<&str>) -> SignalMessage {
|
||||||
SignalMessage::DirectCallOffer {
|
SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: "alice".into(),
|
caller_fingerprint: "alice".into(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: "bob".into(),
|
target_fingerprint: "bob".into(),
|
||||||
@@ -118,6 +121,7 @@ fn mk_answer(
|
|||||||
callee_reflexive_addr: Option<&str>,
|
callee_reflexive_addr: Option<&str>,
|
||||||
) -> SignalMessage {
|
) -> SignalMessage {
|
||||||
SignalMessage::DirectCallAnswer {
|
SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.into(),
|
call_id: call_id.into(),
|
||||||
accept_mode: mode,
|
accept_mode: mode,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ async fn spawn_mock_relay() -> (SocketAddr, tokio::task::JoinHandle<()>) {
|
|||||||
Ok(Some(SignalMessage::RegisterPresence { .. })) => {
|
Ok(Some(SignalMessage::RegisterPresence { .. })) => {
|
||||||
let _ = t
|
let _ = t
|
||||||
.send_signal(&SignalMessage::RegisterPresenceAck {
|
.send_signal(&SignalMessage::RegisterPresenceAck {
|
||||||
|
version: 1,
|
||||||
success: true,
|
success: true,
|
||||||
error: None,
|
error: None,
|
||||||
relay_build: None,
|
relay_build: None,
|
||||||
@@ -74,6 +75,7 @@ async fn spawn_mock_relay() -> (SocketAddr, tokio::task::JoinHandle<()>) {
|
|||||||
Ok(Some(SignalMessage::Reflect)) => {
|
Ok(Some(SignalMessage::Reflect)) => {
|
||||||
let _ = t
|
let _ = t
|
||||||
.send_signal(&SignalMessage::ReflectResponse {
|
.send_signal(&SignalMessage::ReflectResponse {
|
||||||
|
version: 1,
|
||||||
observed_addr: observed_addr.to_string(),
|
observed_addr: observed_addr.to_string(),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use std::net::{Ipv4Addr, SocketAddr};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
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};
|
use wzp_transport::{QuinnTransport, client_config, create_endpoint, server_config};
|
||||||
|
|
||||||
/// Spawn a minimal mock relay that loops over `recv_signal`,
|
/// 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 {
|
match server_transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::Reflect)) => {
|
Ok(Some(SignalMessage::Reflect)) => {
|
||||||
let resp = SignalMessage::ReflectResponse {
|
let resp = SignalMessage::ReflectResponse {
|
||||||
|
version: default_signal_version(),
|
||||||
observed_addr: observed.to_string(),
|
observed_addr: observed.to_string(),
|
||||||
};
|
};
|
||||||
// If the send fails the client has gone; just exit.
|
// If the send fails the client has gone; just exit.
|
||||||
@@ -164,7 +165,7 @@ async fn reflect_happy_path() {
|
|||||||
.expect("some message");
|
.expect("some message");
|
||||||
|
|
||||||
let observed_addr = match resp {
|
let observed_addr = match resp {
|
||||||
SignalMessage::ReflectResponse { observed_addr } => observed_addr,
|
SignalMessage::ReflectResponse { observed_addr, .. } => observed_addr,
|
||||||
other => panic!(
|
other => panic!(
|
||||||
"expected ReflectResponse, got {:?}",
|
"expected ReflectResponse, got {:?}",
|
||||||
std::mem::discriminant(&other)
|
std::mem::discriminant(&other)
|
||||||
@@ -251,7 +252,7 @@ async fn reflect_two_clients_distinct_ports() {
|
|||||||
.expect("ok")
|
.expect("ok")
|
||||||
.expect("some");
|
.expect("some");
|
||||||
match resp {
|
match resp {
|
||||||
SignalMessage::ReflectResponse { observed_addr } => observed_addr,
|
SignalMessage::ReflectResponse { observed_addr, .. } => observed_addr,
|
||||||
_ => panic!("wrong variant"),
|
_ => panic!("wrong variant"),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use tower_http::services::ServeDir;
|
|||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use wzp_client::call::{CallConfig, CallDecoder, CallEncoder};
|
use wzp_client::call::{CallConfig, CallDecoder, CallEncoder};
|
||||||
use wzp_proto::MediaTransport;
|
use wzp_proto::{MediaTransport, default_signal_version};
|
||||||
|
|
||||||
mod metrics;
|
mod metrics;
|
||||||
use metrics::WebMetrics;
|
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)
|
// Send auth token to relay (if auth is enabled)
|
||||||
if let Some(ref token) = browser_token {
|
if let Some(ref token) = browser_token {
|
||||||
let auth = wzp_proto::SignalMessage::AuthToken {
|
let auth = wzp_proto::SignalMessage::AuthToken {
|
||||||
|
version: default_signal_version(),
|
||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
};
|
};
|
||||||
if let Err(e) = transport.send_signal(&auth).await {
|
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 {
|
Ok(Ok(Some(wzp_proto::SignalMessage::QualityDirective {
|
||||||
recommended_profile,
|
recommended_profile,
|
||||||
reason,
|
reason,
|
||||||
|
..
|
||||||
}))) => {
|
}))) => {
|
||||||
let idx = profile_to_index(&recommended_profile);
|
let idx = profile_to_index(&recommended_profile);
|
||||||
info!(
|
info!(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use tauri::{Emitter, Manager};
|
use tauri::{Emitter, Manager};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use wzp_proto::MediaTransport;
|
use wzp_proto::{MediaTransport, default_signal_version};
|
||||||
|
|
||||||
// ─── Call-flow debug logs (GUI-gated) ────────────────────────────────
|
// ─── Call-flow debug logs (GUI-gated) ────────────────────────────────
|
||||||
//
|
//
|
||||||
@@ -615,6 +615,7 @@ async fn connect(
|
|||||||
// Send our report
|
// Send our report
|
||||||
if let Some(ref t) = transport_for_report {
|
if let Some(ref t) = transport_for_report {
|
||||||
let report = wzp_proto::SignalMessage::MediaPathReport {
|
let report = wzp_proto::SignalMessage::MediaPathReport {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id_for_report.clone(),
|
call_id: call_id_for_report.clone(),
|
||||||
direct_ok: local_direct_ok,
|
direct_ok: local_direct_ok,
|
||||||
race_winner: format!("{:?}", local_winner),
|
race_winner: format!("{:?}", local_winner),
|
||||||
@@ -1247,7 +1248,7 @@ fn do_register_signal(
|
|||||||
relay: String,
|
relay: String,
|
||||||
) -> impl std::future::Future<Output = Result<String, String>> + Send {
|
) -> impl std::future::Future<Output = Result<String, String>> + Send {
|
||||||
async move {
|
async move {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
&app,
|
&app,
|
||||||
@@ -1316,6 +1317,7 @@ fn do_register_signal(
|
|||||||
let alias = derive_alias(&seed);
|
let alias = derive_alias(&seed);
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::RegisterPresence {
|
.send_signal(&SignalMessage::RegisterPresence {
|
||||||
|
version: default_signal_version(),
|
||||||
identity_pub,
|
identity_pub,
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
alias: Some(alias),
|
alias: Some(alias),
|
||||||
@@ -1375,7 +1377,7 @@ fn do_register_signal(
|
|||||||
let signal_state = signal_state_loop.clone();
|
let signal_state = signal_state_loop.clone();
|
||||||
loop {
|
loop {
|
||||||
match transport.recv_signal().await {
|
match transport.recv_signal().await {
|
||||||
Ok(Some(SignalMessage::CallRinging { call_id })) => {
|
Ok(Some(SignalMessage::CallRinging { call_id, .. })) => {
|
||||||
tracing::info!(%call_id, "signal: CallRinging");
|
tracing::info!(%call_id, "signal: CallRinging");
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
&app_clone,
|
&app_clone,
|
||||||
@@ -1453,6 +1455,7 @@ fn do_register_signal(
|
|||||||
peer_direct_addr,
|
peer_direct_addr,
|
||||||
peer_local_addrs,
|
peer_local_addrs,
|
||||||
peer_mapped_addr,
|
peer_mapped_addr,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
// Phase 3: peer_direct_addr carries the OTHER party's
|
// Phase 3: peer_direct_addr carries the OTHER party's
|
||||||
// reflex addr. Phase 5.5: peer_local_addrs carries
|
// reflex addr. Phase 5.5: peer_local_addrs carries
|
||||||
@@ -1512,6 +1515,7 @@ fn do_register_signal(
|
|||||||
call_id,
|
call_id,
|
||||||
direct_ok,
|
direct_ok,
|
||||||
race_winner,
|
race_winner,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
// Phase 6: the peer is telling us whether
|
// Phase 6: the peer is telling us whether
|
||||||
// their direct path succeeded. Fire the
|
// their direct path succeeded. Fire the
|
||||||
@@ -1543,6 +1547,7 @@ fn do_register_signal(
|
|||||||
local_addrs,
|
local_addrs,
|
||||||
mapped_addr,
|
mapped_addr,
|
||||||
generation,
|
generation,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
// Phase 8: peer re-gathered candidates after a
|
// Phase 8: peer re-gathered candidates after a
|
||||||
// network change. Emit to JS for UI notification
|
// network change. Emit to JS for UI notification
|
||||||
@@ -1586,6 +1591,7 @@ fn do_register_signal(
|
|||||||
allocation,
|
allocation,
|
||||||
probe_time_ms,
|
probe_time_ms,
|
||||||
external_ip,
|
external_ip,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
%call_id,
|
%call_id,
|
||||||
@@ -1647,6 +1653,7 @@ fn do_register_signal(
|
|||||||
let _ = t
|
let _ = t
|
||||||
.send_signal(
|
.send_signal(
|
||||||
&wzp_proto::SignalMessage::HardNatBirthdayStart {
|
&wzp_proto::SignalMessage::HardNatBirthdayStart {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id_bg,
|
call_id: call_id_bg,
|
||||||
acceptor_port_count: result.succeeded,
|
acceptor_port_count: result.succeeded,
|
||||||
acceptor_ports: ext_ports,
|
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");
|
tracing::info!(count = users.len(), "signal: PresenceList received");
|
||||||
// Emit to JS frontend for lobby user list
|
// Emit to JS frontend for lobby user list
|
||||||
let user_list: Vec<serde_json::Value> = users
|
let user_list: Vec<serde_json::Value> = users
|
||||||
@@ -1687,6 +1694,7 @@ fn do_register_signal(
|
|||||||
proposed_profile,
|
proposed_profile,
|
||||||
local_loss_pct,
|
local_loss_pct,
|
||||||
local_rtt_ms,
|
local_rtt_ms,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(%call_id, %proposal_id, ?proposed_profile, "signal: UpgradeProposal from peer");
|
tracing::info!(%call_id, %proposal_id, ?proposed_profile, "signal: UpgradeProposal from peer");
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
@@ -1706,6 +1714,7 @@ fn do_register_signal(
|
|||||||
proposal_id,
|
proposal_id,
|
||||||
accepted,
|
accepted,
|
||||||
reason,
|
reason,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(%call_id, %proposal_id, accepted, ?reason, "signal: UpgradeResponse from peer");
|
tracing::info!(%call_id, %proposal_id, accepted, ?reason, "signal: UpgradeResponse from peer");
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
@@ -1722,6 +1731,7 @@ fn do_register_signal(
|
|||||||
call_id,
|
call_id,
|
||||||
proposal_id,
|
proposal_id,
|
||||||
confirmed_profile,
|
confirmed_profile,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(%call_id, %proposal_id, ?confirmed_profile, "signal: UpgradeConfirm");
|
tracing::info!(%call_id, %proposal_id, ?confirmed_profile, "signal: UpgradeConfirm");
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
@@ -1739,6 +1749,7 @@ fn do_register_signal(
|
|||||||
max_profile,
|
max_profile,
|
||||||
loss_pct,
|
loss_pct,
|
||||||
rtt_ms,
|
rtt_ms,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(%call_id, ?max_profile, "signal: QualityCapability from peer");
|
tracing::info!(%call_id, ?max_profile, "signal: QualityCapability from peer");
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
@@ -1758,6 +1769,7 @@ fn do_register_signal(
|
|||||||
acceptor_port_count,
|
acceptor_port_count,
|
||||||
acceptor_ports,
|
acceptor_ports,
|
||||||
external_ip,
|
external_ip,
|
||||||
|
..
|
||||||
})) => {
|
})) => {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
%call_id,
|
%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
|
// "STUN for QUIC" response — the relay told us our
|
||||||
// own server-reflexive address. If a Tauri command
|
// own server-reflexive address. If a Tauri command
|
||||||
// is currently awaiting this, fire the oneshot;
|
// is currently awaiting this, fire the oneshot;
|
||||||
@@ -2009,7 +2021,7 @@ async fn place_call(
|
|||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
target_fp: String,
|
target_fp: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
emit_call_debug(
|
emit_call_debug(
|
||||||
&app,
|
&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");
|
tracing::info!(%call_id, %target_fp, reflex = ?own_reflex, mapped = ?caller_mapped_addr, "place_call: sending DirectCallOffer");
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::DirectCallOffer {
|
.send_signal(&SignalMessage::DirectCallOffer {
|
||||||
|
version: default_signal_version(),
|
||||||
caller_fingerprint: sig.fingerprint.clone(),
|
caller_fingerprint: sig.fingerprint.clone(),
|
||||||
caller_alias: None,
|
caller_alias: None,
|
||||||
target_fingerprint: target_fp.clone(),
|
target_fingerprint: target_fp.clone(),
|
||||||
@@ -2199,6 +2212,7 @@ async fn place_call(
|
|||||||
if let Some(ref t) = sig.transport {
|
if let Some(ref t) = sig.transport {
|
||||||
let _ = t
|
let _ = t
|
||||||
.send_signal(&SignalMessage::HardNatProbe {
|
.send_signal(&SignalMessage::HardNatProbe {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id_bg,
|
call_id: call_id_bg,
|
||||||
port_sequence: result.observed_ports,
|
port_sequence: result.observed_ports,
|
||||||
allocation: alloc_str,
|
allocation: alloc_str,
|
||||||
@@ -2228,7 +2242,7 @@ async fn answer_call(
|
|||||||
call_id: String,
|
call_id: String,
|
||||||
mode: i32,
|
mode: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
let accept_mode = match mode {
|
let accept_mode = match mode {
|
||||||
0 => wzp_proto::CallAcceptMode::Reject,
|
0 => wzp_proto::CallAcceptMode::Reject,
|
||||||
1 => wzp_proto::CallAcceptMode::AcceptTrusted,
|
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");
|
tracing::info!(%call_id, ?accept_mode, reflex = ?own_reflex, mapped = ?callee_mapped_addr, "answer_call: sending DirectCallAnswer");
|
||||||
transport
|
transport
|
||||||
.send_signal(&SignalMessage::DirectCallAnswer {
|
.send_signal(&SignalMessage::DirectCallAnswer {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id.clone(),
|
call_id: call_id.clone(),
|
||||||
accept_mode,
|
accept_mode,
|
||||||
identity_pub: None,
|
identity_pub: None,
|
||||||
@@ -2437,6 +2452,7 @@ async fn answer_call(
|
|||||||
if let Some(ref t) = sig.transport {
|
if let Some(ref t) = sig.transport {
|
||||||
let _ = t
|
let _ = t
|
||||||
.send_signal(&wzp_proto::SignalMessage::HardNatProbe {
|
.send_signal(&wzp_proto::SignalMessage::HardNatProbe {
|
||||||
|
version: default_signal_version(),
|
||||||
call_id: call_id_bg,
|
call_id: call_id_bg,
|
||||||
port_sequence: result.observed_ports,
|
port_sequence: result.observed_ports,
|
||||||
allocation: alloc_str,
|
allocation: alloc_str,
|
||||||
@@ -2475,7 +2491,7 @@ async fn answer_call(
|
|||||||
/// or temporarily unreachable for reflect but the call can still
|
/// or temporarily unreachable for reflect but the call can still
|
||||||
/// proceed with STUN-discovered addresses.
|
/// proceed with STUN-discovered addresses.
|
||||||
async fn try_reflect_own_addr(state: &Arc<AppState>) -> Result<Option<String>, String> {
|
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 (tx, rx) = tokio::sync::oneshot::channel::<std::net::SocketAddr>();
|
||||||
let transport = {
|
let transport = {
|
||||||
let mut sig = state.signal.lock().await;
|
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.
|
/// with `new URL(...)` / a regex if needed.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_reflected_address(state: tauri::State<'_, Arc<AppState>>) -> Result<String, String> {
|
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 (tx, rx) = tokio::sync::oneshot::channel::<std::net::SocketAddr>();
|
||||||
let transport = {
|
let transport = {
|
||||||
let mut sig = state.signal.lock().await;
|
let mut sig = state.signal.lock().await;
|
||||||
@@ -2766,7 +2782,7 @@ async fn hangup_call(
|
|||||||
state: tauri::State<'_, Arc<AppState>>,
|
state: tauri::State<'_, Arc<AppState>>,
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use wzp_proto::SignalMessage;
|
use wzp_proto::{SignalMessage, default_signal_version};
|
||||||
|
|
||||||
emit_call_debug(&app, "hangup_call:start", serde_json::json!({}));
|
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 {
|
if let Some(ref transport) = sig.transport {
|
||||||
match transport
|
match transport
|
||||||
.send_signal(&SignalMessage::Hangup {
|
.send_signal(&SignalMessage::Hangup {
|
||||||
|
version: default_signal_version(),
|
||||||
reason: wzp_proto::HangupReason::Normal,
|
reason: wzp_proto::HangupReason::Normal,
|
||||||
call_id: None,
|
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. |
|
| 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.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.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.4 | Open | — | — | — | — | — |
|
||||||
| T3.5 | Open | — | — | — | — | — |
|
| T3.5 | Open | — | — | — | — | — |
|
||||||
| T4.1 | Open | — | — | — | — | Skeleton — expand before claiming |
|
| 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
|
- 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.1 — Add `SignalMessage::TransportFeedback` — report: reports/T2.1-report.md
|
||||||
- T2.2 — `BandwidthEstimator` in `wzp-proto::bandwidth` — report: reports/T2.2-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`).
|
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