1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::vec;
4use alloc::vec::Vec;
5use core::mem::MaybeUninit;
6
7use super::types::PacketHashlistAllocation;
8
9pub struct PacketHashlist {
15 queue: PacketHashQueue,
16 set: PacketHashSet,
17}
18
19impl PacketHashlist {
20 pub fn new(max_size: usize) -> Self {
21 Self::with_allocation(max_size, PacketHashlistAllocation::Eager)
22 }
23
24 pub fn with_allocation(max_size: usize, allocation: PacketHashlistAllocation) -> Self {
25 Self {
26 queue: PacketHashQueue::new(max_size, allocation),
27 set: PacketHashSet::new(max_size, allocation),
28 }
29 }
30
31 pub fn is_duplicate(&self, hash: &[u8; 32]) -> bool {
33 self.set.contains(hash)
34 }
35
36 pub fn add(&mut self, hash: [u8; 32]) {
38 if self.queue.capacity() == 0 || self.set.contains(&hash) {
39 return;
40 }
41
42 if self.queue.len() == self.queue.capacity() {
43 let Some(evicted) = self.queue.pop_front() else {
44 return;
45 };
46 let removed = self.set.remove(&evicted);
47 debug_assert!(removed, "evicted hash must exist in dedup set");
48 }
49
50 let inserted = self.set.insert(hash);
51 debug_assert!(inserted, "new hash must insert into dedup set");
52 self.queue.push_back(hash);
53 }
54
55 pub fn remove(&mut self, hash: &[u8; 32]) -> bool {
57 if !self.set.remove(hash) {
58 return false;
59 }
60
61 let removed = self.queue.remove(hash);
62 debug_assert!(removed, "dedup set entry must exist in FIFO queue");
63 true
64 }
65
66 pub fn len(&self) -> usize {
68 debug_assert_eq!(self.queue.len(), self.set.len());
69 self.queue.len()
70 }
71
72 pub fn is_empty(&self) -> bool {
73 self.len() == 0
74 }
75
76 pub fn iter(&self) -> impl Iterator<Item = &[u8; 32]> {
78 (0..self.queue.len).map(|offset| {
79 let index = (self.queue.head + offset) % self.queue.capacity();
80 self.queue.entries.get(index)
81 })
82 }
83}
84
85struct RawHashSlots {
91 slots: Box<[MaybeUninit<[u8; 32]>]>,
92}
93
94impl RawHashSlots {
95 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
96 let mut slots = Box::<[[u8; 32]]>::new_uninit_slice(capacity);
97 if allocation == PacketHashlistAllocation::Eager {
98 for slot in &mut slots {
99 unsafe { core::ptr::write_volatile(slot.as_mut_ptr(), [0; 32]) };
102 }
103 }
104 Self { slots }
105 }
106
107 fn len(&self) -> usize {
108 self.slots.len()
109 }
110
111 fn write(&mut self, index: usize, hash: [u8; 32]) {
112 self.slots[index].write(hash);
113 }
114
115 fn read(&self, index: usize) -> [u8; 32] {
116 unsafe { self.slots[index].assume_init_read() }
119 }
120
121 fn get(&self, index: usize) -> &[u8; 32] {
122 unsafe { self.slots[index].assume_init_ref() }
125 }
126}
127
128pub struct AnnounceSignatureCache {
135 entries: BTreeMap<[u8; 32], f64>,
136 insertion_order: Vec<[u8; 32]>,
137 max_entries: usize,
138 ttl_secs: f64,
139}
140
141impl AnnounceSignatureCache {
142 pub fn new(max_entries: usize, ttl_secs: f64) -> Self {
143 Self {
144 entries: BTreeMap::new(),
145 insertion_order: Vec::new(),
146 max_entries,
147 ttl_secs,
148 }
149 }
150
151 pub fn contains(&self, key: &[u8; 32]) -> bool {
153 if self.max_entries == 0 {
154 return false;
155 }
156 self.entries.contains_key(key)
157 }
158
159 pub fn insert(&mut self, key: [u8; 32], now: f64) {
161 if self.max_entries == 0 {
162 return;
163 }
164 if self.entries.contains_key(&key) {
165 return;
166 }
167 while self.entries.len() >= self.max_entries {
169 if let Some(oldest) = self.insertion_order.first().copied() {
170 self.entries.remove(&oldest);
171 self.insertion_order.remove(0);
172 } else {
173 break;
174 }
175 }
176 self.entries.insert(key, now);
177 self.insertion_order.push(key);
178 }
179
180 pub fn cull(&mut self, now: f64) -> usize {
182 if self.max_entries == 0 {
183 return 0;
184 }
185 let cutoff = now - self.ttl_secs;
186 let before = self.entries.len();
187 self.entries.retain(|_, ts| *ts > cutoff);
188 self.insertion_order
189 .retain(|key| self.entries.contains_key(key));
190 before - self.entries.len()
191 }
192
193 pub fn len(&self) -> usize {
194 self.entries.len()
195 }
196
197 pub fn is_empty(&self) -> bool {
198 self.entries.is_empty()
199 }
200}
201
202struct PacketHashQueue {
203 entries: RawHashSlots,
204 head: usize,
205 len: usize,
206}
207
208impl PacketHashQueue {
209 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
210 Self {
211 entries: RawHashSlots::new(capacity, allocation),
212 head: 0,
213 len: 0,
214 }
215 }
216
217 fn capacity(&self) -> usize {
218 self.entries.len()
219 }
220
221 fn len(&self) -> usize {
222 self.len
223 }
224
225 fn push_back(&mut self, hash: [u8; 32]) {
226 debug_assert!(self.len < self.capacity());
227 if self.capacity() == 0 {
228 return;
229 }
230 let tail = (self.head + self.len) % self.capacity();
231 self.entries.write(tail, hash);
232 self.len += 1;
233 }
234
235 fn pop_front(&mut self) -> Option<[u8; 32]> {
236 if self.len == 0 || self.capacity() == 0 {
237 return None;
238 }
239 let hash = self.entries.read(self.head);
240 self.head = (self.head + 1) % self.capacity();
241 self.len -= 1;
242 if self.len == 0 {
243 self.head = 0;
244 }
245 Some(hash)
246 }
247
248 fn remove(&mut self, hash: &[u8; 32]) -> bool {
249 let Some(offset) = (0..self.len).find(|offset| {
250 let index = (self.head + offset) % self.capacity();
251 self.entries.get(index) == hash
252 }) else {
253 return false;
254 };
255
256 for current in offset..self.len - 1 {
257 let next_index = (self.head + current + 1) % self.capacity();
258 let current_index = (self.head + current) % self.capacity();
259 let next = self.entries.read(next_index);
260 self.entries.write(current_index, next);
261 }
262 self.len -= 1;
263 if self.len == 0 {
264 self.head = 0;
265 }
266 true
267 }
268}
269
270struct PacketHashSet {
271 entries: RawHashSlots,
272 controls: Box<[u8]>,
273 len: usize,
274}
275
276impl PacketHashSet {
277 fn new(max_entries: usize, allocation: PacketHashlistAllocation) -> Self {
278 let capacity = bucket_capacity(max_entries);
279 Self {
280 entries: RawHashSlots::new(capacity, allocation),
281 controls: vec![0; capacity].into_boxed_slice(),
282 len: 0,
283 }
284 }
285
286 fn len(&self) -> usize {
287 self.len
288 }
289
290 fn contains(&self, hash: &[u8; 32]) -> bool {
291 if self.controls.is_empty() {
292 return false;
293 }
294
295 let mut idx = self.bucket_index(hash);
296 loop {
297 if self.controls[idx] == 0 {
298 return false;
299 }
300 if self.entries.get(idx) == hash {
301 return true;
302 }
303 idx = (idx + 1) & (self.controls.len() - 1);
304 }
305 }
306
307 fn insert(&mut self, hash: [u8; 32]) -> bool {
308 if self.controls.is_empty() {
309 return false;
310 }
311
312 let mut idx = self.bucket_index(&hash);
313 loop {
314 if self.controls[idx] == 0 {
315 self.entries.write(idx, hash);
317 self.controls[idx] = 1;
318 self.len += 1;
319 return true;
320 }
321 if self.entries.get(idx) == &hash {
322 return false;
323 }
324 idx = (idx + 1) & (self.controls.len() - 1);
325 }
326 }
327
328 fn remove(&mut self, hash: &[u8; 32]) -> bool {
329 if self.controls.is_empty() {
330 return false;
331 }
332
333 let mut idx = self.bucket_index(hash);
334 loop {
335 if self.controls[idx] == 0 {
336 return false;
337 }
338 if self.entries.get(idx) == hash {
339 break;
340 }
341 idx = (idx + 1) & (self.controls.len() - 1);
342 }
343
344 self.controls[idx] = 0;
345 self.len -= 1;
346
347 let mut next = (idx + 1) & (self.controls.len() - 1);
348 while self.controls[next] != 0 {
349 let entry = self.entries.read(next);
350 self.controls[next] = 0;
351 self.len -= 1;
352 let inserted = self.insert(entry);
353 debug_assert!(inserted, "cluster reinsert after removal must succeed");
354 next = (next + 1) & (self.controls.len() - 1);
355 }
356
357 true
358 }
359
360 fn bucket_index(&self, hash: &[u8; 32]) -> usize {
361 debug_assert!(!self.controls.is_empty());
362 (hash_bytes(hash) as usize) & (self.controls.len() - 1)
363 }
364}
365
366fn bucket_capacity(max_entries: usize) -> usize {
367 if max_entries == 0 {
368 return 0;
369 }
370
371 let min_capacity = max_entries.saturating_mul(2).max(1);
372 min_capacity.next_power_of_two()
373}
374
375fn hash_bytes(hash: &[u8; 32]) -> u64 {
376 let mut state = 0xcbf29ce484222325u64;
377 for byte in hash {
378 state ^= u64::from(*byte);
379 state = state.wrapping_mul(0x100000001b3);
380 }
381 state
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 fn make_hash(seed: u8) -> [u8; 32] {
389 let mut h = [0u8; 32];
390 h[0] = seed;
391 h
392 }
393
394 fn policies() -> [PacketHashlistAllocation; 2] {
395 [
396 PacketHashlistAllocation::Eager,
397 PacketHashlistAllocation::Lazy,
398 ]
399 }
400
401 #[test]
402 fn test_new_hash_not_duplicate() {
403 for policy in policies() {
404 let hl = PacketHashlist::with_allocation(100, policy);
405 assert!(!hl.is_duplicate(&make_hash(1)));
406 }
407 }
408
409 #[test]
410 fn test_added_hash_is_duplicate() {
411 for policy in policies() {
412 let mut hl = PacketHashlist::with_allocation(100, policy);
413 let h = make_hash(1);
414 hl.add(h);
415 assert!(hl.is_duplicate(&h));
416 }
417 }
418
419 #[test]
420 fn test_duplicate_insert_does_not_increase_len() {
421 for policy in policies() {
422 let mut hl = PacketHashlist::with_allocation(2, policy);
423 let h = make_hash(1);
424 hl.add(h);
425 hl.add(h);
426 assert_eq!(hl.len(), 1);
427 assert!(hl.is_duplicate(&h));
428 }
429 }
430
431 #[test]
432 fn test_full_hashlist_evicts_oldest_unique_hash() {
433 for policy in policies() {
434 let mut hl = PacketHashlist::with_allocation(3, policy);
435 let hashes = [make_hash(1), make_hash(2), make_hash(3), make_hash(4)];
436 for hash in hashes {
437 hl.add(hash);
438 }
439 assert!(!hl.is_duplicate(&hashes[0]));
440 assert!(hashes[1..].iter().all(|hash| hl.is_duplicate(hash)));
441 assert_eq!(hl.len(), 3);
442 }
443 }
444
445 #[test]
446 fn test_duplicate_does_not_refresh_recency() {
447 for policy in policies() {
448 let mut hl = PacketHashlist::with_allocation(2, policy);
449 let h1 = make_hash(1);
450 let h2 = make_hash(2);
451 let h3 = make_hash(3);
452 hl.add(h1);
453 hl.add(h2);
454 hl.add(h2);
455 hl.add(h3);
456 assert!(!hl.is_duplicate(&h1));
457 assert!(hl.is_duplicate(&h2));
458 assert!(hl.is_duplicate(&h3));
459 }
460 }
461
462 #[test]
463 fn removal_preserves_fifo_order_after_queue_wraps() {
464 for policy in policies() {
465 let mut hl = PacketHashlist::with_allocation(3, policy);
466 for seed in 1..=4 {
467 hl.add(make_hash(seed));
468 }
469
470 assert!(hl.remove(&make_hash(3)));
471 assert!(!hl.remove(&make_hash(1)));
472 assert_eq!(
473 hl.iter().copied().collect::<Vec<_>>(),
474 vec![make_hash(2), make_hash(4)]
475 );
476
477 hl.add(make_hash(5));
478 assert_eq!(
479 hl.iter().copied().collect::<Vec<_>>(),
480 vec![make_hash(2), make_hash(4), make_hash(5)]
481 );
482 }
483 }
484
485 #[test]
486 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
487 for policy in policies() {
488 let mut hl = PacketHashlist::with_allocation(3, policy);
489 for seed in 1..=9 {
490 hl.add(make_hash(seed));
491 }
492 assert_eq!(
493 hl.iter().copied().collect::<Vec<_>>(),
494 vec![make_hash(7), make_hash(8), make_hash(9)]
495 );
496 }
497 }
498
499 #[test]
500 fn test_zero_capacity_hashlist_is_noop() {
501 for policy in policies() {
502 let mut hl = PacketHashlist::with_allocation(0, policy);
503 let h = make_hash(1);
504 hl.add(h);
505 assert_eq!(hl.len(), 0);
506 assert!(!hl.is_duplicate(&h));
507 assert_eq!(hl.iter().count(), 0);
508 }
509 }
510
511 #[test]
512 fn collision_cluster_removal_preserves_remaining_entries() {
513 for policy in policies() {
514 let mut set = PacketHashSet::new(3, policy);
515 let mut colliding = Vec::new();
516 for seed in 0..=u8::MAX {
517 let hash = make_hash(seed);
518 if hash_bytes(&hash) & 7 == 0 {
519 colliding.push(hash);
520 if colliding.len() == 3 {
521 break;
522 }
523 }
524 }
525 assert_eq!(colliding.len(), 3);
526 for hash in &colliding {
527 assert!(set.insert(*hash));
528 }
529 assert!(set.remove(&colliding[0]));
530 assert!(set.contains(&colliding[1]));
531 assert!(set.contains(&colliding[2]));
532 }
533 }
534
535 #[test]
536 fn raw_slots_read_only_after_write() {
537 for policy in policies() {
538 let mut slots = RawHashSlots::new(2, policy);
539 slots.write(1, make_hash(42));
540 assert_eq!(slots.read(1), make_hash(42));
541 }
542 }
543
544 #[test]
547 fn test_sig_cache_insert_and_contains() {
548 let mut cache = AnnounceSignatureCache::new(100, 60.0);
549 let k = make_hash(1);
550 assert!(!cache.contains(&k));
551 cache.insert(k, 100.0);
552 assert!(cache.contains(&k));
553 assert_eq!(cache.len(), 1);
554 }
555
556 #[test]
557 fn test_sig_cache_duplicate_insert_is_noop() {
558 let mut cache = AnnounceSignatureCache::new(100, 60.0);
559 let k = make_hash(1);
560 cache.insert(k, 100.0);
561 cache.insert(k, 200.0);
562 assert_eq!(cache.len(), 1);
563 }
564
565 #[test]
566 fn test_sig_cache_ttl_expiry() {
567 let mut cache = AnnounceSignatureCache::new(100, 60.0);
568 cache.insert(make_hash(1), 100.0);
569 cache.insert(make_hash(2), 150.0);
570
571 assert_eq!(cache.cull(155.0), 0);
573 assert_eq!(cache.len(), 2);
574
575 assert_eq!(cache.cull(161.0), 1);
577 assert_eq!(cache.len(), 1);
578 assert!(!cache.contains(&make_hash(1)));
579 assert!(cache.contains(&make_hash(2)));
580 }
581
582 #[test]
583 fn test_sig_cache_capacity_eviction() {
584 let mut cache = AnnounceSignatureCache::new(2, 600.0);
585 cache.insert(make_hash(1), 100.0);
586 cache.insert(make_hash(2), 101.0);
587 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
590 assert!(!cache.contains(&make_hash(1)));
591 assert!(cache.contains(&make_hash(2)));
592 assert!(cache.contains(&make_hash(3)));
593 }
594
595 #[test]
596 fn test_sig_cache_disabled_when_zero_capacity() {
597 let mut cache = AnnounceSignatureCache::new(0, 60.0);
598 let k = make_hash(1);
599 cache.insert(k, 100.0);
600 assert!(!cache.contains(&k));
601 assert_eq!(cache.len(), 0);
602 assert_eq!(cache.cull(200.0), 0);
603 }
604}