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}