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:
152
crates/wzp-client/src/bench_cli.rs
Normal file
152
crates/wzp-client/src/bench_cli.rs
Normal 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.");
|
||||
}
|
||||
Reference in New Issue
Block a user