rns_core/transport/
dedup.rs1use alloc::collections::BTreeMap;
2use alloc::vec;
3use alloc::vec::Vec;
4
5pub struct PacketHashlist {
11 queue: PacketHashQueue,
12 set: PacketHashSet,
13}
14
15impl PacketHashlist {
16 pub fn new(max_size: usize) -> Self {
17 Self {
18 queue: PacketHashQueue::new(max_size),
19 set: PacketHashSet::new(max_size),
20 }
21 }
22
23 pub fn is_duplicate(&self, hash: &[u8; 32]) -> bool {
25 self.set.contains(hash)
26 }
27
28 pub fn add(&mut self, hash: [u8; 32]) {
30 if self.queue.capacity() == 0 || self.set.contains(&hash) {
31 return;
32 }
33
34 if self.queue.len() == self.queue.capacity() {
35 let evicted = self
36 .queue
37 .pop_front()
38 .expect("full dedup queue must have an oldest entry to evict");
39 let removed = self.set.remove(&evicted);
40 debug_assert!(removed, "evicted hash must exist in dedup set");
41 }
42
43 let inserted = self.set.insert(hash);
44 debug_assert!(inserted, "new hash must insert into dedup set");
45 self.queue.push_back(hash);
46 }
47
48 pub fn len(&self) -> usize {
50 debug_assert_eq!(self.queue.len(), self.set.len());
51 self.queue.len()
52 }
53
54 pub fn is_empty(&self) -> bool {
55 self.len() == 0
56 }
57}
58
59pub struct AnnounceSignatureCache {
66 entries: BTreeMap<[u8; 32], f64>,
67 insertion_order: Vec<[u8; 32]>,
68 max_entries: usize,
69 ttl_secs: f64,
70}
71
72impl AnnounceSignatureCache {
73 pub fn new(max_entries: usize, ttl_secs: f64) -> Self {
74 Self {
75 entries: BTreeMap::new(),
76 insertion_order: Vec::new(),
77 max_entries,
78 ttl_secs,
79 }
80 }
81
82 pub fn contains(&self, key: &[u8; 32]) -> bool {
84 if self.max_entries == 0 {
85 return false;
86 }
87 self.entries.contains_key(key)
88 }
89
90 pub fn insert(&mut self, key: [u8; 32], now: f64) {
92 if self.max_entries == 0 {
93 return;
94 }
95 if self.entries.contains_key(&key) {
96 return;
97 }
98 while self.entries.len() >= self.max_entries {
100 if let Some(oldest) = self.insertion_order.first().copied() {
101 self.entries.remove(&oldest);
102 self.insertion_order.remove(0);
103 } else {
104 break;
105 }
106 }
107 self.entries.insert(key, now);
108 self.insertion_order.push(key);
109 }
110
111 pub fn cull(&mut self, now: f64) -> usize {
113 if self.max_entries == 0 {
114 return 0;
115 }
116 let cutoff = now - self.ttl_secs;
117 let before = self.entries.len();
118 self.entries.retain(|_, ts| *ts > cutoff);
119 self.insertion_order
120 .retain(|key| self.entries.contains_key(key));
121 before - self.entries.len()
122 }
123
124 pub fn len(&self) -> usize {
125 self.entries.len()
126 }
127}
128
129struct PacketHashQueue {
130 entries: Vec<[u8; 32]>,
131 head: usize,
132 len: usize,
133}
134
135impl PacketHashQueue {
136 fn new(capacity: usize) -> Self {
137 Self {
138 entries: vec![[0u8; 32]; capacity],
139 head: 0,
140 len: 0,
141 }
142 }
143
144 fn capacity(&self) -> usize {
145 self.entries.len()
146 }
147
148 fn len(&self) -> usize {
149 self.len
150 }
151
152 fn push_back(&mut self, hash: [u8; 32]) {
153 debug_assert!(self.len < self.capacity());
154 if self.capacity() == 0 {
155 return;
156 }
157 let tail = (self.head + self.len) % self.capacity();
158 self.entries[tail] = hash;
159 self.len += 1;
160 }
161
162 fn pop_front(&mut self) -> Option<[u8; 32]> {
163 if self.len == 0 || self.capacity() == 0 {
164 return None;
165 }
166 let hash = self.entries[self.head];
167 self.head = (self.head + 1) % self.capacity();
168 self.len -= 1;
169 if self.len == 0 {
170 self.head = 0;
171 }
172 Some(hash)
173 }
174}
175
176struct PacketHashSet {
177 buckets: Vec<Option<[u8; 32]>>,
178 len: usize,
179}
180
181impl PacketHashSet {
182 fn new(max_entries: usize) -> Self {
183 Self {
184 buckets: vec![None; bucket_capacity(max_entries)],
185 len: 0,
186 }
187 }
188
189 fn len(&self) -> usize {
190 self.len
191 }
192
193 fn contains(&self, hash: &[u8; 32]) -> bool {
194 if self.buckets.is_empty() {
195 return false;
196 }
197
198 let mut idx = self.bucket_index(hash);
199 loop {
200 match self.buckets[idx] {
201 Some(entry) if &entry == hash => return true,
202 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
203 None => return false,
204 }
205 }
206 }
207
208 fn insert(&mut self, hash: [u8; 32]) -> bool {
209 if self.buckets.is_empty() {
210 return false;
211 }
212
213 let mut idx = self.bucket_index(&hash);
214 loop {
215 match self.buckets[idx] {
216 Some(entry) if entry == hash => return false,
217 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
218 None => {
219 self.buckets[idx] = Some(hash);
220 self.len += 1;
221 return true;
222 }
223 }
224 }
225 }
226
227 fn remove(&mut self, hash: &[u8; 32]) -> bool {
228 if self.buckets.is_empty() {
229 return false;
230 }
231
232 let mut idx = self.bucket_index(hash);
233 loop {
234 match self.buckets[idx] {
235 Some(entry) if &entry == hash => break,
236 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
237 None => return false,
238 }
239 }
240
241 self.buckets[idx] = None;
242 self.len -= 1;
243
244 let mut next = (idx + 1) & (self.buckets.len() - 1);
245 while let Some(entry) = self.buckets[next].take() {
246 self.len -= 1;
247 let inserted = self.insert(entry);
248 debug_assert!(inserted, "cluster reinsert after removal must succeed");
249 next = (next + 1) & (self.buckets.len() - 1);
250 }
251
252 true
253 }
254
255 fn bucket_index(&self, hash: &[u8; 32]) -> usize {
256 debug_assert!(!self.buckets.is_empty());
257 (hash_bytes(hash) as usize) & (self.buckets.len() - 1)
258 }
259}
260
261fn bucket_capacity(max_entries: usize) -> usize {
262 if max_entries == 0 {
263 return 0;
264 }
265
266 let min_capacity = max_entries.saturating_mul(2).max(1);
267 min_capacity.next_power_of_two()
268}
269
270fn hash_bytes(hash: &[u8; 32]) -> u64 {
271 let mut state = 0xcbf29ce484222325u64;
272 for byte in hash {
273 state ^= u64::from(*byte);
274 state = state.wrapping_mul(0x100000001b3);
275 }
276 state
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn make_hash(seed: u8) -> [u8; 32] {
284 let mut h = [0u8; 32];
285 h[0] = seed;
286 h
287 }
288
289 #[test]
290 fn test_new_hash_not_duplicate() {
291 let hl = PacketHashlist::new(100);
292 assert!(!hl.is_duplicate(&make_hash(1)));
293 }
294
295 #[test]
296 fn test_added_hash_is_duplicate() {
297 let mut hl = PacketHashlist::new(100);
298 let h = make_hash(1);
299 hl.add(h);
300 assert!(hl.is_duplicate(&h));
301 }
302
303 #[test]
304 fn test_duplicate_insert_does_not_increase_len() {
305 let mut hl = PacketHashlist::new(2);
306 let h = make_hash(1);
307
308 hl.add(h);
309 hl.add(h);
310
311 assert_eq!(hl.len(), 1);
312 assert!(hl.is_duplicate(&h));
313 }
314
315 #[test]
316 fn test_full_hashlist_evicts_oldest_unique_hash() {
317 let mut hl = PacketHashlist::new(3);
318 let h1 = make_hash(1);
319 let h2 = make_hash(2);
320 let h3 = make_hash(3);
321 let h4 = make_hash(4);
322
323 hl.add(h1);
324 hl.add(h2);
325 hl.add(h3);
326 hl.add(h4);
327
328 assert!(!hl.is_duplicate(&h1));
329 assert!(hl.is_duplicate(&h2));
330 assert!(hl.is_duplicate(&h3));
331 assert!(hl.is_duplicate(&h4));
332 assert_eq!(hl.len(), 3);
333 }
334
335 #[test]
336 fn test_duplicate_does_not_refresh_recency() {
337 let mut hl = PacketHashlist::new(2);
338 let h1 = make_hash(1);
339 let h2 = make_hash(2);
340 let h3 = make_hash(3);
341
342 hl.add(h1);
343 hl.add(h2);
344 hl.add(h2);
345 hl.add(h3);
346
347 assert!(!hl.is_duplicate(&h1));
348 assert!(hl.is_duplicate(&h2));
349 assert!(hl.is_duplicate(&h3));
350 assert_eq!(hl.len(), 2);
351 }
352
353 #[test]
354 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
355 let mut hl = PacketHashlist::new(3);
356 let h1 = make_hash(1);
357 let h2 = make_hash(2);
358 let h3 = make_hash(3);
359 let h4 = make_hash(4);
360 let h5 = make_hash(5);
361
362 hl.add(h1);
363 hl.add(h2);
364 hl.add(h3);
365 hl.add(h4);
366 hl.add(h5);
367
368 assert!(!hl.is_duplicate(&h1));
369 assert!(!hl.is_duplicate(&h2));
370 assert!(hl.is_duplicate(&h3));
371 assert!(hl.is_duplicate(&h4));
372 assert!(hl.is_duplicate(&h5));
373 assert_eq!(hl.len(), 3);
374 }
375
376 #[test]
377 fn test_zero_capacity_hashlist_is_noop() {
378 let mut hl = PacketHashlist::new(0);
379 let h = make_hash(1);
380
381 hl.add(h);
382
383 assert_eq!(hl.len(), 0);
384 assert!(!hl.is_duplicate(&h));
385 }
386
387 #[test]
390 fn test_sig_cache_insert_and_contains() {
391 let mut cache = AnnounceSignatureCache::new(100, 60.0);
392 let k = make_hash(1);
393 assert!(!cache.contains(&k));
394 cache.insert(k, 100.0);
395 assert!(cache.contains(&k));
396 assert_eq!(cache.len(), 1);
397 }
398
399 #[test]
400 fn test_sig_cache_duplicate_insert_is_noop() {
401 let mut cache = AnnounceSignatureCache::new(100, 60.0);
402 let k = make_hash(1);
403 cache.insert(k, 100.0);
404 cache.insert(k, 200.0);
405 assert_eq!(cache.len(), 1);
406 }
407
408 #[test]
409 fn test_sig_cache_ttl_expiry() {
410 let mut cache = AnnounceSignatureCache::new(100, 60.0);
411 cache.insert(make_hash(1), 100.0);
412 cache.insert(make_hash(2), 150.0);
413
414 assert_eq!(cache.cull(155.0), 0);
416 assert_eq!(cache.len(), 2);
417
418 assert_eq!(cache.cull(161.0), 1);
420 assert_eq!(cache.len(), 1);
421 assert!(!cache.contains(&make_hash(1)));
422 assert!(cache.contains(&make_hash(2)));
423 }
424
425 #[test]
426 fn test_sig_cache_capacity_eviction() {
427 let mut cache = AnnounceSignatureCache::new(2, 600.0);
428 cache.insert(make_hash(1), 100.0);
429 cache.insert(make_hash(2), 101.0);
430 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
433 assert!(!cache.contains(&make_hash(1)));
434 assert!(cache.contains(&make_hash(2)));
435 assert!(cache.contains(&make_hash(3)));
436 }
437
438 #[test]
439 fn test_sig_cache_disabled_when_zero_capacity() {
440 let mut cache = AnnounceSignatureCache::new(0, 60.0);
441 let k = make_hash(1);
442 cache.insert(k, 100.0);
443 assert!(!cache.contains(&k));
444 assert_eq!(cache.len(), 0);
445 assert_eq!(cache.cull(200.0), 0);
446 }
447}