Files
wz-phone/docs/API.md
Siavash Sameni 5425c59e7d docs: comprehensive project documentation
- 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>
2026-03-28 05:30:11 +04:00

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 bytes
  • MediaHeader::read_from(buf: &mut impl Buf) -> Option<Self> -- deserialize
  • MediaHeader::encode_fec_ratio(ratio: f32) -> u8 -- float to 7-bit wire encoding
  • MediaHeader::decode_fec_ratio(encoded: u8) -> f32 -- 7-bit wire to float
  • MediaPacket::to_bytes(&self) -> Bytes -- serialize complete packet
  • MediaPacket::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) -> u32
  • CodecId::frame_duration_ms(self) -> u8
  • CodecId::sample_rate_hz(self) -> u32
  • CodecId::from_wire(val: u8) -> Option<Self>
  • CodecId::to_wire(self) -> u8
  • QualityProfile::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::Good
  • AdaptiveQualityController::tier(&self) -> Tier
  • Tier::classify(report: &QualityReport) -> Self
  • Tier::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) -> Self
  • JitterBuffer::default_5s() -> Self -- target=50, max=250, min=25
  • JitterBuffer::push(&mut self, packet: MediaPacket)
  • JitterBuffer::pop(&mut self) -> PlayoutResult
  • JitterBuffer::depth(&self) -> usize
  • JitterBuffer::stats(&self) -> &JitterStats
  • JitterBuffer::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]) -> Self
  • Session::state(&self) -> SessionState
  • Session::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 40ms
  • Codec2Encoder::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) -> Self
  • RaptorQFecEncoder::with_defaults(frames_per_block: usize) -> Self -- symbol_size=256
  • Implements FecEncoder trait

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) -> Self
  • RaptorQFecDecoder::with_defaults(frames_per_block: usize) -> Self
  • Implements FecDecoder trait

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) -> Self
  • Interleaver::with_default_depth() -> Self -- depth=3
  • Interleaver::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) -> Self
  • AdaptiveFec::build_encoder(&self) -> RaptorQFecEncoder
  • AdaptiveFec::ratio(&self) -> f32
  • AdaptiveFec::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) -> u8
  • EncoderBlockManager::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 CryptoSession trait

AntiReplayWindow (anti_replay.rs)

pub struct AntiReplayWindow { /* highest: u16, bitmap: Vec<u64>, initialized: bool */ }

Key methods:

  • AntiReplayWindow::new() -> Self -- 1024-packet window
  • AntiReplayWindow::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]) -> Self
  • RekeyManager::should_rekey(&self, packet_count: u64) -> bool -- every 2^16 packets
  • RekeyManager::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) -> Self
  • QuinnTransport::connection(&self) -> &quinn::Connection
  • QuinnTransport::max_datagram_size(&self) -> Option<usize>
  • Implements MediaTransport trait

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() -> Self
  • PathMonitor::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() -> Self
  • RoomManager::join(&mut self, room_name: &str, addr: SocketAddr, transport: Arc<QuinnTransport>) -> ParticipantId
  • RoomManager::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) -> usize
  • RoomManager::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) -> Self
  • RelayPipeline::ingest(&mut self, packet: MediaPacket) -> Vec<MediaPacket> -- FEC decode + jitter pop
  • RelayPipeline::prepare_outbound(&mut self, packet: MediaPacket) -> Vec<MediaPacket> -- assign seq + FEC encode
  • RelayPipeline::stats(&self) -> &PipelineStats
  • RelayPipeline::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) -> Self
  • SessionManager::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) -> Self
  • CallEncoder::encode_frame(&mut self, pcm: &[i16]) -> Result<Vec<MediaPacket>, anyhow::Error> -- returns source + repair packets
  • CallEncoder::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) -> Self
  • CallDecoder::ingest(&mut self, packet: MediaPacket) -- feeds FEC decoder and jitter buffer
  • CallDecoder::decode_next(&mut self, pcm: &mut [i16]) -> Option<usize> -- pops from jitter, decodes
  • CallDecoder::profile(&self) -> QualityProfile
  • CallDecoder::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 mono
  • AudioCapture::read_frame(&self) -> Option<Vec<i16>> -- blocking, returns 960 samples
  • AudioCapture::stop(&self)
  • AudioPlayback::start() -> Result<Self, anyhow::Error> -- opens default output at 48 kHz mono
  • AudioPlayback::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 meter
  • static/audio-processor.js -- AudioWorklet for microphone capture (960-sample frames)
  • static/playback-processor.js -- AudioWorklet for audio playback (ring buffer, 200ms max)