forked from unom/punktfunk
First step of 1 Gbps+ readiness (the whole point of the GF(2^16) Leopard FEC): make 1 Gbps configurable and its dominant failure mode observable, before the real transport work (sendmmsg + paced encode|send split) lands. Investigation (6-way) verdict: we're ~halfway, and it's mostly clamps plus one real piece of work. The integer/type path, FEC (a 1 Gbps frame is only a few hundred shards in one GF(2^16) block, far under the 65535 ceiling), AES-GCM (AES-NI, ~10-25x headroom), and the M1 reassembler bounds (fully derived from the negotiated FecConfig) are ALL already 1 Gbps-ready and untouched. This commit (the configurable + observable foundation): - m3.rs: MAX_BITRATE_KBPS 500_000 -> 2_000_000 (2 Gbps headroom over the 1 Gbps+ target); MAX_PROBE_KBPS 1_000_000 -> 3_000_000 (probe can demonstrate headroom ABOVE the session cap so a client can confidently pick a 1 Gbps+ bitrate). - transport/udp.rs: TARGET_SOCKBUF 8 MB -> 32 MB (a multi-MB IDR keyframe burst no longer fills the buffer); scripts/99-punktfunk-net.conf bumped to match. - Observability: Transport::send now returns Ok(true|false) (false = WouldBlock send-buffer drop, previously a silent Ok(())). Session counts these as a new `packets_send_dropped` stat (distinct from recv-side packets_dropped) — in Stats, the C ABI PunktfunkStats (header regenerated), a PUNKTFUNK_PERF periodic wire-Mbps + drop dump in virtual_stream, and the speed-test probe completion log. This is the dominant 1 Gbps+ loss mode and was invisible. Loopback-verified: a probe now runs at 1.2 Gbps target (no longer truncated to 1 Gbps) with the drop counter live. NOT yet a sustained-1-Gbps proof — the single-send()-per-packet native path is the next, real piece of work (port the proven GameStream sendmmsg + paced send thread into the core Transport). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
2.6 KiB
Rust
78 lines
2.6 KiB
Rust
//! In-process transport for unit tests and the C ABI harness. Two cross-wired
|
|
//! [`LoopbackTransport`]s form a host↔client link, with optional deterministic loss so
|
|
//! tests can exercise FEC recovery without a real network.
|
|
|
|
use super::Transport;
|
|
use std::collections::VecDeque;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
/// One direction of the link.
|
|
struct Channel {
|
|
queue: Mutex<VecDeque<Vec<u8>>>,
|
|
/// Drop one of every `drop_period` packets (0 = lossless).
|
|
drop_period: u32,
|
|
sent: AtomicU64,
|
|
dropped: AtomicU64,
|
|
}
|
|
|
|
impl Channel {
|
|
fn new(drop_period: u32) -> Arc<Channel> {
|
|
Arc::new(Channel {
|
|
queue: Mutex::new(VecDeque::new()),
|
|
drop_period,
|
|
sent: AtomicU64::new(0),
|
|
dropped: AtomicU64::new(0),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Sends on `tx`, receives on `rx`. Created in cross-wired pairs by [`loopback_pair`].
|
|
pub struct LoopbackTransport {
|
|
tx: Arc<Channel>,
|
|
rx: Arc<Channel>,
|
|
}
|
|
|
|
impl LoopbackTransport {
|
|
/// Number of packets this transport's send side has deliberately dropped.
|
|
pub fn dropped(&self) -> u64 {
|
|
self.tx.dropped.load(Ordering::Relaxed)
|
|
}
|
|
}
|
|
|
|
/// Create a connected `(host, client)` pair. `host_drop_period` injects loss on the
|
|
/// host→client (video) path; `client_drop_period` on the reverse (input) path.
|
|
pub fn loopback_pair(
|
|
host_drop_period: u32,
|
|
client_drop_period: u32,
|
|
) -> (LoopbackTransport, LoopbackTransport) {
|
|
let h2c = Channel::new(host_drop_period);
|
|
let c2h = Channel::new(client_drop_period);
|
|
let host = LoopbackTransport {
|
|
tx: h2c.clone(),
|
|
rx: c2h.clone(),
|
|
};
|
|
let client = LoopbackTransport { tx: c2h, rx: h2c };
|
|
(host, client)
|
|
}
|
|
|
|
impl Transport for LoopbackTransport {
|
|
fn send(&self, packet: &[u8]) -> std::io::Result<bool> {
|
|
let n = self.tx.sent.fetch_add(1, Ordering::Relaxed);
|
|
if self.tx.drop_period != 0 && (n % self.tx.drop_period as u64) == 0 {
|
|
// Deterministically drop in flight (the 1st of each `drop_period` group). This models
|
|
// NETWORK loss (the packet left the sender, then vanished), not a local send-buffer
|
|
// drop — so it still reports `Ok(true)`: the host sent it; the recv/FEC side handles
|
|
// the loss. (`Ok(false)` is reserved for a real WouldBlock send-buffer overflow.)
|
|
self.tx.dropped.fetch_add(1, Ordering::Relaxed);
|
|
return Ok(true);
|
|
}
|
|
self.tx.queue.lock().unwrap().push_back(packet.to_vec());
|
|
Ok(true)
|
|
}
|
|
|
|
fn recv(&self) -> std::io::Result<Option<Vec<u8>>> {
|
|
Ok(self.rx.queue.lock().unwrap().pop_front())
|
|
}
|
|
}
|