feat: Phase 3 — crypto handshake, codec2, benchmarks, audio I/O, relay forwarding

E2E crypto handshake:
- Client/relay handshake via SignalMessage (CallOffer/CallAnswer)
- X25519 ephemeral key exchange with Ed25519 identity signatures
- Integration tests proving bidirectional encrypt/decrypt

Codec2 integration:
- Pure Rust codec2 crate (v0.3) — no C bindings needed
- MODE_3200 (160 samples/20ms, 8 bytes) and MODE_1200 (320 samples/40ms, 6 bytes)
- 11 new tests including encode/decode roundtrip and adaptive switching

Relay forwarding:
- Bidirectional client → remote forwarding with pipeline processing
- CLI args: --listen, --remote
- Periodic stats logging, clean shutdown via tokio::select!

Benchmark tool (wzp-bench):
- Codec roundtrip, FEC recovery, crypto throughput, full pipeline benchmarks
- Sine wave PCM generator for realistic testing

Audio I/O (cpal):
- AudioCapture (microphone) and AudioPlayback (speakers) at 48kHz mono
- CLI --live mode: mic → encode → send / recv → decode → speakers

120 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-27 13:43:22 +04:00
parent 43d7f70fe9
commit 79f9ff1596
18 changed files with 2451 additions and 75 deletions

View File

@@ -0,0 +1,152 @@
//! WarzonePhone benchmark CLI.
//!
//! Usage: wzp-bench [--codec] [--fec] [--crypto] [--pipeline] [--all]
//! wzp-bench --fec --loss 30 (test FEC with 30% loss)
use wzp_client::bench;
fn print_header(title: &str) {
println!();
println!("┌─────────────────────────────────────────────────────┐");
println!("{:<51}", title);
println!("├─────────────────────────────────────────────────────┤");
}
fn print_row(label: &str, value: &str) {
println!("{:<28} {:>20}", label, value);
}
fn print_footer() {
println!("└─────────────────────────────────────────────────────┘");
}
fn run_codec() {
print_header("Codec Roundtrip (Opus 24kbps)");
let r = bench::bench_codec_roundtrip();
print_row("Frames", &format!("{}", r.frames));
print_row("Encode total", &format!("{:.2} ms", r.total_encode.as_secs_f64() * 1000.0));
print_row("Decode total", &format!("{:.2} ms", r.total_decode.as_secs_f64() * 1000.0));
print_row("Avg encode", &format!("{:.1} us", r.avg_encode_us));
print_row("Avg decode", &format!("{:.1} us", r.avg_decode_us));
print_row("Throughput", &format!("{:.0} frames/sec", r.frames_per_sec));
print_row("Compression ratio", &format!("{:.1}x", r.compression_ratio));
print_footer();
}
fn run_fec(loss_pct: f32) {
print_header(&format!("FEC Recovery (loss={:.0}%)", loss_pct));
let r = bench::bench_fec_recovery(loss_pct);
print_row("Blocks attempted", &format!("{}", r.blocks_attempted));
print_row("Blocks recovered", &format!("{}", r.blocks_recovered));
print_row("Recovery rate", &format!("{:.1}%", r.recovery_rate_pct));
print_row("Source bytes", &format!("{}", r.total_source_bytes));
print_row("Repair (overhead) bytes", &format!("{}", r.overhead_bytes));
print_row("Total time", &format!("{:.2} ms", r.total_time.as_secs_f64() * 1000.0));
print_footer();
}
fn run_crypto() {
print_header("Crypto (ChaCha20-Poly1305)");
let r = bench::bench_encrypt_decrypt();
print_row("Packets", &format!("{}", r.packets));
print_row("Total time", &format!("{:.2} ms", r.total_time.as_secs_f64() * 1000.0));
print_row("Throughput", &format!("{:.0} pkt/sec", r.packets_per_sec));
print_row("Bandwidth", &format!("{:.2} MB/sec", r.megabytes_per_sec));
print_row("Avg latency", &format!("{:.2} us", r.avg_latency_us));
print_footer();
}
fn run_pipeline() {
print_header("Full Pipeline (E2E)");
let r = bench::bench_full_pipeline();
print_row("Frames", &format!("{}", r.frames));
print_row("Encode pipeline", &format!("{:.2} ms", r.total_encode_pipeline.as_secs_f64() * 1000.0));
print_row("Decode pipeline", &format!("{:.2} ms", r.total_decode_pipeline.as_secs_f64() * 1000.0));
print_row("Avg E2E latency", &format!("{:.1} us/frame", r.avg_e2e_latency_us));
print_row("PCM in", &format!("{} bytes", r.pcm_bytes_in));
print_row("Wire out", &format!("{} bytes", r.wire_bytes_out));
print_row("Overhead ratio", &format!("{:.3}x", r.overhead_ratio));
print_footer();
}
fn print_usage() {
println!("Usage: wzp-bench [OPTIONS]");
println!();
println!("Options:");
println!(" --codec Run codec roundtrip benchmark");
println!(" --fec Run FEC recovery benchmark");
println!(" --crypto Run encryption benchmark");
println!(" --pipeline Run full pipeline benchmark");
println!(" --all Run all benchmarks (default)");
println!(" --loss <N> FEC loss percentage (default: 20)");
println!(" --help Show this help");
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
if args.iter().any(|a| a == "--help" || a == "-h") {
print_usage();
return;
}
let mut run_codec_flag = false;
let mut run_fec_flag = false;
let mut run_crypto_flag = false;
let mut run_pipeline_flag = false;
let mut loss_pct: f32 = 20.0;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--codec" => run_codec_flag = true,
"--fec" => run_fec_flag = true,
"--crypto" => run_crypto_flag = true,
"--pipeline" => run_pipeline_flag = true,
"--all" => {
run_codec_flag = true;
run_fec_flag = true;
run_crypto_flag = true;
run_pipeline_flag = true;
}
"--loss" => {
i += 1;
if i < args.len() {
loss_pct = args[i].parse().unwrap_or(20.0);
}
}
other => {
eprintln!("Unknown option: {}", other);
print_usage();
std::process::exit(1);
}
}
i += 1;
}
// Default: run all if no specific flag given
if !run_codec_flag && !run_fec_flag && !run_crypto_flag && !run_pipeline_flag {
run_codec_flag = true;
run_fec_flag = true;
run_crypto_flag = true;
run_pipeline_flag = true;
}
println!("=== WarzonePhone Protocol Benchmark ===");
if run_codec_flag {
run_codec();
}
if run_fec_flag {
run_fec(loss_pct);
}
if run_crypto_flag {
run_crypto();
}
if run_pipeline_flag {
run_pipeline();
}
println!();
println!("Done.");
}