- ARCHITECTURE.md: protocol design, wire format, FEC, crypto, relay modes - USAGE.md: build instructions, all CLI flags, deployment examples - DESIGN.md: rationale for codec/FEC/transport/crypto choices - EXTENSIBILITY.md: trait extension points, Warzone integration, future features - PROGRESS.md: phase 1-4 timeline, test coverage, known issues - API.md: complete crate API reference for all 8 crates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 KiB
WarzonePhone Crate API Reference
wzp-proto
Path: crates/wzp-proto/src/
The protocol definition crate. Contains all shared types, trait interfaces, and core logic. No implementation dependencies -- this is the hub of the star dependency graph.
Traits (traits.rs)
/// Encodes PCM audio into compressed frames.
pub trait AudioEncoder: Send + Sync {
fn encode(&mut self, pcm: &[i16], out: &mut [u8]) -> Result<usize, CodecError>;
fn codec_id(&self) -> CodecId;
fn set_profile(&mut self, profile: QualityProfile) -> Result<(), CodecError>;
fn max_frame_bytes(&self) -> usize;
fn set_inband_fec(&mut self, _enabled: bool) {} // default no-op
fn set_dtx(&mut self, _enabled: bool) {} // default no-op
}
/// Decodes compressed frames back to PCM audio.
pub trait AudioDecoder: Send + Sync {
fn decode(&mut self, encoded: &[u8], pcm: &mut [i16]) -> Result<usize, CodecError>;
fn decode_lost(&mut self, pcm: &mut [i16]) -> Result<usize, CodecError>;
fn codec_id(&self) -> CodecId;
fn set_profile(&mut self, profile: QualityProfile) -> Result<(), CodecError>;
}
/// Encodes source symbols into FEC-protected blocks.
pub trait FecEncoder: Send + Sync {
fn add_source_symbol(&mut self, data: &[u8]) -> Result<(), FecError>;
fn generate_repair(&mut self, ratio: f32) -> Result<Vec<(u8, Vec<u8>)>, FecError>;
fn finalize_block(&mut self) -> Result<u8, FecError>;
fn current_block_id(&self) -> u8;
fn current_block_size(&self) -> usize;
}
/// Decodes FEC-protected blocks, recovering lost source symbols.
pub trait FecDecoder: Send + Sync {
fn add_symbol(&mut self, block_id: u8, symbol_index: u8, is_repair: bool, data: &[u8]) -> Result<(), FecError>;
fn try_decode(&mut self, block_id: u8) -> Result<Option<Vec<Vec<u8>>>, FecError>;
fn expire_before(&mut self, block_id: u8);
}
/// Per-call encryption session (symmetric, after key exchange).
pub trait CryptoSession: Send + Sync {
fn encrypt(&mut self, header_bytes: &[u8], plaintext: &[u8], out: &mut Vec<u8>) -> Result<(), CryptoError>;
fn decrypt(&mut self, header_bytes: &[u8], ciphertext: &[u8], out: &mut Vec<u8>) -> Result<(), CryptoError>;
fn initiate_rekey(&mut self) -> Result<[u8; 32], CryptoError>;
fn complete_rekey(&mut self, peer_ephemeral_pub: &[u8; 32]) -> Result<(), CryptoError>;
fn overhead(&self) -> usize { 16 } // ChaCha20-Poly1305 tag
}
/// Key exchange using the Warzone identity model.
pub trait KeyExchange: Send + Sync {
fn from_identity_seed(seed: &[u8; 32]) -> Self where Self: Sized;
fn generate_ephemeral(&mut self) -> [u8; 32];
fn identity_public_key(&self) -> [u8; 32];
fn fingerprint(&self) -> [u8; 16];
fn sign(&self, data: &[u8]) -> Vec<u8>;
fn verify(peer_identity_pub: &[u8; 32], data: &[u8], signature: &[u8]) -> bool where Self: Sized;
fn derive_session(&self, peer_ephemeral_pub: &[u8; 32]) -> Result<Box<dyn CryptoSession>, CryptoError>;
}
/// Transport layer for sending/receiving media and signaling.
#[async_trait]
pub trait MediaTransport: Send + Sync {
async fn send_media(&self, packet: &MediaPacket) -> Result<(), TransportError>;
async fn recv_media(&self) -> Result<Option<MediaPacket>, TransportError>;
async fn send_signal(&self, msg: &SignalMessage) -> Result<(), TransportError>;
async fn recv_signal(&self) -> Result<Option<SignalMessage>, TransportError>;
fn path_quality(&self) -> PathQuality;
async fn close(&self) -> Result<(), TransportError>;
}
/// Wraps/unwraps packets for DPI evasion (Phase 2).
pub trait ObfuscationLayer: Send + Sync {
fn obfuscate(&mut self, data: &[u8], out: &mut Vec<u8>) -> Result<(), ObfuscationError>;
fn deobfuscate(&mut self, data: &[u8], out: &mut Vec<u8>) -> Result<(), ObfuscationError>;
}
/// Adaptive quality controller.
pub trait QualityController: Send + Sync {
fn observe(&mut self, report: &QualityReport) -> Option<QualityProfile>;
fn force_profile(&mut self, profile: QualityProfile);
fn current_profile(&self) -> QualityProfile;
}
Wire Format Types (packet.rs)
pub struct MediaHeader { /* 12 bytes */ }
pub struct QualityReport { /* 4 bytes */ }
pub struct MediaPacket { pub header: MediaHeader, pub payload: Bytes, pub quality_report: Option<QualityReport> }
pub enum SignalMessage { CallOffer{..}, CallAnswer{..}, IceCandidate{..}, Rekey{..}, QualityUpdate{..}, Ping{..}, Pong{..}, Hangup{..} }
pub enum HangupReason { Normal, Busy, Declined, Timeout, Error }
Key methods:
MediaHeader::write_to(&self, buf: &mut impl BufMut)-- serialize to 12 bytesMediaHeader::read_from(buf: &mut impl Buf) -> Option<Self>-- deserializeMediaHeader::encode_fec_ratio(ratio: f32) -> u8-- float to 7-bit wire encodingMediaHeader::decode_fec_ratio(encoded: u8) -> f32-- 7-bit wire to floatMediaPacket::to_bytes(&self) -> Bytes-- serialize complete packetMediaPacket::from_bytes(data: Bytes) -> Option<Self>-- deserialize
Codec Identifiers (codec_id.rs)
pub enum CodecId { Opus24k = 0, Opus16k = 1, Opus6k = 2, Codec2_3200 = 3, Codec2_1200 = 4 }
pub struct QualityProfile {
pub codec: CodecId,
pub fec_ratio: f32,
pub frame_duration_ms: u8,
pub frames_per_block: u8,
}
Constants: QualityProfile::GOOD, QualityProfile::DEGRADED, QualityProfile::CATASTROPHIC
Key methods:
CodecId::bitrate_bps(self) -> u32CodecId::frame_duration_ms(self) -> u8CodecId::sample_rate_hz(self) -> u32CodecId::from_wire(val: u8) -> Option<Self>CodecId::to_wire(self) -> u8QualityProfile::total_bitrate_kbps(&self) -> f32
Quality Controller (quality.rs)
pub enum Tier { Good, Degraded, Catastrophic }
pub struct AdaptiveQualityController { /* ... */ }
Key methods:
AdaptiveQualityController::new() -> Self-- starts at Tier::GoodAdaptiveQualityController::tier(&self) -> TierTier::classify(report: &QualityReport) -> SelfTier::profile(self) -> QualityProfile
Jitter Buffer (jitter.rs)
pub struct JitterBuffer { /* ... */ }
pub struct JitterStats { pub packets_received: u64, pub packets_played: u64, pub packets_lost: u64, pub packets_late: u64, pub packets_duplicate: u64, pub current_depth: usize }
pub enum PlayoutResult { Packet(MediaPacket), Missing { seq: u16 }, NotReady }
Key methods:
JitterBuffer::new(target_depth: usize, max_depth: usize, min_depth: usize) -> SelfJitterBuffer::default_5s() -> Self-- target=50, max=250, min=25JitterBuffer::push(&mut self, packet: MediaPacket)JitterBuffer::pop(&mut self) -> PlayoutResultJitterBuffer::depth(&self) -> usizeJitterBuffer::stats(&self) -> &JitterStatsJitterBuffer::reset(&mut self)JitterBuffer::set_target_depth(&mut self, depth: usize)
Session State Machine (session.rs)
pub enum SessionState { Idle, Connecting, Handshaking, Active, Rekeying, Closed }
pub enum SessionEvent { Initiate, Connected, HandshakeComplete, RekeyStart, RekeyComplete, Terminate{reason}, ConnectionLost }
pub struct Session { /* ... */ }
Key methods:
Session::new(session_id: [u8; 16]) -> SelfSession::state(&self) -> SessionStateSession::transition(&mut self, event: SessionEvent, now_ms: u64) -> Result<SessionState, TransitionError>Session::is_media_active(&self) -> bool-- true for Active and Rekeying
Error Types (error.rs)
pub enum CodecError { EncodeFailed(String), DecodeFailed(String), UnsupportedTransition{from, to} }
pub enum FecError { BlockFull{max}, InsufficientSymbols{needed, have}, InvalidBlock(u8), Internal(String) }
pub enum CryptoError { DecryptionFailed, InvalidPublicKey, RekeyFailed(String), ReplayDetected{seq}, Internal(String) }
pub enum TransportError { ConnectionLost, DatagramTooLarge{size, max}, Timeout{ms}, Io(io::Error), Internal(String) }
pub enum ObfuscationError { Failed(String), InvalidFraming }
PathQuality (traits.rs)
pub struct PathQuality {
pub loss_pct: f32, // 0.0-100.0
pub rtt_ms: u32,
pub jitter_ms: u32,
pub bandwidth_kbps: u32,
}
wzp-codec
Path: crates/wzp-codec/src/
Factory Functions (lib.rs)
/// Create an adaptive encoder (accepts 48 kHz PCM, handles resampling for Codec2).
pub fn create_encoder(profile: QualityProfile) -> Box<dyn AudioEncoder>
/// Create an adaptive decoder (outputs 48 kHz PCM, handles upsampling from Codec2).
pub fn create_decoder(profile: QualityProfile) -> Box<dyn AudioDecoder>
Public Types
pub struct AdaptiveEncoder { /* wraps OpusEncoder + Codec2Encoder */ }
pub struct AdaptiveDecoder { /* wraps OpusDecoder + Codec2Decoder */ }
pub struct OpusEncoder { /* audiopus::coder::Encoder wrapper */ }
pub struct OpusDecoder { /* audiopus::coder::Decoder wrapper */ }
pub struct Codec2Encoder { /* codec2::Codec2 wrapper */ }
pub struct Codec2Decoder { /* codec2::Codec2 wrapper */ }
Key methods on concrete types:
OpusEncoder::new(profile: QualityProfile) -> Result<Self, CodecError>OpusEncoder::frame_samples(&self) -> usize-- 960 for 20ms, 1920 for 40msCodec2Encoder::new(profile: QualityProfile) -> Result<Self, CodecError>Codec2Encoder::frame_samples(&self) -> usize-- 160 for 20ms/3200bps, 320 for 40ms/1200bps
Resampler (resample.rs)
pub fn resample_48k_to_8k(input: &[i16]) -> Vec<i16> // 6:1 decimation with box filter
pub fn resample_8k_to_48k(input: &[i16]) -> Vec<i16> // 1:6 linear interpolation
wzp-fec
Path: crates/wzp-fec/src/
Factory Functions (lib.rs)
/// Create an encoder/decoder pair configured for the given quality profile.
pub fn create_fec_pair(profile: &QualityProfile) -> (RaptorQFecEncoder, RaptorQFecDecoder)
/// Create an encoder configured for the given quality profile.
pub fn create_encoder(profile: &QualityProfile) -> RaptorQFecEncoder
/// Create a decoder configured for the given quality profile.
pub fn create_decoder(profile: &QualityProfile) -> RaptorQFecDecoder
RaptorQFecEncoder (encoder.rs)
pub struct RaptorQFecEncoder { /* block_id, frames_per_block, source_symbols, symbol_size */ }
Key methods:
RaptorQFecEncoder::new(frames_per_block: usize, symbol_size: u16) -> SelfRaptorQFecEncoder::with_defaults(frames_per_block: usize) -> Self-- symbol_size=256- Implements
FecEncodertrait
RaptorQFecDecoder (decoder.rs)
pub struct RaptorQFecDecoder { /* blocks: HashMap<u8, BlockState>, symbol_size, frames_per_block */ }
Key methods:
RaptorQFecDecoder::new(frames_per_block: usize, symbol_size: u16) -> SelfRaptorQFecDecoder::with_defaults(frames_per_block: usize) -> Self- Implements
FecDecodertrait
Interleaver (interleave.rs)
pub type Symbol = (u8, u8, bool, Vec<u8>); // (block_id, symbol_index, is_repair, data)
pub struct Interleaver { depth: usize }
Key methods:
Interleaver::new(depth: usize) -> SelfInterleaver::with_default_depth() -> Self-- depth=3Interleaver::interleave(&self, blocks: &[Vec<Symbol>]) -> Vec<Symbol>Interleaver::depth(&self) -> usize
AdaptiveFec (adaptive.rs)
pub struct AdaptiveFec { pub frames_per_block: usize, pub repair_ratio: f32, pub symbol_size: u16 }
Key methods:
AdaptiveFec::from_profile(profile: &QualityProfile) -> SelfAdaptiveFec::build_encoder(&self) -> RaptorQFecEncoderAdaptiveFec::ratio(&self) -> f32AdaptiveFec::overhead_factor(&self) -> f32-- 1.0 + repair_ratio
Block Managers (block_manager.rs)
pub enum EncoderBlockState { Building, Pending, Sent, Acknowledged }
pub enum DecoderBlockState { Assembling, Complete, Expired }
pub struct EncoderBlockManager { /* ... */ }
pub struct DecoderBlockManager { /* ... */ }
Key methods:
EncoderBlockManager::next_block_id(&mut self) -> u8EncoderBlockManager::mark_sent(&mut self, block_id: u8)EncoderBlockManager::mark_acknowledged(&mut self, block_id: u8)DecoderBlockManager::touch(&mut self, block_id: u8)DecoderBlockManager::mark_complete(&mut self, block_id: u8)DecoderBlockManager::expire_before(&mut self, block_id: u8)
Helper Functions (encoder.rs)
/// Build source EncodingPackets for a given block (for testing/interleaving).
pub fn source_packets_for_block(block_id: u8, symbols: &[Vec<u8>], symbol_size: u16) -> Vec<EncodingPacket>
/// Generate repair packets for the given source symbols.
pub fn repair_packets_for_block(block_id: u8, symbols: &[Vec<u8>], symbol_size: u16, ratio: f32) -> Vec<EncodingPacket>
wzp-crypto
Path: crates/wzp-crypto/src/
Re-exports (lib.rs)
pub use anti_replay::AntiReplayWindow;
pub use handshake::WarzoneKeyExchange;
pub use nonce::{build_nonce, Direction};
pub use rekey::RekeyManager;
pub use session::ChaChaSession;
pub use wzp_proto::{CryptoError, CryptoSession, KeyExchange};
WarzoneKeyExchange (handshake.rs)
pub struct WarzoneKeyExchange { /* signing_key, x25519_static, ephemeral_secret */ }
Implements KeyExchange trait. Key derivation:
- Ed25519:
HKDF(seed, "warzone-ed25519-identity") - X25519:
HKDF(seed, "warzone-x25519-identity") - Session:
HKDF(X25519_DH_shared_secret, "warzone-session-key")
ChaChaSession (session.rs)
pub struct ChaChaSession { /* cipher, session_id, send_seq, recv_seq, rekey_mgr, pending_rekey_secret */ }
Key methods:
ChaChaSession::new(shared_secret: [u8; 32]) -> Self- Implements
CryptoSessiontrait
AntiReplayWindow (anti_replay.rs)
pub struct AntiReplayWindow { /* highest: u16, bitmap: Vec<u64>, initialized: bool */ }
Key methods:
AntiReplayWindow::new() -> Self-- 1024-packet windowAntiReplayWindow::check_and_update(&mut self, seq: u16) -> Result<(), CryptoError>
Nonce Construction (nonce.rs)
pub enum Direction { Send = 0, Recv = 1 }
pub fn build_nonce(session_id: &[u8; 4], seq: u32, direction: Direction) -> [u8; 12]
RekeyManager (rekey.rs)
pub struct RekeyManager { /* current_key, last_rekey_at */ }
Key methods:
RekeyManager::new(initial_key: [u8; 32]) -> SelfRekeyManager::should_rekey(&self, packet_count: u64) -> bool-- every 2^16 packetsRekeyManager::perform_rekey(&mut self, new_peer_pub: &[u8; 32], our_new_secret: StaticSecret, packet_count: u64) -> [u8; 32]
wzp-transport
Path: crates/wzp-transport/src/
Re-exports (lib.rs)
pub use config::{client_config, server_config};
pub use connection::{accept, connect, create_endpoint};
pub use path_monitor::PathMonitor;
pub use quic::QuinnTransport;
pub use wzp_proto::{MediaTransport, PathQuality, TransportError};
QuinnTransport (quic.rs)
pub struct QuinnTransport { /* connection: quinn::Connection, path_monitor: Mutex<PathMonitor> */ }
Key methods:
QuinnTransport::new(connection: quinn::Connection) -> SelfQuinnTransport::connection(&self) -> &quinn::ConnectionQuinnTransport::max_datagram_size(&self) -> Option<usize>- Implements
MediaTransporttrait
Configuration (config.rs)
/// Create a server configuration with a self-signed certificate.
pub fn server_config() -> (quinn::ServerConfig, Vec<u8>)
/// Create a client configuration that trusts any certificate (testing).
pub fn client_config() -> quinn::ClientConfig
QUIC parameters: ALPN wzp, 30s idle timeout, 5s keepalive, 256KB receive window, 128KB send window, 300ms initial RTT.
Connection Lifecycle (connection.rs)
pub fn create_endpoint(bind_addr: SocketAddr, server_config: Option<quinn::ServerConfig>) -> Result<quinn::Endpoint, TransportError>
pub async fn connect(endpoint: &quinn::Endpoint, addr: SocketAddr, server_name: &str, config: quinn::ClientConfig) -> Result<quinn::Connection, TransportError>
pub async fn accept(endpoint: &quinn::Endpoint) -> Result<quinn::Connection, TransportError>
PathMonitor (path_monitor.rs)
pub struct PathMonitor { /* EWMA state for loss, RTT, jitter, bandwidth */ }
Key methods:
PathMonitor::new() -> SelfPathMonitor::observe_sent(&mut self, seq: u16, timestamp_ms: u64)PathMonitor::observe_received(&mut self, seq: u16, timestamp_ms: u64)PathMonitor::observe_rtt(&mut self, rtt_ms: u32)PathMonitor::quality(&self) -> PathQuality
Datagram Helpers (datagram.rs)
pub fn serialize_media(packet: &MediaPacket) -> Bytes
pub fn deserialize_media(data: Bytes) -> Option<MediaPacket>
pub fn max_datagram_payload(connection: &quinn::Connection) -> Option<usize>
Reliable Stream Framing (reliable.rs)
pub async fn send_signal(connection: &Connection, msg: &SignalMessage) -> Result<(), TransportError>
pub async fn recv_signal(recv: &mut quinn::RecvStream) -> Result<SignalMessage, TransportError>
Framing: 4-byte big-endian length prefix + serde_json payload. Max message size: 1 MB.
wzp-relay
Path: crates/wzp-relay/src/
Re-exports (lib.rs)
pub use config::RelayConfig;
pub use handshake::accept_handshake;
pub use pipeline::{PipelineConfig, PipelineStats, RelayPipeline};
pub use session_mgr::{RelaySession, SessionId, SessionManager};
RoomManager (room.rs)
pub type ParticipantId = u64;
pub struct RoomManager { /* rooms: HashMap<String, Room> */ }
Key methods:
RoomManager::new() -> SelfRoomManager::join(&mut self, room_name: &str, addr: SocketAddr, transport: Arc<QuinnTransport>) -> ParticipantIdRoomManager::leave(&mut self, room_name: &str, participant_id: ParticipantId)RoomManager::others(&self, room_name: &str, participant_id: ParticipantId) -> Vec<Arc<QuinnTransport>>RoomManager::room_size(&self, room_name: &str) -> usizeRoomManager::list(&self) -> Vec<(String, usize)>
/// Run the receive loop for one participant in a room (forwards to all others).
pub async fn run_participant(room_mgr: Arc<Mutex<RoomManager>>, room_name: String, participant_id: ParticipantId, transport: Arc<QuinnTransport>)
RelayPipeline (pipeline.rs)
pub struct PipelineConfig { pub initial_profile: QualityProfile, pub jitter_target: usize, pub jitter_max: usize, pub jitter_min: usize }
pub struct PipelineStats { pub packets_received: u64, pub packets_forwarded: u64, pub packets_fec_recovered: u64, pub packets_lost: u64, pub profile_changes: u64 }
pub struct RelayPipeline { /* fec_encoder, fec_decoder, jitter, quality, profile, out_seq, stats */ }
Key methods:
RelayPipeline::new(config: PipelineConfig) -> SelfRelayPipeline::ingest(&mut self, packet: MediaPacket) -> Vec<MediaPacket>-- FEC decode + jitter popRelayPipeline::prepare_outbound(&mut self, packet: MediaPacket) -> Vec<MediaPacket>-- assign seq + FEC encodeRelayPipeline::stats(&self) -> &PipelineStatsRelayPipeline::profile(&self) -> QualityProfile
SessionManager (session_mgr.rs)
pub type SessionId = [u8; 16];
pub struct RelaySession { pub state: Session, pub upstream_pipeline: RelayPipeline, pub downstream_pipeline: RelayPipeline, pub profile: QualityProfile, pub last_activity_ms: u64 }
pub struct SessionManager { /* sessions: HashMap<SessionId, RelaySession>, max_sessions */ }
Key methods:
SessionManager::new(max_sessions: usize) -> SelfSessionManager::create_session(&mut self, session_id: SessionId, config: PipelineConfig) -> Option<&mut RelaySession>SessionManager::get_session(&mut self, id: &SessionId) -> Option<&mut RelaySession>SessionManager::remove_session(&mut self, id: &SessionId) -> Option<RelaySession>SessionManager::expire_idle(&mut self, now_ms: u64, timeout_ms: u64) -> usize
Handshake (handshake.rs)
/// Accept the relay (callee) side of the cryptographic handshake.
pub async fn accept_handshake(transport: &dyn MediaTransport, seed: &[u8; 32]) -> Result<(Box<dyn CryptoSession>, QualityProfile), anyhow::Error>
RelayConfig (config.rs)
pub struct RelayConfig {
pub listen_addr: SocketAddr, // default: 0.0.0.0:4433
pub remote_relay: Option<SocketAddr>, // None = room mode
pub max_sessions: usize, // default: 100
pub jitter_target_depth: usize, // default: 50
pub jitter_max_depth: usize, // default: 250
pub log_level: String, // default: "info"
}
wzp-client
Path: crates/wzp-client/src/
Re-exports (lib.rs)
#[cfg(feature = "audio")]
pub use audio_io::{AudioCapture, AudioPlayback};
pub use call::{CallConfig, CallDecoder, CallEncoder};
pub use handshake::perform_handshake;
CallEncoder (call.rs)
pub struct CallEncoder { /* audio_enc, fec_enc, profile, seq, block_id, frame_in_block, timestamp_ms */ }
Key methods:
CallEncoder::new(config: &CallConfig) -> SelfCallEncoder::encode_frame(&mut self, pcm: &[i16]) -> Result<Vec<MediaPacket>, anyhow::Error>-- returns source + repair packetsCallEncoder::set_profile(&mut self, profile: QualityProfile) -> Result<(), anyhow::Error>
CallDecoder (call.rs)
pub struct CallDecoder { /* audio_dec, fec_dec, jitter, quality, profile */ }
Key methods:
CallDecoder::new(config: &CallConfig) -> SelfCallDecoder::ingest(&mut self, packet: MediaPacket)-- feeds FEC decoder and jitter bufferCallDecoder::decode_next(&mut self, pcm: &mut [i16]) -> Option<usize>-- pops from jitter, decodesCallDecoder::profile(&self) -> QualityProfileCallDecoder::jitter_stats(&self) -> JitterStats
CallConfig (call.rs)
pub struct CallConfig {
pub profile: QualityProfile, // default: GOOD
pub jitter_target: usize, // default: 10
pub jitter_max: usize, // default: 250
pub jitter_min: usize, // default: 3
}
Client Handshake (handshake.rs)
/// Perform the client (caller) side of the cryptographic handshake.
pub async fn perform_handshake(transport: &dyn MediaTransport, seed: &[u8; 32]) -> Result<Box<dyn CryptoSession>, anyhow::Error>
Echo Test (echo_test.rs)
pub struct WindowResult { pub index: usize, pub time_offset_secs: f64, pub frames_sent: u32, pub frames_received: u32, pub loss_pct: f32, pub snr_db: f32, pub correlation: f32, pub peak_amplitude: i16, pub is_silent: bool }
pub struct EchoTestResult { pub duration_secs: f64, pub total_frames_sent: u64, pub total_frames_received: u64, pub overall_loss_pct: f32, pub windows: Vec<WindowResult>, /* ... */ }
pub async fn run_echo_test(transport: &(dyn MediaTransport + Send + Sync), duration_secs: u32, window_secs: f64) -> anyhow::Result<EchoTestResult>
pub fn print_report(result: &EchoTestResult)
Audio I/O (audio_io.rs, requires audio feature)
pub struct AudioCapture { /* rx: mpsc::Receiver<Vec<i16>>, running: Arc<AtomicBool> */ }
pub struct AudioPlayback { /* tx: mpsc::SyncSender<Vec<i16>>, running: Arc<AtomicBool> */ }
Key methods:
AudioCapture::start() -> Result<Self, anyhow::Error>-- opens default input at 48 kHz monoAudioCapture::read_frame(&self) -> Option<Vec<i16>>-- blocking, returns 960 samplesAudioCapture::stop(&self)AudioPlayback::start() -> Result<Self, anyhow::Error>-- opens default output at 48 kHz monoAudioPlayback::write_frame(&self, pcm: &[i16])AudioPlayback::stop(&self)
Benchmarks (bench.rs)
pub struct CodecResult { pub frames: usize, pub avg_encode_us: f64, pub avg_decode_us: f64, pub frames_per_sec: f64, pub compression_ratio: f64, /* ... */ }
pub struct FecResult { pub blocks_attempted: usize, pub blocks_recovered: usize, pub recovery_rate_pct: f64, /* ... */ }
pub struct CryptoResult { pub packets: usize, pub packets_per_sec: f64, pub megabytes_per_sec: f64, pub avg_latency_us: f64, /* ... */ }
pub struct PipelineResult { pub frames: usize, pub avg_e2e_latency_us: f64, pub overhead_ratio: f64, /* ... */ }
pub fn generate_sine_wave(freq_hz: f32, sample_rate: u32, num_samples: usize) -> Vec<i16>
pub fn bench_codec_roundtrip() -> CodecResult // 1000 frames Opus 24kbps
pub fn bench_fec_recovery(loss_pct: f32) -> FecResult // 100 blocks with simulated loss
pub fn bench_encrypt_decrypt() -> CryptoResult // 30000 packets ChaCha20
pub fn bench_full_pipeline() -> PipelineResult // 50 frames E2E
wzp-web
Path: crates/wzp-web/src/
The web bridge binary. No public library API -- it is a standalone Axum server.
Binary: wzp-web
- Serves static files from
crates/wzp-web/static/ - WebSocket endpoint:
GET /ws/{room}-- upgrades to WebSocket - Each WebSocket client gets a QUIC connection to the relay with the room name as SNI
- Browser -> relay: WebSocket binary messages (960 Int16 samples as raw bytes) ->
CallEncoder->MediaTransport::send_media() - Relay -> browser:
MediaTransport::recv_media()->CallDecoder-> WebSocket binary messages
Static Files
static/index.html-- web UI with room input, connect/disconnect, PTT, level meterstatic/audio-processor.js-- AudioWorklet for microphone capture (960-sample frames)static/playback-processor.js-- AudioWorklet for audio playback (ring buffer, 200ms max)