Skip to main content

srt_runtime/arq/
mod.rs

1//! SRT ARQ (Automatic Repeat reQuest) reliability engine —
2//! `draft-sharabayko-srt-01` §4.8 (Acknowledgement and Lost Packet
3//! Handling), §4.8.1 (Packet Acknowledgement — ACKs, ACKACKs), §4.8.2
4//! (Packet Retransmission — NAKs), §4.10 (Round-Trip Time Estimation).
5//! Curated behavioural rules: `specs/rules/srt-arq.md`. The wire field
6//! layouts this module drives (ACK/NAK/ACKACK) are the existing
7//! [`crate::packet`] codecs — this module never re-encodes them, it only
8//! decides *when* to build one and *what* to do with one received.
9//!
10//! Sans-IO, like the rest of this crate: [`Sender`] and [`Receiver`] never
11//! read a wall clock. All timing is driven by a caller-supplied
12//! `now: core::time::Duration` (elapsed time since a fixed epoch the caller
13//! owns) passed to `tick`/`feed_data`/`on_data`/`on_ack`/`on_ackack`.
14//!
15//! # Module map
16//! - [`seq`] — wrap-safe 31-bit sequence-number arithmetic (not itself a
17//!   `srt-arq.md` rule — the draft does not specify a comparison algorithm;
18//!   see the module doc there for the resolution).
19//! - [`rtt::RttEstimator`] — the rule 29-31 RTT/RTTVar EWMA, shared by both
20//!   roles (rule 34: a socket's RTT state is really one estimator usable by
21//!   both a sender and a receiver path).
22//! - [`Sender`] — send buffer, NAK-driven retransmit queue, ACK/ACKACK
23//!   handling (rules 1, 3, 5, 7-10, 15-18, 23-24, 33).
24//! - [`Receiver`] — loss detection, Full/Light ACK generation, periodic NAK,
25//!   ACKACK-driven RTT measurement (rules 4, 8, 11-14, 21-22, 26-30, 32).
26//!
27//! # Non-goals (explicit follow-ups, not curated as ARQ rules here)
28//! - TLPKTDROP fake-ACK skip handling (rule 13) — `srt-tsbpd.md` scope.
29//! - RTO-based periodic retransmission without a NAK / congestion control
30//!   (§5, FileCC) — srt-arq.md is explicit that the RTO formula is
31//!   congestion-control scope, not curated there.
32//! - Send-queue overflow / unsent-packet drop sizing (rules 19-20) — the
33//!   latency-window math is §4.4/§4.5 scope.
34
35mod receiver;
36pub mod rtt;
37mod sender;
38pub mod seq;
39
40pub use receiver::{FeedOutcome, Receiver};
41pub use rtt::RttEstimator;
42pub use sender::Sender;
43
44use core::time::Duration;
45
46/// Full ACK timer period — 10 milliseconds (`specs/rules/srt-arq.md` rule
47/// 11, quoting `draft-sharabayko-srt-01` L2874-2876: "the ACK period or
48/// synchronization time interval SYN").
49pub const FULL_ACK_PERIOD: Duration = Duration::from_millis(10);
50
51/// Light-ACK packet-count threshold — 64 packets (`specs/rules/srt-arq.md`
52/// rule 12, L2877-2883): if this many packets have been sent/received
53/// within the Full ACK period, the receiver sends a Light ACK early.
54pub const LIGHT_ACK_THRESHOLD: u32 = 64;
55
56/// `NAKInterval` floor — 20 milliseconds (`specs/rules/srt-arq.md` rule 22,
57/// L2953-2960 / L3276): `NAKInterval = max((RTT + 4*RTTVar) / 2, 20ms)`.
58pub const NAK_INTERVAL_FLOOR: Duration = Duration::from_millis(20);
59
60/// Convert a [`Duration`] to the wire `Timestamp` field's microsecond
61/// `u32` (`draft-sharabayko-srt-01` §3: "microseconds elapsed since the SRT
62/// connection was established"), clamping rather than panicking if `now`
63/// has advanced past `u32::MAX` microseconds (about 71.5 minutes) since the
64/// caller's epoch — a wrapping/rebasing timestamp policy is a caller
65/// concern, not curated in `specs/rules/srt-arq.md`.
66pub(crate) fn duration_to_wire_us(d: Duration) -> u32 {
67    d.as_micros().min(u128::from(u32::MAX)) as u32
68}
69
70/// `NAKInterval = max((RTT + 4 * RTTVar) / 2, 20 ms)` (`specs/rules/srt-arq.md`
71/// rule 22, verbatim from `draft-sharabayko-srt-01` L2953-2960, unit
72/// resolution cross-referenced from §5.2's L3276 restatement).
73pub(crate) fn nak_interval(rtt: Duration, rtt_var: Duration) -> Duration {
74    let sum = rtt + rtt_var * 4;
75    let half = sum / 2;
76    if half < NAK_INTERVAL_FLOOR {
77        NAK_INTERVAL_FLOOR
78    } else {
79        half
80    }
81}