rns_core/transport/
dedup.rs1use alloc::vec;
2use alloc::vec::Vec;
3
4pub struct PacketHashlist {
10 queue: PacketHashQueue,
11 set: PacketHashSet,
12}
13
14impl PacketHashlist {
15 pub fn new(max_size: usize) -> Self {
16 Self {
17 queue: PacketHashQueue::new(max_size),
18 set: PacketHashSet::new(max_size),
19 }
20 }
21
22 pub fn is_duplicate(&self, hash: &[u8; 32]) -> bool {
24 self.set.contains(hash)
25 }
26
27 pub fn add(&mut self, hash: [u8; 32]) {
29 if self.queue.capacity() == 0 || self.set.contains(&hash) {
30 return;
31 }
32
33 if self.queue.len() == self.queue.capacity() {
34 let evicted = self
35 .queue
36 .pop_front()
37 .expect("full dedup queue must have an oldest entry to evict");
38 let removed = self.set.remove(&evicted);
39 debug_assert!(removed, "evicted hash must exist in dedup set");
40 }
41
42 let inserted = self.set.insert(hash);
43 debug_assert!(inserted, "new hash must insert into dedup set");
44 self.queue.push_back(hash);
45 }
46
47 pub fn len(&self) -> usize {
49 debug_assert_eq!(self.queue.len(), self.set.len());
50 self.queue.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
54 self.len() == 0
55 }
56}
57
58struct PacketHashQueue {
59 entries: Vec<[u8; 32]>,
60 head: usize,
61 len: usize,
62}
63
64impl PacketHashQueue {
65 fn new(capacity: usize) -> Self {
66 Self {
67 entries: vec![[0u8; 32]; capacity],
68 head: 0,
69 len: 0,
70 }
71 }
72
73 fn capacity(&self) -> usize {
74 self.entries.len()
75 }
76
77 fn len(&self) -> usize {
78 self.len
79 }
80
81 fn push_back(&mut self, hash: [u8; 32]) {
82 debug_assert!(self.len < self.capacity());
83 if self.capacity() == 0 {
84 return;
85 }
86 let tail = (self.head + self.len) % self.capacity();
87 self.entries[tail] = hash;
88 self.len += 1;
89 }
90
91 fn pop_front(&mut self) -> Option<[u8; 32]> {
92 if self.len == 0 || self.capacity() == 0 {
93 return None;
94 }
95 let hash = self.entries[self.head];
96 self.head = (self.head + 1) % self.capacity();
97 self.len -= 1;
98 if self.len == 0 {
99 self.head = 0;
100 }
101 Some(hash)
102 }
103}
104
105struct PacketHashSet {
106 buckets: Vec<Option<[u8; 32]>>,
107 len: usize,
108}
109
110impl PacketHashSet {
111 fn new(max_entries: usize) -> Self {
112 Self {
113 buckets: vec![None; bucket_capacity(max_entries)],
114 len: 0,
115 }
116 }
117
118 fn len(&self) -> usize {
119 self.len
120 }
121
122 fn contains(&self, hash: &[u8; 32]) -> bool {
123 if self.buckets.is_empty() {
124 return false;
125 }
126
127 let mut idx = self.bucket_index(hash);
128 loop {
129 match self.buckets[idx] {
130 Some(entry) if &entry == hash => return true,
131 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
132 None => return false,
133 }
134 }
135 }
136
137 fn insert(&mut self, hash: [u8; 32]) -> bool {
138 if self.buckets.is_empty() {
139 return false;
140 }
141
142 let mut idx = self.bucket_index(&hash);
143 loop {
144 match self.buckets[idx] {
145 Some(entry) if entry == hash => return false,
146 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
147 None => {
148 self.buckets[idx] = Some(hash);
149 self.len += 1;
150 return true;
151 }
152 }
153 }
154 }
155
156 fn remove(&mut self, hash: &[u8; 32]) -> bool {
157 if self.buckets.is_empty() {
158 return false;
159 }
160
161 let mut idx = self.bucket_index(hash);
162 loop {
163 match self.buckets[idx] {
164 Some(entry) if &entry == hash => break,
165 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
166 None => return false,
167 }
168 }
169
170 self.buckets[idx] = None;
171 self.len -= 1;
172
173 let mut next = (idx + 1) & (self.buckets.len() - 1);
174 while let Some(entry) = self.buckets[next].take() {
175 self.len -= 1;
176 let inserted = self.insert(entry);
177 debug_assert!(inserted, "cluster reinsert after removal must succeed");
178 next = (next + 1) & (self.buckets.len() - 1);
179 }
180
181 true
182 }
183
184 fn bucket_index(&self, hash: &[u8; 32]) -> usize {
185 debug_assert!(!self.buckets.is_empty());
186 (hash_bytes(hash) as usize) & (self.buckets.len() - 1)
187 }
188}
189
190fn bucket_capacity(max_entries: usize) -> usize {
191 if max_entries == 0 {
192 return 0;
193 }
194
195 let min_capacity = max_entries.saturating_mul(2).max(1);
196 min_capacity.next_power_of_two()
197}
198
199fn hash_bytes(hash: &[u8; 32]) -> u64 {
200 let mut state = 0xcbf29ce484222325u64;
201 for byte in hash {
202 state ^= u64::from(*byte);
203 state = state.wrapping_mul(0x100000001b3);
204 }
205 state
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 fn make_hash(seed: u8) -> [u8; 32] {
213 let mut h = [0u8; 32];
214 h[0] = seed;
215 h
216 }
217
218 #[test]
219 fn test_new_hash_not_duplicate() {
220 let hl = PacketHashlist::new(100);
221 assert!(!hl.is_duplicate(&make_hash(1)));
222 }
223
224 #[test]
225 fn test_added_hash_is_duplicate() {
226 let mut hl = PacketHashlist::new(100);
227 let h = make_hash(1);
228 hl.add(h);
229 assert!(hl.is_duplicate(&h));
230 }
231
232 #[test]
233 fn test_duplicate_insert_does_not_increase_len() {
234 let mut hl = PacketHashlist::new(2);
235 let h = make_hash(1);
236
237 hl.add(h);
238 hl.add(h);
239
240 assert_eq!(hl.len(), 1);
241 assert!(hl.is_duplicate(&h));
242 }
243
244 #[test]
245 fn test_full_hashlist_evicts_oldest_unique_hash() {
246 let mut hl = PacketHashlist::new(3);
247 let h1 = make_hash(1);
248 let h2 = make_hash(2);
249 let h3 = make_hash(3);
250 let h4 = make_hash(4);
251
252 hl.add(h1);
253 hl.add(h2);
254 hl.add(h3);
255 hl.add(h4);
256
257 assert!(!hl.is_duplicate(&h1));
258 assert!(hl.is_duplicate(&h2));
259 assert!(hl.is_duplicate(&h3));
260 assert!(hl.is_duplicate(&h4));
261 assert_eq!(hl.len(), 3);
262 }
263
264 #[test]
265 fn test_duplicate_does_not_refresh_recency() {
266 let mut hl = PacketHashlist::new(2);
267 let h1 = make_hash(1);
268 let h2 = make_hash(2);
269 let h3 = make_hash(3);
270
271 hl.add(h1);
272 hl.add(h2);
273 hl.add(h2);
274 hl.add(h3);
275
276 assert!(!hl.is_duplicate(&h1));
277 assert!(hl.is_duplicate(&h2));
278 assert!(hl.is_duplicate(&h3));
279 assert_eq!(hl.len(), 2);
280 }
281
282 #[test]
283 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
284 let mut hl = PacketHashlist::new(3);
285 let h1 = make_hash(1);
286 let h2 = make_hash(2);
287 let h3 = make_hash(3);
288 let h4 = make_hash(4);
289 let h5 = make_hash(5);
290
291 hl.add(h1);
292 hl.add(h2);
293 hl.add(h3);
294 hl.add(h4);
295 hl.add(h5);
296
297 assert!(!hl.is_duplicate(&h1));
298 assert!(!hl.is_duplicate(&h2));
299 assert!(hl.is_duplicate(&h3));
300 assert!(hl.is_duplicate(&h4));
301 assert!(hl.is_duplicate(&h5));
302 assert_eq!(hl.len(), 3);
303 }
304
305 #[test]
306 fn test_zero_capacity_hashlist_is_noop() {
307 let mut hl = PacketHashlist::new(0);
308 let h = make_hash(1);
309
310 hl.add(h);
311
312 assert_eq!(hl.len(), 0);
313 assert!(!hl.is_duplicate(&h));
314 }
315}