use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use subetha_cxc::control_table::ControlTable;
use subetha_cxc::udp_bridge::{ReliableUdpReceiver, ReliableUdpSender};
fn arg(args: &[String], key: &str) -> Option<String> {
args.iter().position(|a| a == key).and_then(|i| args.get(i + 1)).cloned()
}
fn argp<T: std::str::FromStr>(args: &[String], key: &str, default: T) -> T {
arg(args, key).and_then(|v| v.parse().ok()).unwrap_or(default)
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let role = arg(&args, "--role").unwrap_or_default();
let bind = arg(&args, "--bind").unwrap_or_else(|| "0.0.0.0:0".into());
let count: u64 = argp(&args, "--count", 1000);
let item_bytes: usize = argp(&args, "--item-bytes", 8).max(8);
match role.as_str() {
"sender" => run_sender(&args, &bind, count, item_bytes),
"receiver" => run_receiver(&args, &bind, count),
"probe" => run_probe(),
"salvage" => run_salvage(&args),
other => {
eprintln!("usage: --role <sender|receiver|probe|salvage> ... (got {other:?})");
std::process::exit(2);
}
}
}
fn run_salvage(args: &[String]) {
use subetha_cxc::salvage::PacketSalvage;
let n: u64 = argp(args, "--count", 2000);
let (b, r, bl) = (8usize, 2usize, 16usize);
let s = PacketSalvage::new(b, r, bl).expect("salvage code");
let mut rng = 0xDEAD_BEEF_1234_5678u64;
let mut next = || {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
rng >> 33
};
let (mut salvaged, mut beyond) = (0u64, 0u64);
let stride = bl + 4;
for i in 0..n {
let payload: Vec<u8> = (0..b * bl).map(|j| ((i as usize * 31 + j * 7) & 0xFF) as u8).collect();
let mut packet = s.encode(&payload).expect("encode");
let n_corrupt = if next() % 8 == 0 { r + 1 } else { 1 + (next() % r as u64) as usize };
for _ in 0..n_corrupt {
let blk = (next() % (b + r) as u64) as usize;
let pos = blk * stride + (next() % bl as u64) as usize;
packet[pos] ^= 0xFF;
}
match s.decode(&packet) {
Some(p) if p == payload => salvaged += 1,
_ => beyond += 1,
}
}
println!(
"[salvage] recovered {salvaged}/{n} corrupt packets the kernel would have DROPPED \
({}%); {beyond} were corrupted beyond the {r}-block budget (fall back to inter-packet FEC/ARQ)",
salvaged * 100 / n
);
}
fn run_probe() {
use subetha_cxc::link_sensor::platform_sensor;
let mut s = platform_sensor(None);
for i in 0..3 {
let snap = s.sample();
println!(
"[probe {i}] backend={} signal_quality={:?} drop_rate={:?} link_stress={:.3}",
s.backend(),
snap.signal_quality,
snap.drop_rate,
snap.link_stress()
);
std::thread::sleep(Duration::from_millis(250));
}
}
fn run_sender(args: &[String], bind: &str, count: u64, item_bytes: usize) {
let peer: SocketAddr = arg(args, "--peer")
.expect("--peer IP:port required for sender")
.parse()
.expect("--peer must be IP:port");
let k: usize = argp(args, "--k", 8);
let r: usize = argp(args, "--r", 2);
let depth: u8 = argp(args, "--interleave", 1);
let control = Arc::new(ControlTable::new());
control.set_interleave_depth(depth);
let mut s = ReliableUdpSender::bind_with_control(bind, peer, k, r, item_bytes, control)
.expect("bind sender");
let tower_r: usize = argp(args, "--tower-r", 0);
if tower_r > 0 {
s.enable_tower(8, tower_r);
}
println!(
"[sender] {} -> {peer} k={k} r={r} interleave={depth} count={count} item_bytes={item_bytes}",
s.local_addr().expect("local addr")
);
let t0 = Instant::now();
let mut last_print = Instant::now();
let mut buf = vec![0u8; item_bytes];
for i in 0..count {
buf[..8].copy_from_slice(&i.to_le_bytes());
while s.flow_blocked() {
s.pump_feedback().ok();
if s.flow_blocked() {
std::thread::sleep(Duration::from_micros(50));
}
}
s.send_item(&buf).expect("send_item");
if last_print.elapsed() > Duration::from_millis(400) {
let c = s.control();
eprintln!(
"[sender] adapt: level={:?} parity_r={} interleave={} link={}({:.2})",
c.level(),
c.parity_r(),
c.interleave_depth(),
s.link_backend(),
s.link_stress()
);
last_print = Instant::now();
}
}
s.flush().expect("flush");
let acked = s
.drain_until_acked(Duration::from_secs(60))
.expect("drain_until_acked");
let secs = t0.elapsed().as_secs_f64();
let goodput = (count as f64 * item_bytes as f64 * 8.0) / secs.max(1e-9) / 1e6;
println!(
"[sender] DONE sent={count} fully_acked={acked} pending={} secs={secs:.2} \
rate={:.0} items/s goodput={goodput:.1} Mbit/s",
s.pending_len(),
count as f64 / secs.max(1e-9)
);
}
fn run_receiver(args: &[String], bind: &str, count: u64) {
let loss: u32 = argp(args, "--loss", 0);
let seed: u64 = argp(args, "--seed", 1);
let mut recv = ReliableUdpReceiver::bind(bind).expect("bind receiver");
if loss > 0 {
recv = recv.with_debug_loss(loss, seed);
}
let drop_mod: u32 = argp(args, "--drop-block-mod", 0);
if drop_mod > 0 {
recv = recv.with_block_drop_mod(drop_mod);
}
let max_hold_ms: u64 = argp(args, "--max-hold-ms", 0);
if max_hold_ms > 0 {
recv = recv.with_max_hold(Duration::from_millis(max_hold_ms));
}
let rtt_ms: u64 = argp(args, "--rtt-ms", 0);
if rtt_ms > 0 {
recv = recv.with_feedback_delay(Duration::from_millis(rtt_ms));
}
if args.iter().any(|a| a == "--serial-nak") {
recv = recv.with_nak_batch(1);
}
let burst_at: u64 = argp(args, "--burst-at", 0);
let burst_len: u64 = argp(args, "--burst-len", 0);
if burst_len > 0 {
recv = recv.with_burst_loss(burst_at, burst_len);
}
println!(
"[receiver] {} count={count} injected_loss={loss}% seed={seed} drop_block_mod={drop_mod}",
recv.local_addr().expect("local addr")
);
let t0 = Instant::now();
let mut got: Vec<u64> = Vec::with_capacity(count as usize);
let mut bytes: u128 = 0;
let deadline = Duration::from_secs(180);
let mut last_print = Instant::now();
let mut last_bytes: u128 = 0;
while (got.len() as u64) < count {
if t0.elapsed() > deadline {
eprintln!("[receiver] TIMEOUT got {} / {count}", got.len());
break;
}
for item in recv.poll().expect("poll") {
bytes += item.len() as u128;
let idx = u64::from_le_bytes(item[..8].try_into().expect("8-byte index"));
got.push(idx);
}
if last_print.elapsed() > Duration::from_millis(500) {
let dt = last_print.elapsed().as_secs_f64().max(1e-9);
let interval_mbit = ((bytes - last_bytes) as f64 * 8.0) / dt / 1e6;
eprintln!(
"[receiver] t={:.1}s got={} interval={:.1} Mbit/s head={:?}",
t0.elapsed().as_secs_f64(),
got.len(),
interval_mbit,
recv.head_status()
);
last_bytes = bytes;
last_print = Instant::now();
}
}
for _ in 0..100 {
recv.nudge_feedback().ok();
std::thread::sleep(Duration::from_millis(2));
}
let secs = t0.elapsed().as_secs_f64();
let ordered = got.iter().copied().eq(0..count);
let sum: u128 = got.iter().map(|&x| x as u128).sum();
let expected: u128 = (0..count as u128).sum();
let pass = (got.len() as u64 == count) && ordered && sum == expected;
let goodput = (bytes as f64 * 8.0) / secs.max(1e-9) / 1e6;
println!(
"[receiver] {} got={} expected={count} ordered={ordered} sum_ok={} secs={secs:.2} \
rate={:.0} items/s goodput={goodput:.1} Mbit/s",
if pass { "PASS" } else { "FAIL" },
got.len(),
sum == expected,
count as f64 / secs.max(1e-9)
);
std::process::exit(if pass { 0 } else { 1 });
}