Files
wz-phone/crates/wzp-fec/src/interleave.rs
Siavash Sameni 51e893590c 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>
2026-03-27 12:45:07 +04:00

153 lines
4.9 KiB
Rust

//! Temporal interleaving — spreads symbols from multiple FEC blocks across
//! transmission slots so that burst losses damage multiple blocks lightly
//! rather than one block fatally.
/// A symbol ready for transmission: (block_id, symbol_index, is_repair, data).
pub type Symbol = (u8, u8, bool, Vec<u8>);
/// Temporal interleaver that mixes symbols across multiple FEC blocks.
pub struct Interleaver {
/// Number of blocks to interleave across (spread depth).
depth: usize,
}
impl Interleaver {
/// Create an interleaver with the given spread depth.
pub fn new(depth: usize) -> Self {
Self { depth }
}
/// Create with default depth of 3 blocks.
pub fn with_default_depth() -> Self {
Self::new(3)
}
/// Spread depth (number of blocks mixed together).
pub fn depth(&self) -> usize {
self.depth
}
/// Interleave symbols from multiple blocks into a single transmission sequence.
///
/// Each inner `Vec` contains the symbols for one FEC block.
/// The output interleaves them in round-robin fashion: symbol 0 from block A,
/// symbol 0 from block B, symbol 0 from block C, symbol 1 from block A, etc.
///
/// This ensures a burst loss of N consecutive packets only destroys at most
/// ceil(N/depth) symbols from any single block.
pub fn interleave(&self, blocks: &[Vec<Symbol>]) -> Vec<Symbol> {
if blocks.is_empty() {
return Vec::new();
}
let max_len = blocks.iter().map(|b| b.len()).max().unwrap_or(0);
let mut output = Vec::with_capacity(blocks.iter().map(|b| b.len()).sum());
for slot in 0..max_len {
for block in blocks {
if slot < block.len() {
output.push(block[slot].clone());
}
}
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interleave_mixes_blocks() {
let interleaver = Interleaver::with_default_depth();
let block_a: Vec<Symbol> = (0..3)
.map(|i| (0u8, i as u8, false, vec![0xA0 + i as u8]))
.collect();
let block_b: Vec<Symbol> = (0..3)
.map(|i| (1u8, i as u8, false, vec![0xB0 + i as u8]))
.collect();
let block_c: Vec<Symbol> = (0..3)
.map(|i| (2u8, i as u8, false, vec![0xC0 + i as u8]))
.collect();
let result = interleaver.interleave(&[block_a, block_b, block_c]);
assert_eq!(result.len(), 9);
// Round-robin: A0, B0, C0, A1, B1, C1, A2, B2, C2
assert_eq!(result[0].0, 0); // block A
assert_eq!(result[1].0, 1); // block B
assert_eq!(result[2].0, 2); // block C
assert_eq!(result[3].0, 0); // block A
assert_eq!(result[4].0, 1); // block B
assert_eq!(result[5].0, 2); // block C
// Verify symbol indices cycle correctly
assert_eq!(result[0].1, 0); // sym 0 from A
assert_eq!(result[3].1, 1); // sym 1 from A
assert_eq!(result[6].1, 2); // sym 2 from A
}
#[test]
fn interleave_unequal_lengths() {
let interleaver = Interleaver::new(2);
let block_a: Vec<Symbol> = (0..3)
.map(|i| (0u8, i as u8, false, vec![0xA0 + i as u8]))
.collect();
let block_b: Vec<Symbol> = (0..1)
.map(|i| (1u8, i as u8, false, vec![0xB0 + i as u8]))
.collect();
let result = interleaver.interleave(&[block_a, block_b]);
// A0, B0, A1, A2
assert_eq!(result.len(), 4);
assert_eq!(result[0].0, 0); // A0
assert_eq!(result[1].0, 1); // B0
assert_eq!(result[2].0, 0); // A1
assert_eq!(result[3].0, 0); // A2
}
#[test]
fn interleave_empty() {
let interleaver = Interleaver::with_default_depth();
let result = interleaver.interleave(&[]);
assert!(result.is_empty());
}
#[test]
fn burst_loss_distributed() {
// With 3-block interleaving and a burst of 6 consecutive losses,
// each block loses at most 2 symbols.
let interleaver = Interleaver::new(3);
let blocks: Vec<Vec<Symbol>> = (0..3)
.map(|b| {
(0..6)
.map(|i| (b as u8, i as u8, false, vec![b as u8; 10]))
.collect()
})
.collect();
let interleaved = interleaver.interleave(&blocks);
assert_eq!(interleaved.len(), 18);
// Simulate burst loss of 6 consecutive packets starting at index 3
let lost_range = 3..9;
let mut losses_per_block = [0u32; 3];
for idx in lost_range {
let block_id = interleaved[idx].0 as usize;
losses_per_block[block_id] += 1;
}
// Each block should lose exactly 2 (6 losses / 3 blocks)
for &loss in &losses_per_block {
assert_eq!(loss, 2, "Each block should lose at most 2 symbols from a burst of 6");
}
}
}