feat: WarzonePhone lossy VoIP protocol — Phase 1 complete

Rust workspace with 7 crates implementing a custom VoIP protocol
designed for extremely lossy connections (5-70% loss, 100-500kbps,
300-800ms RTT). 89 tests passing across all crates.

Crates:
- wzp-proto: Wire format, traits, adaptive quality controller, jitter buffer, session FSM
- wzp-codec: Opus encoder/decoder (audiopus), Codec2 stubs, adaptive switching, resampling
- wzp-fec: RaptorQ fountain codes, interleaving, block management (proven 30-70% loss recovery)
- wzp-crypto: X25519+ChaCha20-Poly1305, Warzone identity compatible, anti-replay, rekeying
- wzp-transport: QUIC via quinn with DATAGRAM frames, path monitoring, signaling streams
- wzp-relay: Integration stub (Phase 2)
- wzp-client: Integration stub (Phase 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 12:45:07 +04:00
commit 51e893590c
47 changed files with 7097 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
//! Codec2 decoder — stub implementation.
//!
//! Codec2 operates at 8 kHz mono. Resampling back to 48 kHz is handled
//! externally (see `resample.rs` and `AdaptiveCodec`).
//!
//! This is a stub that returns an error on decode. When `codec2-sys`
//! is linked, replace the body of `decode()` with actual FFI calls.
use wzp_proto::{AudioDecoder, CodecError, CodecId, QualityProfile};
/// Stub Codec2 decoder implementing `AudioDecoder`.
///
/// Currently returns `CodecError::DecodeFailed` for decode operations.
/// PLC fills output with silence (zeros).
pub struct Codec2Decoder {
codec_id: CodecId,
frame_duration_ms: u8,
}
impl Codec2Decoder {
/// Create a new stub Codec2 decoder.
pub fn new(profile: QualityProfile) -> Result<Self, CodecError> {
Ok(Self {
codec_id: profile.codec,
frame_duration_ms: profile.frame_duration_ms,
})
}
/// Expected number of 8 kHz PCM output samples per frame.
pub fn frame_samples(&self) -> usize {
(8_000 * self.frame_duration_ms as usize) / 1000
}
}
impl AudioDecoder for Codec2Decoder {
fn decode(&mut self, _encoded: &[u8], _pcm: &mut [i16]) -> Result<usize, CodecError> {
Err(CodecError::DecodeFailed(
"codec2-sys not yet linked".to_string(),
))
}
fn decode_lost(&mut self, pcm: &mut [i16]) -> Result<usize, CodecError> {
let samples = self.frame_samples();
let n = samples.min(pcm.len());
// Fill with silence as basic PLC
pcm[..n].fill(0);
Ok(n)
}
fn codec_id(&self) -> CodecId {
self.codec_id
}
fn set_profile(&mut self, profile: QualityProfile) -> Result<(), CodecError> {
match profile.codec {
CodecId::Codec2_3200 | CodecId::Codec2_1200 => {
self.codec_id = profile.codec;
self.frame_duration_ms = profile.frame_duration_ms;
Ok(())
}
other => Err(CodecError::UnsupportedTransition {
from: self.codec_id,
to: other,
}),
}
}
}