Skip to main content

bitfold_peer/
unsequenced.rs

1//! Unsequenced packet duplicate detection using a sliding window.
2//!
3//! This module implements a 1024-bit sliding window for detecting duplicate unsequenced packets.
4//! Unsequenced packets can arrive in any order and may be duplicated by the network, so we need
5//! to track which group IDs have already been received to filter out duplicates.
6//!
7//! # Design
8//!
9//! The sliding window uses 1024 bits (32 u32 words) to track received group IDs:
10//! - Each bit represents whether a specific group ID has been received
11//! - The window base (`incoming_unsequenced_group`) tracks the oldest group in the window
12//! - When packets arrive far ahead of the window, the window slides forward
13//! - Old packets (behind the window) are treated as duplicates
14//!
15//! # Wrapping Arithmetic
16//!
17//! Group IDs are u16 values that wrap around at 65536. The window uses wrapping arithmetic
18//! to correctly handle the transition from 65535 to 0.
19
20/// State for tracking unsequenced packet duplicates using a sliding window.
21///
22/// This struct maintains a 1024-bit window for duplicate detection of unsequenced packets.
23/// It tracks which group IDs have been received and automatically slides the window forward
24/// when new packets arrive far ahead of the current window base.
25#[derive(Debug, Clone)]
26pub struct UnsequencedState {
27    /// Incoming unsequenced group base (start of sliding window)
28    incoming_unsequenced_group: u16,
29    /// Unsequenced window for duplicate detection (1024 bits = 128 bytes)
30    /// Each bit represents whether a group has been received in the window
31    unsequenced_window: [u32; 32], // 32 u32s = 1024 bits
32    /// Outgoing unsequenced group counter
33    outgoing_unsequenced_group: u16,
34}
35
36impl Default for UnsequencedState {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl UnsequencedState {
43    /// Creates a new unsequenced state with an empty window.
44    pub fn new() -> Self {
45        Self {
46            incoming_unsequenced_group: 0,
47            unsequenced_window: [0; 32], // All bits start as 0 (no groups received)
48            outgoing_unsequenced_group: 0,
49        }
50    }
51
52    /// Gets the next outgoing unsequenced group ID and increments the counter.
53    pub fn next_outgoing_group(&mut self) -> u16 {
54        let group = self.outgoing_unsequenced_group;
55        self.outgoing_unsequenced_group = self.outgoing_unsequenced_group.wrapping_add(1);
56        group
57    }
58
59    /// Returns the current incoming unsequenced group (window base).
60    pub fn incoming_group(&self) -> u16 {
61        self.incoming_unsequenced_group
62    }
63
64    /// Returns the current outgoing unsequenced group.
65    pub fn outgoing_group(&self) -> u16 {
66        self.outgoing_unsequenced_group
67    }
68
69    /// Checks if an incoming unsequenced group is a duplicate or within the window.
70    /// Returns true if this is a duplicate (already received), false if new.
71    pub fn is_duplicate(&self, group: u16) -> bool {
72        // Check if window is completely empty (no packets received yet)
73        let window_empty = self.unsequenced_window.iter().all(|&w| w == 0);
74        if window_empty {
75            // First packet(s), not a duplicate
76            return false;
77        }
78
79        // Calculate offset from window base
80        let offset = group.wrapping_sub(self.incoming_unsequenced_group);
81
82        // If offset is within window (0-1023), check the window
83        if offset < 1024 {
84            // Check if this bit is set in the window
85            let word_index = (offset / 32) as usize;
86            let bit_index = offset % 32;
87
88            if word_index < 32 {
89                return (self.unsequenced_window[word_index] & (1 << bit_index)) != 0;
90            }
91        }
92
93        // Outside the forward window - check if it's old (behind the window)
94        // Using wrapping arithmetic: if offset > 32768, it's actually a negative offset
95        // meaning it's behind the current window base (old packet)
96        if offset > 32768 {
97            return true; // Old packet, treat as duplicate
98        }
99
100        // New packet ahead of window, not a duplicate
101        false
102    }
103
104    /// Marks an incoming unsequenced group as received in the window.
105    /// Advances the window base if necessary.
106    pub fn mark_received(&mut self, group: u16) {
107        // Calculate offset from window base
108        let offset = group.wrapping_sub(self.incoming_unsequenced_group);
109
110        // If the group is far ahead (and not wrapping backwards), we need to advance the window base
111        if (1024..=32768).contains(&offset) {
112            // Calculate how much to advance
113            let advance = offset.saturating_sub(512); // Keep window centered on new packets
114
115            // Shift window bits and clear old ones
116            let word_shift = (advance / 32) as usize;
117            let bit_shift = advance % 32;
118
119            if word_shift > 0 {
120                // Shift entire words
121                if word_shift >= 32 {
122                    // Complete window reset
123                    self.unsequenced_window = [0; 32];
124                } else {
125                    // Partial shift
126                    for i in 0..(32 - word_shift) {
127                        self.unsequenced_window[i] = self.unsequenced_window[i + word_shift];
128                    }
129                    for i in (32 - word_shift)..32 {
130                        self.unsequenced_window[i] = 0;
131                    }
132                }
133            }
134
135            if bit_shift > 0 && word_shift < 32 {
136                // Shift bits within words
137                for i in 0..31 {
138                    self.unsequenced_window[i] = (self.unsequenced_window[i] >> bit_shift)
139                        | (self.unsequenced_window[i + 1] << (32 - bit_shift));
140                }
141                self.unsequenced_window[31] >>= bit_shift;
142            }
143
144            // Advance window base
145            self.incoming_unsequenced_group = self.incoming_unsequenced_group.wrapping_add(advance);
146        }
147
148        // Now mark the bit for this group
149        let final_offset = group.wrapping_sub(self.incoming_unsequenced_group);
150        if final_offset < 1024 {
151            let word_index = (final_offset / 32) as usize;
152            let bit_index = final_offset % 32;
153            if word_index < 32 {
154                self.unsequenced_window[word_index] |= 1 << bit_index;
155            }
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn test_unsequenced_window_sliding() {
166        let mut state = UnsequencedState::new();
167
168        // Receive a packet at group 0
169        assert!(!state.is_duplicate(0));
170        state.mark_received(0);
171
172        // Same packet again should be duplicate
173        assert!(state.is_duplicate(0));
174
175        // Receive a packet far ahead (group 1500, outside window of 1024)
176        assert!(!state.is_duplicate(1500));
177        state.mark_received(1500);
178
179        // Now group 0 should be treated as very old and duplicate
180        assert!(state.is_duplicate(0));
181    }
182
183    #[test]
184    fn test_unsequenced_window_wrapping() {
185        let mut state = UnsequencedState::new();
186
187        // Start by receiving some packets to establish a base around 65000
188        for i in 0..3 {
189            let group = 65000 + i;
190            assert!(!state.is_duplicate(group));
191            state.mark_received(group);
192        }
193
194        // Now receive near the end of u16 range
195        assert!(!state.is_duplicate(65500));
196        state.mark_received(65500);
197
198        // Wrap around to 10 (should be treated as newer, after 65535)
199        assert!(!state.is_duplicate(10));
200        state.mark_received(10);
201
202        // Sending 65002 (old packet from before) should be duplicate
203        assert!(state.is_duplicate(65002));
204    }
205
206    #[test]
207    fn test_unsequenced_basic_duplicate_detection() {
208        let mut state = UnsequencedState::new();
209
210        // First packet should not be duplicate
211        assert!(!state.is_duplicate(5));
212        state.mark_received(5);
213
214        // Same packet again should be duplicate
215        assert!(state.is_duplicate(5));
216
217        // Different packet should not be duplicate
218        assert!(!state.is_duplicate(10));
219        state.mark_received(10);
220
221        // Both should now be marked
222        assert!(state.is_duplicate(5));
223        assert!(state.is_duplicate(10));
224    }
225
226    #[test]
227    fn test_unsequenced_window_boundaries() {
228        let mut state = UnsequencedState::new();
229
230        // Mark group 100 as base
231        state.mark_received(100);
232
233        // Within window (100 + 1023 = 1123)
234        assert!(!state.is_duplicate(1123));
235        state.mark_received(1123);
236        assert!(state.is_duplicate(1123));
237
238        // Just outside window should trigger slide
239        assert!(!state.is_duplicate(1124));
240        state.mark_received(1124);
241
242        // Old packet should now be duplicate
243        assert!(state.is_duplicate(100));
244    }
245
246    #[test]
247    fn test_next_outgoing_group() {
248        let mut state = UnsequencedState::new();
249
250        assert_eq!(state.next_outgoing_group(), 0);
251        assert_eq!(state.next_outgoing_group(), 1);
252        assert_eq!(state.next_outgoing_group(), 2);
253
254        // Test wrapping
255        state.outgoing_unsequenced_group = 65535;
256        assert_eq!(state.next_outgoing_group(), 65535);
257        assert_eq!(state.next_outgoing_group(), 0);
258    }
259
260    #[test]
261    fn test_getters() {
262        let mut state = UnsequencedState::new();
263
264        assert_eq!(state.incoming_group(), 0);
265        assert_eq!(state.outgoing_group(), 0);
266
267        state.mark_received(100);
268        assert_eq!(state.incoming_group(), 0); // Base doesn't move for small offsets
269
270        state.mark_received(1500); // Far ahead, should trigger slide
271        assert!(state.incoming_group() > 0); // Base should have moved
272
273        state.next_outgoing_group();
274        assert_eq!(state.outgoing_group(), 1);
275    }
276
277    #[test]
278    fn test_empty_window() {
279        let state = UnsequencedState::new();
280
281        // Empty window should not consider anything a duplicate
282        assert!(!state.is_duplicate(0));
283        assert!(!state.is_duplicate(100));
284        assert!(!state.is_duplicate(65535));
285    }
286
287    #[test]
288    fn test_complete_window_reset() {
289        let mut state = UnsequencedState::new();
290
291        // Mark some packets
292        state.mark_received(0);
293        state.mark_received(1);
294        state.mark_received(2);
295
296        // Jump very far ahead (more than 32 words)
297        state.mark_received(2000);
298
299        // Old packets should be duplicates (window was reset and moved)
300        assert!(state.is_duplicate(0));
301        assert!(state.is_duplicate(1));
302        assert!(state.is_duplicate(2));
303
304        // New packet in the new window should work
305        assert!(!state.is_duplicate(2001));
306        state.mark_received(2001);
307        assert!(state.is_duplicate(2001));
308    }
309}