Skip to main content

ferro_lumberjack/
sequence.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Wrapping `u32` sequence-number arithmetic for Lumberjack ACKs.
3//!
4//! Lumberjack sequence numbers are `u32` and wrap modulo `2^32`. A naive
5//! signed comparison (`acked >= expected`) is **wrong** for long-running
6//! connections that emit more than `2^32` events between reconnects: the
7//! ACK seq wraps around and a correct ACK gets rejected as "stale".
8//!
9//! [RFC 1982] describes the standard solution — "serial number arithmetic"
10//! — and that is what this module implements.
11//!
12//! [RFC 1982]: https://www.rfc-editor.org/rfc/rfc1982
13
14/// A monotonic sequence number with wrapping `u32` arithmetic.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Sequence(u32);
17
18impl Sequence {
19    /// Construct a sequence number from a raw `u32`.
20    #[must_use]
21    pub const fn new(value: u32) -> Self {
22        Self(value)
23    }
24
25    /// Underlying `u32` value.
26    #[must_use]
27    pub const fn value(self) -> u32 {
28        self.0
29    }
30
31    /// Advance the sequence number by `n`, wrapping at `u32::MAX`.
32    #[must_use]
33    pub const fn advance(self, n: u32) -> Self {
34        Self(self.0.wrapping_add(n))
35    }
36
37    /// Returns true if `acked` is exactly equal to this sequence under
38    /// wrapping arithmetic. Equivalent to `acked == self.value()`, but
39    /// the explicit form documents intent.
40    #[must_use]
41    pub const fn is_exactly_acked_by(self, acked: u32) -> bool {
42        acked.wrapping_sub(self.0) == 0
43    }
44
45    /// Returns true if `acked` is *at least* this sequence — i.e. the
46    /// receiver has acknowledged this sequence or any newer one — under
47    /// wrapping arithmetic per [RFC 1982].
48    ///
49    /// "At least" is interpreted on the half-circle: `acked` is at least
50    /// `self` iff `(acked - self) mod 2^32 < 2^31`. This means the
51    /// comparison is well-defined as long as the two values are within
52    /// `2^31` of each other on the wire — far larger than any plausible
53    /// in-flight window.
54    ///
55    /// [RFC 1982]: https://www.rfc-editor.org/rfc/rfc1982
56    #[must_use]
57    pub const fn is_at_least_acked_by(self, acked: u32) -> bool {
58        // If acked - self underflows past 2^31, acked is "behind" us.
59        acked.wrapping_sub(self.0) < 0x8000_0000
60    }
61}
62
63impl From<u32> for Sequence {
64    fn from(value: u32) -> Self {
65        Self::new(value)
66    }
67}
68
69impl From<Sequence> for u32 {
70    fn from(seq: Sequence) -> Self {
71        seq.value()
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn exact_match() {
81        let s = Sequence::new(100);
82        assert!(s.is_exactly_acked_by(100));
83        assert!(!s.is_exactly_acked_by(99));
84        assert!(!s.is_exactly_acked_by(101));
85    }
86
87    #[test]
88    fn exact_match_wraps_around() {
89        let s = Sequence::new(u32::MAX);
90        assert!(s.is_exactly_acked_by(u32::MAX));
91        // Not acked by 0 — that would be the *next* sequence.
92        assert!(!s.is_exactly_acked_by(0));
93    }
94
95    #[test]
96    fn advance_wraps() {
97        let s = Sequence::new(u32::MAX);
98        assert_eq!(s.advance(1).value(), 0);
99        assert_eq!(s.advance(2).value(), 1);
100    }
101
102    #[test]
103    fn at_least_basic() {
104        let s = Sequence::new(100);
105        assert!(s.is_at_least_acked_by(100));
106        assert!(s.is_at_least_acked_by(101));
107        assert!(s.is_at_least_acked_by(200));
108        assert!(!s.is_at_least_acked_by(99));
109        assert!(!s.is_at_least_acked_by(0));
110    }
111
112    #[test]
113    fn at_least_across_wrap() {
114        // Sender sent seq u32::MAX; receiver acks with seq 5 (after wrap).
115        // The ACK is "ahead" by 6 → at least.
116        let s = Sequence::new(u32::MAX);
117        assert!(s.is_at_least_acked_by(0));
118        assert!(s.is_at_least_acked_by(5));
119        // 2^31 - 1 ahead is still "ahead".
120        assert!(s.is_at_least_acked_by(0x7FFF_FFFE));
121        // 2^31 ahead is the equidistant midpoint — by the RFC 1982 rule,
122        // values exactly 2^31 away are unordered, and we treat them as
123        // "behind" (strict <).
124        assert!(!s.is_at_least_acked_by(0x7FFF_FFFF));
125    }
126
127    #[test]
128    fn round_trip_u32() {
129        let s = Sequence::from(42_u32);
130        let v: u32 = s.into();
131        assert_eq!(v, 42);
132    }
133}