Skip to main content

srt_runtime/arq/
seq.rs

1//! 32-bit wrap-safe sequence-number arithmetic for SRT ARQ.
2//!
3//! SRT data packet sequence numbers occupy the low 31 bits of the header's
4//! first word (`draft-sharabayko-srt-01` §3.1, Figure 3: `Packet Sequence
5//! Number (31)`; the top bit is the header `F` packet-type flag, reused
6//! bit-for-bit as the NAK loss-list range marker, Appendix A) — see the
7//! crate-internal `crate::packet::SEQ_NUMBER_MASK`.
8//!
9//! `specs/rules/srt-arq.md` does not specify a sequence-number comparison or
10//! arithmetic algorithm for ARQ purposes (it is not one of the curated
11//! rules). This module resolves that gap the standard way for a modular
12//! sequence space — comparable to RFC 1982 serial number arithmetic:
13//! circular over the 31-bit space, picking the shorter of the two
14//! directions between two numbers as "before"/"after". This is
15//! implementation-defined, not spec-cited, and is needed for the send/
16//! receive buffers to behave correctly once a stream's sequence numbers
17//! wrap past `0x7FFF_FFFF` back to `0`.
18
19use crate::packet::SEQ_NUMBER_MASK;
20
21/// Size of the SRT sequence-number space: `2^31` (31-bit field, §3.1).
22const SEQ_MOD: i64 = (SEQ_NUMBER_MASK as i64) + 1;
23/// Half the sequence space — the wrap-around threshold used to decide which
24/// of two directions between two sequence numbers is the shorter one.
25const SEQ_HALF: i64 = SEQ_MOD / 2;
26
27/// Add `n` to a sequence number, wrapping at the 31-bit boundary.
28pub fn seq_add(seq: u32, n: u32) -> u32 {
29    (((seq as u64) + (n as u64)) % (SEQ_MOD as u64)) as u32
30}
31
32/// The next sequence number after `seq` (wraps `0x7FFF_FFFF` -> `0`).
33pub fn seq_next(seq: u32) -> u32 {
34    seq_add(seq, 1)
35}
36
37/// Signed circular distance `a - b` in the 31-bit sequence space, in
38/// `(-SEQ_HALF, SEQ_HALF]`. Positive means `a` is ahead of `b`.
39pub fn seq_diff(a: u32, b: u32) -> i32 {
40    let a = i64::from(a & SEQ_NUMBER_MASK);
41    let b = i64::from(b & SEQ_NUMBER_MASK);
42    let raw = (a - b).rem_euclid(SEQ_MOD);
43    if raw > SEQ_HALF {
44        (raw - SEQ_MOD) as i32
45    } else {
46        raw as i32
47    }
48}
49
50/// `a` precedes `b` in circular sequence order.
51pub fn seq_lt(a: u32, b: u32) -> bool {
52    seq_diff(a, b) < 0
53}
54
55/// `a` precedes or equals `b`.
56pub fn seq_leq(a: u32, b: u32) -> bool {
57    seq_diff(a, b) <= 0
58}
59
60/// `a` follows `b` in circular sequence order.
61pub fn seq_gt(a: u32, b: u32) -> bool {
62    seq_diff(a, b) > 0
63}
64
65/// `a` follows or equals `b`.
66pub fn seq_geq(a: u32, b: u32) -> bool {
67    seq_diff(a, b) >= 0
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn wrap_boundary_increment() {
76        assert_eq!(seq_next(SEQ_NUMBER_MASK), 0);
77        assert_eq!(seq_add(SEQ_NUMBER_MASK, 1), 0);
78        assert_eq!(seq_add(SEQ_NUMBER_MASK, 5), 4);
79        assert_eq!(seq_add(0, SEQ_NUMBER_MASK), SEQ_NUMBER_MASK);
80    }
81
82    #[test]
83    fn wrap_boundary_ordering() {
84        // 0 comes right after SEQ_NUMBER_MASK (the maximum 31-bit value) in
85        // circular sequence order.
86        assert!(seq_lt(SEQ_NUMBER_MASK, 0));
87        assert!(seq_gt(0, SEQ_NUMBER_MASK));
88        assert_eq!(seq_diff(0, SEQ_NUMBER_MASK), 1);
89        assert_eq!(seq_diff(SEQ_NUMBER_MASK, 0), -1);
90    }
91
92    #[test]
93    fn ordinary_ordering_without_wrap() {
94        assert!(seq_lt(10, 20));
95        assert!(seq_leq(10, 10));
96        assert!(seq_leq(10, 20));
97        assert!(seq_gt(20, 10));
98        assert!(seq_geq(20, 20));
99        assert!(seq_geq(20, 10));
100        assert!(!seq_lt(20, 10));
101        assert!(!seq_gt(10, 20));
102    }
103
104    #[test]
105    fn diff_is_antisymmetric() {
106        for (a, b) in [(0u32, 0u32), (5, 100), (SEQ_NUMBER_MASK, 3), (12345, 12340)] {
107            assert_eq!(seq_diff(a, b), -seq_diff(b, a));
108        }
109    }
110
111    #[test]
112    fn walking_forward_past_the_wrap_stays_consistent() {
113        // Start just below the wrap and walk forward 10 steps; sequence
114        // order must stay monotonic through the wrap.
115        let start = SEQ_NUMBER_MASK - 4;
116        let mut prev = start;
117        for _ in 0..10 {
118            let next = seq_next(prev);
119            assert!(seq_lt(prev, next), "prev={prev:#x} next={next:#x}");
120            assert_eq!(seq_diff(next, prev), 1);
121            prev = next;
122        }
123    }
124}