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
9type Key = [u8; 16];
13
14fn key_of(hash: &[u8; 32]) -> Key {
15 let mut key = [0u8; 16];
16 key.copy_from_slice(&hash[..16]);
17 key
18}
19
20pub struct PacketHashlist {
26 queue: PacketHashQueue,
27 set: PacketHashSet,
28 max_size: usize,
29}
30
31impl PacketHashlist {
32 pub fn new(max_size: usize) -> Self {
33 Self::with_allocation(max_size, PacketHashlistAllocation::Eager)
34 }
35
36 pub fn with_allocation(max_size: usize, allocation: PacketHashlistAllocation) -> Self {
44 let max_size = max_size.min(MAX_ENTRIES);
46 Self {
47 queue: PacketHashQueue::new(max_size, allocation),
48 set: PacketHashSet::new(max_size, None, allocation),
49 max_size,
50 }
51 }
52
53 pub fn seed_hashing(&mut self, seed: u64) {
57 self.set.key_seed = Some(seed);
58 if self.set.len > 0 {
61 self.set.rebuild(&self.queue);
62 }
63 }
64
65 pub(crate) fn hashing_seed(&self) -> Option<u64> {
66 self.set.key_seed
67 }
68
69 pub fn is_duplicate(&self, hash: &[u8; 32]) -> bool {
71 self.set.contains(&key_of(hash), &self.queue)
72 }
73
74 pub fn prefetch(&self, hash: &[u8; 32]) {
78 self.set.prefetch(self.set.key(&key_of(hash)));
79 }
80
81 pub fn add(&mut self, hash: [u8; 32]) {
83 let hash = key_of(&hash);
84 if self.max_size == 0 || self.set.contains(&hash, &self.queue) {
85 return;
86 }
87 if self.queue.len() == self.max_size {
88 let oldest = *self.queue.entries.get(self.queue.head);
89 let removed = self.set.remove(&oldest, &self.queue);
90 debug_assert!(removed, "oldest hash must exist in index");
91 self.queue.pop_front();
92 }
93 self.set.make_room(&self.queue);
94 let slot = (self.queue.head + self.queue.len) % self.queue.capacity();
95 self.queue.push_back(hash);
96 self.set.insert(slot, &self.queue);
97 if self.queue.len() == self.max_size {
98 let oldest = self.queue.entries.get(self.queue.head);
101 self.set.prefetch(self.set.key(oldest));
102 }
103 }
104
105 pub fn remove(&mut self, hash: &[u8; 32]) -> bool {
107 let hash = &key_of(hash);
108 if !self.set.contains(hash, &self.queue) {
109 return false;
110 }
111 let removed = self.queue.remove(hash);
112 debug_assert!(removed, "indexed hash must exist in FIFO queue");
113 self.set.rebuild(&self.queue);
115 true
116 }
117
118 pub fn len(&self) -> usize {
120 debug_assert_eq!(self.queue.len(), self.set.len());
121 self.queue.len()
122 }
123
124 pub fn is_empty(&self) -> bool {
125 self.len() == 0
126 }
127
128 pub fn iter(&self) -> impl Iterator<Item = [u8; 32]> + '_ {
132 (0..self.queue.len).map(|offset| {
133 let index = (self.queue.head + offset) % self.queue.capacity();
134 let mut hash = [0u8; 32];
135 hash[..16].copy_from_slice(self.queue.entries.get(index));
136 hash
137 })
138 }
139}
140
141struct RawHashSlots {
144 slots: Box<[MaybeUninit<Key>]>,
145}
146
147impl RawHashSlots {
148 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
149 let mut slots = Box::<[Key]>::new_uninit_slice(capacity);
150 if allocation == PacketHashlistAllocation::Eager {
151 for slot in &mut slots {
152 unsafe { core::ptr::write_volatile(slot.as_mut_ptr(), [0; 16]) };
155 }
156 }
157 Self { slots }
158 }
159
160 fn len(&self) -> usize {
161 self.slots.len()
162 }
163
164 fn write(&mut self, index: usize, hash: Key) {
165 self.slots[index].write(hash);
166 }
167
168 fn read(&self, index: usize) -> Key {
169 unsafe { self.slots[index].assume_init_read() }
172 }
173
174 fn get(&self, index: usize) -> &Key {
175 unsafe { self.slots[index].assume_init_ref() }
178 }
179}
180
181pub struct AnnounceSignatureCache {
188 entries: BTreeMap<[u8; 32], f64>,
189 insertion_order: Vec<[u8; 32]>,
190 max_entries: usize,
191 ttl_secs: f64,
192}
193
194impl AnnounceSignatureCache {
195 pub fn new(max_entries: usize, ttl_secs: f64) -> Self {
196 Self {
197 entries: BTreeMap::new(),
198 insertion_order: Vec::new(),
199 max_entries,
200 ttl_secs,
201 }
202 }
203
204 pub fn contains(&self, key: &[u8; 32]) -> bool {
206 if self.max_entries == 0 {
207 return false;
208 }
209 self.entries.contains_key(key)
210 }
211
212 pub fn insert(&mut self, key: [u8; 32], now: f64) {
214 if self.max_entries == 0 {
215 return;
216 }
217 if self.entries.contains_key(&key) {
218 return;
219 }
220 while self.entries.len() >= self.max_entries {
222 if let Some(oldest) = self.insertion_order.first().copied() {
223 self.entries.remove(&oldest);
224 self.insertion_order.remove(0);
225 } else {
226 break;
227 }
228 }
229 self.entries.insert(key, now);
230 self.insertion_order.push(key);
231 }
232
233 pub fn cull(&mut self, now: f64) -> usize {
235 if self.max_entries == 0 {
236 return 0;
237 }
238 let cutoff = now - self.ttl_secs;
239 let before = self.entries.len();
240 self.entries.retain(|_, ts| *ts > cutoff);
241 self.insertion_order
242 .retain(|key| self.entries.contains_key(key));
243 before - self.entries.len()
244 }
245
246 pub fn len(&self) -> usize {
247 self.entries.len()
248 }
249
250 pub fn is_empty(&self) -> bool {
251 self.entries.is_empty()
252 }
253}
254
255struct PacketHashQueue {
256 entries: RawHashSlots,
257 head: usize,
258 len: usize,
259}
260
261impl PacketHashQueue {
262 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
263 Self {
264 entries: RawHashSlots::new(capacity, allocation),
265 head: 0,
266 len: 0,
267 }
268 }
269
270 fn capacity(&self) -> usize {
271 self.entries.len()
272 }
273
274 fn len(&self) -> usize {
275 self.len
276 }
277
278 fn push_back(&mut self, hash: Key) {
279 debug_assert!(self.len < self.capacity());
280 if self.capacity() == 0 {
281 return;
282 }
283 let tail = (self.head + self.len) % self.capacity();
284 self.entries.write(tail, hash);
285 self.len += 1;
286 }
287
288 fn pop_front(&mut self) -> Option<Key> {
289 if self.len == 0 || self.capacity() == 0 {
290 return None;
291 }
292 let hash = self.entries.read(self.head);
293 self.head = (self.head + 1) % self.capacity();
294 self.len -= 1;
295 if self.len == 0 {
296 self.head = 0;
297 }
298 Some(hash)
299 }
300
301 fn remove(&mut self, hash: &Key) -> bool {
302 let Some(offset) = (0..self.len).find(|offset| {
303 let index = (self.head + offset) % self.capacity();
304 self.entries.get(index) == hash
305 }) else {
306 return false;
307 };
308
309 for current in offset..self.len - 1 {
310 let next_index = (self.head + current + 1) % self.capacity();
311 let current_index = (self.head + current) % self.capacity();
312 let next = self.entries.read(next_index);
313 self.entries.write(current_index, next);
314 }
315 self.len -= 1;
316 if self.len == 0 {
317 self.head = 0;
318 }
319 true
320 }
321}
322
323struct PacketHashSet {
333 buckets: Box<[u32]>,
335 active: usize,
337 len: usize,
338 key_seed: Option<u64>,
340 slot_mask: u32,
342 displacement_shift: u32,
345 max_displacement: u32,
346 tag_mask: u32,
349}
350
351const DISPLACEMENT_BITS: u32 = 4;
354
355impl PacketHashSet {
356 fn new(
357 max_entries: usize,
358 key_seed: Option<u64>,
359 allocation: PacketHashlistAllocation,
360 ) -> Self {
361 let slot_bits = usize::BITS - max_entries.leading_zeros();
362 let displacement_bits = DISPLACEMENT_BITS.min(32 - slot_bits.min(32));
363 let low_bits = slot_bits + displacement_bits;
364 let buckets = bucket_capacity(max_entries);
365 let active = match allocation {
366 PacketHashlistAllocation::Eager => buckets,
367 PacketHashlistAllocation::Lazy => buckets.min(INITIAL_BUCKETS),
368 };
369 Self {
370 buckets: vec![0; buckets].into_boxed_slice(),
373 active,
374 len: 0,
375 key_seed,
376 slot_mask: low_mask(slot_bits),
377 displacement_shift: slot_bits.min(31),
378 max_displacement: low_mask(displacement_bits),
379 tag_mask: !low_mask(low_bits),
380 }
381 }
382
383 fn key(&self, hash: &Key) -> u64 {
385 let leading = leading_bits(hash);
386 match self.key_seed {
387 None => leading,
388 Some(seed) => {
389 use core::hash::BuildHasher;
390 foldhash::fast::FixedState::with_seed(seed).hash_one(leading)
391 }
392 }
393 }
394
395 fn len(&self) -> usize {
396 self.len
397 }
398
399 #[inline]
400 fn tag(&self, key: u64) -> u32 {
401 (key >> 32) as u32 & self.tag_mask
402 }
403
404 #[inline]
405 fn entry_slot(&self, entry: u32) -> usize {
406 (entry & self.slot_mask) as usize - 1
407 }
408
409 #[inline]
410 fn entry(&self, tag: u32, displacement: usize, slot_plus_one: u32) -> u32 {
411 let displacement = displacement.min(self.max_displacement as usize) as u32;
412 tag | (displacement << self.displacement_shift) | slot_plus_one
413 }
414
415 #[inline]
416 fn mask(&self) -> usize {
417 self.active - 1
418 }
419
420 fn make_room(&mut self, queue: &PacketHashQueue) {
423 if self.active < self.buckets.len() && (self.len + 1) * 2 > self.active {
424 self.active *= 2;
425 self.rebuild(queue);
426 }
427 }
428
429 fn position(&self, hash: &Key, queue: &PacketHashQueue) -> Option<usize> {
430 if self.buckets.is_empty() {
431 return None;
432 }
433 let key = self.key(hash);
434 let tag = self.tag(key);
435 let mut idx = self.bucket_index(key);
436 loop {
437 let entry = self.buckets[idx];
438 if entry == 0 {
439 return None;
440 }
441 if entry & self.tag_mask == tag && queue.entries.get(self.entry_slot(entry)) == hash {
442 return Some(idx);
443 }
444 idx = (idx + 1) & self.mask();
445 }
446 }
447
448 fn contains(&self, hash: &Key, queue: &PacketHashQueue) -> bool {
449 self.position(hash, queue).is_some()
450 }
451
452 fn insert(&mut self, slot: usize, queue: &PacketHashQueue) {
453 let key = self.key(queue.entries.get(slot));
454 let home = self.bucket_index(key);
455 let mut idx = home;
456 while self.buckets[idx] != 0 {
457 idx = (idx + 1) & self.mask();
458 }
459 let displacement = idx.wrapping_sub(home) & self.mask();
460 self.buckets[idx] = self.entry(self.tag(key), displacement, slot as u32 + 1);
461 self.len += 1;
462 }
463
464 fn remove(&mut self, hash: &Key, queue: &PacketHashQueue) -> bool {
465 let Some(idx) = self.position(hash, queue) else {
466 return false;
467 };
468 self.buckets[idx] = 0;
469 self.len -= 1;
470 let mask = self.mask();
476 let mut hole = idx;
477 let mut next = (idx + 1) & mask;
478 while self.buckets[next] != 0 {
479 let entry = self.buckets[next];
480 let home = self.home(entry, next, queue);
481 let reachable_without_hole = if hole <= next {
482 hole < home && home <= next
483 } else {
484 hole < home || home <= next
485 };
486 if !reachable_without_hole {
487 let displacement = hole.wrapping_sub(home) & mask;
488 self.buckets[hole] =
489 self.entry(entry & self.tag_mask, displacement, entry & self.slot_mask);
490 self.buckets[next] = 0;
491 hole = next;
492 }
493 next = (next + 1) & mask;
494 }
495 true
496 }
497
498 fn rebuild(&mut self, queue: &PacketHashQueue) {
499 self.buckets[..self.active].fill(0);
500 self.len = 0;
501 for offset in 0..queue.len {
502 self.insert((queue.head + offset) % queue.capacity(), queue);
503 }
504 }
505
506 fn prefetch(&self, key: u64) {
507 if !self.buckets.is_empty() {
508 prefetch_read(&self.buckets[self.bucket_index(key)]);
509 }
510 }
511
512 #[inline]
513 fn bucket_index(&self, key: u64) -> usize {
514 debug_assert!(!self.buckets.is_empty());
515 (key as usize) & self.mask()
516 }
517
518 fn home(&self, entry: u32, index: usize, queue: &PacketHashQueue) -> usize {
521 let displacement = (entry >> self.displacement_shift) & self.max_displacement;
522 if displacement < self.max_displacement {
523 index.wrapping_sub(displacement as usize) & self.mask()
524 } else {
525 self.bucket_index(self.key(queue.entries.get(self.entry_slot(entry))))
526 }
527 }
528}
529
530#[inline(always)]
533fn prefetch_read(value: &u32) {
534 #[cfg(target_arch = "x86_64")]
535 unsafe {
538 core::arch::x86_64::_mm_prefetch::<{ core::arch::x86_64::_MM_HINT_T0 }>(
539 (value as *const u32).cast(),
540 );
541 }
542 #[cfg(target_arch = "aarch64")]
543 unsafe {
545 core::arch::asm!(
546 "prfm pldl1keep, [{0}]",
547 in(reg) value as *const u32,
548 options(nostack, readonly, preserves_flags)
549 );
550 }
551 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
552 let _ = value;
553}
554
555fn low_mask(bits: u32) -> u32 {
557 if bits >= 32 {
558 u32::MAX
559 } else {
560 (1u32 << bits) - 1
561 }
562}
563
564const MAX_ENTRIES: usize = u32::MAX as usize - 1;
566
567const INITIAL_BUCKETS: usize = 128;
569
570fn bucket_capacity(max_entries: usize) -> usize {
572 if max_entries == 0 {
573 return 0;
574 }
575 max_entries.saturating_mul(2).max(1).next_power_of_two()
576}
577
578fn leading_bits(hash: &Key) -> u64 {
582 let mut leading = [0u8; 8];
583 leading.copy_from_slice(&hash[..8]);
584 u64::from_le_bytes(leading)
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 fn stored(hash: [u8; 32]) -> [u8; 32] {
593 let mut out = [0u8; 32];
594 out[..16].copy_from_slice(&hash[..16]);
595 out
596 }
597
598 fn make_hash(seed: u8) -> [u8; 32] {
599 let mut h = [0u8; 32];
600 h[0] = seed;
601 h
602 }
603
604 fn seeded_policies() -> impl Iterator<Item = (PacketHashlistAllocation, Option<u64>)> {
606 policies()
607 .into_iter()
608 .flat_map(|policy| [(policy, None), (policy, Some(0x9e37_79b9_7f4a_7c15))])
609 }
610
611 #[test]
612 fn seeding_keeps_retained_hashes_and_fifo_order() {
613 for policy in policies() {
614 let mut list = PacketHashlist::with_allocation(16, policy);
615 let hashes: Vec<[u8; 32]> = (0..12).map(make_hash).collect();
616 for hash in &hashes {
617 list.add(*hash);
618 }
619 list.seed_hashing(0x0123_4567_89ab_cdef);
620 assert!(hashes.iter().all(|hash| list.is_duplicate(hash)));
621 assert!(list.iter().eq(hashes.iter().copied().map(stored)));
622 assert!(!list.is_duplicate(&make_hash(200)));
623 }
624 }
625
626 fn policies() -> [PacketHashlistAllocation; 2] {
627 [
628 PacketHashlistAllocation::Eager,
629 PacketHashlistAllocation::Lazy,
630 ]
631 }
632
633 #[test]
634 fn test_new_hash_not_duplicate() {
635 for policy in policies() {
636 let hl = PacketHashlist::with_allocation(100, policy);
637 assert!(!hl.is_duplicate(&make_hash(1)));
638 }
639 }
640
641 #[test]
642 fn test_added_hash_is_duplicate() {
643 for policy in policies() {
644 let mut hl = PacketHashlist::with_allocation(100, policy);
645 let h = make_hash(1);
646 hl.add(h);
647 assert!(hl.is_duplicate(&h));
648 }
649 }
650
651 #[test]
652 fn test_duplicate_insert_does_not_increase_len() {
653 for policy in policies() {
654 let mut hl = PacketHashlist::with_allocation(2, policy);
655 let h = make_hash(1);
656 hl.add(h);
657 hl.add(h);
658 assert_eq!(hl.len(), 1);
659 assert!(hl.is_duplicate(&h));
660 }
661 }
662
663 #[test]
664 fn test_full_hashlist_evicts_oldest_unique_hash() {
665 for policy in policies() {
666 let mut hl = PacketHashlist::with_allocation(3, policy);
667 let hashes = [make_hash(1), make_hash(2), make_hash(3), make_hash(4)];
668 for hash in hashes {
669 hl.add(hash);
670 }
671 assert!(!hl.is_duplicate(&hashes[0]));
672 assert!(hashes[1..].iter().all(|hash| hl.is_duplicate(hash)));
673 assert_eq!(hl.len(), 3);
674 }
675 }
676
677 #[test]
678 fn test_duplicate_does_not_refresh_recency() {
679 for policy in policies() {
680 let mut hl = PacketHashlist::with_allocation(2, policy);
681 let h1 = make_hash(1);
682 let h2 = make_hash(2);
683 let h3 = make_hash(3);
684 hl.add(h1);
685 hl.add(h2);
686 hl.add(h2);
687 hl.add(h3);
688 assert!(!hl.is_duplicate(&h1));
689 assert!(hl.is_duplicate(&h2));
690 assert!(hl.is_duplicate(&h3));
691 }
692 }
693
694 #[test]
695 fn removal_preserves_fifo_order_after_queue_wraps() {
696 for policy in policies() {
697 let mut hl = PacketHashlist::with_allocation(3, policy);
698 for seed in 1..=4 {
699 hl.add(make_hash(seed));
700 }
701
702 assert!(hl.remove(&make_hash(3)));
703 assert!(!hl.remove(&make_hash(1)));
704 assert_eq!(
705 hl.iter().collect::<Vec<_>>(),
706 [make_hash(2), make_hash(4)].map(stored).to_vec()
707 );
708
709 hl.add(make_hash(5));
710 assert_eq!(
711 hl.iter().collect::<Vec<_>>(),
712 [make_hash(2), make_hash(4), make_hash(5)]
713 .map(stored)
714 .to_vec()
715 );
716 }
717 }
718
719 #[test]
720 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
721 for policy in policies() {
722 let mut hl = PacketHashlist::with_allocation(3, policy);
723 for seed in 1..=9 {
724 hl.add(make_hash(seed));
725 }
726 assert_eq!(
727 hl.iter().collect::<Vec<_>>(),
728 [make_hash(7), make_hash(8), make_hash(9)]
729 .map(stored)
730 .to_vec()
731 );
732 }
733 }
734
735 #[test]
736 fn test_zero_capacity_hashlist_is_noop() {
737 for policy in policies() {
738 let mut hl = PacketHashlist::with_allocation(0, policy);
739 let h = make_hash(1);
740 hl.add(h);
741 assert_eq!(hl.len(), 0);
742 assert!(!hl.is_duplicate(&h));
743 assert_eq!(hl.iter().count(), 0);
744 }
745 }
746
747 #[test]
751 fn hashlist_matches_fifo_model_under_collisions() {
752 use alloc::collections::VecDeque;
753 for (policy, seed) in seeded_policies() {
754 for capacity in [1usize, 2, 3, 7, 16, 33] {
755 let mut list = PacketHashlist::with_allocation(capacity, policy);
756 if let Some(seed) = seed {
757 list.seed_hashing(seed);
758 }
759 let mut model: VecDeque<[u8; 32]> = VecDeque::new();
760 let mut rng = 0x2545_f491_4f6c_dd1du64 ^ capacity as u64;
761 for _ in 0..20_000 {
762 rng ^= rng << 13;
763 rng ^= rng >> 7;
764 rng ^= rng << 17;
765 let mut key = [0u8; 32];
766 key[0] = (rng % 5) as u8;
768 key[8] = (rng >> 8) as u8 % (capacity as u8 * 3 + 1);
769 match (rng >> 20) % 4 {
770 0 | 1 => {
771 list.add(key);
772 if !model.contains(&key) {
773 if model.len() == capacity {
774 model.pop_front();
775 }
776 model.push_back(key);
777 }
778 }
779 2 => {
780 let removed = list.remove(&key);
781 let position = model.iter().position(|k| *k == key);
782 assert_eq!(removed, position.is_some());
783 if let Some(position) = position {
784 model.remove(position);
785 }
786 }
787 _ => {}
788 }
789 assert_eq!(list.len(), model.len());
790 assert_eq!(list.is_duplicate(&key), model.contains(&key));
791 if rng.is_multiple_of(97) {
792 for retained in &model {
793 assert!(list.is_duplicate(retained));
794 }
795 assert!(list.iter().eq(model.iter().copied().map(stored)));
796 }
797 }
798 }
799 }
800 }
801
802 #[test]
803 fn collision_cluster_removal_preserves_remaining_entries() {
804 for policy in policies() {
805 let mut set = PacketHashSet::new(3, None, PacketHashlistAllocation::Eager);
806 let mut queue = PacketHashQueue::new(3, policy);
807 let mut colliding = Vec::new();
808 for seed in 0..=u8::MAX {
809 let hash = key_of(&make_hash(seed));
810 if leading_bits(&hash) & 7 == 0 {
811 colliding.push(hash);
812 if colliding.len() == 3 {
813 break;
814 }
815 }
816 }
817 assert_eq!(colliding.len(), 3);
818 for hash in &colliding {
819 queue.push_back(*hash);
820 set.insert(queue.len - 1, &queue);
821 }
822 assert!(set.remove(&colliding[0], &queue));
823 assert!(set.contains(&colliding[1], &queue));
824 assert!(set.contains(&colliding[2], &queue));
825 }
826 }
827
828 #[test]
829 fn raw_slots_read_only_after_write() {
830 for policy in policies() {
831 let mut slots = RawHashSlots::new(2, policy);
832 slots.write(1, key_of(&make_hash(42)));
833 assert_eq!(slots.read(1), key_of(&make_hash(42)));
834 }
835 }
836
837 #[test]
840 fn lazy_storage_grows_with_occupancy_not_retention_limit() {
841 let mut table = PacketHashlist::with_allocation(250_000, PacketHashlistAllocation::Lazy);
842 assert_eq!(table.set.active, 128);
843 for n in 0u64..1000 {
844 let mut hash = [0; 32];
845 hash[..8].copy_from_slice(&n.to_le_bytes());
846 table.add(hash);
847 }
848 assert_eq!(table.len(), 1000);
849 assert_eq!(table.set.active, 2048);
850 assert_eq!(table.max_size, 250_000);
851 }
852
853 #[test]
854 fn mixed_operations_match_fifo_model_through_growth_and_wraparound() {
855 use alloc::collections::VecDeque;
856 for (policy, seed) in seeded_policies() {
857 for limit in [0, 1, 3, 63, 64, 65, 127, 129, 257] {
858 let mut table = PacketHashlist::with_allocation(limit, policy);
859 if let Some(seed) = seed {
860 table.seed_hashing(seed);
861 }
862 let mut model = VecDeque::new();
863 let mut random = 0x123456789abcdefu64;
864 for step in 0..10_000 {
865 random ^= random << 13;
866 random ^= random >> 7;
867 random ^= random << 17;
868 let id = random % 400;
869 let mut hash = [0; 32];
870 hash[..8].copy_from_slice(&id.to_le_bytes());
871 if step % 5 == 0 {
872 let position = model.iter().position(|h| h == &hash);
873 assert_eq!(table.remove(&hash), position.is_some());
874 if let Some(position) = position {
875 model.remove(position);
876 }
877 } else {
878 table.add(hash);
879 if limit > 0 && !model.contains(&hash) {
880 if model.len() == limit {
881 model.pop_front();
882 }
883 model.push_back(hash);
884 }
885 }
886 assert_eq!(table.len(), model.len());
887 assert_eq!(table.is_duplicate(&hash), model.contains(&hash));
888 assert!(table.iter().eq(model.iter().copied().map(stored)));
889 if step % 100 == 0 {
890 for id in 0u64..400 {
891 let mut hash = [0; 32];
892 hash[..8].copy_from_slice(&id.to_le_bytes());
893 assert_eq!(table.is_duplicate(&hash), model.contains(&hash));
894 }
895 }
896 }
897 }
898 }
899 }
900
901 #[test]
902 fn test_sig_cache_insert_and_contains() {
903 let mut cache = AnnounceSignatureCache::new(100, 60.0);
904 let k = make_hash(1);
905 assert!(!cache.contains(&k));
906 cache.insert(k, 100.0);
907 assert!(cache.contains(&k));
908 assert_eq!(cache.len(), 1);
909 }
910
911 #[test]
912 fn test_sig_cache_duplicate_insert_is_noop() {
913 let mut cache = AnnounceSignatureCache::new(100, 60.0);
914 let k = make_hash(1);
915 cache.insert(k, 100.0);
916 cache.insert(k, 200.0);
917 assert_eq!(cache.len(), 1);
918 }
919
920 #[test]
921 fn test_sig_cache_ttl_expiry() {
922 let mut cache = AnnounceSignatureCache::new(100, 60.0);
923 cache.insert(make_hash(1), 100.0);
924 cache.insert(make_hash(2), 150.0);
925
926 assert_eq!(cache.cull(155.0), 0);
928 assert_eq!(cache.len(), 2);
929
930 assert_eq!(cache.cull(161.0), 1);
932 assert_eq!(cache.len(), 1);
933 assert!(!cache.contains(&make_hash(1)));
934 assert!(cache.contains(&make_hash(2)));
935 }
936
937 #[test]
938 fn test_sig_cache_capacity_eviction() {
939 let mut cache = AnnounceSignatureCache::new(2, 600.0);
940 cache.insert(make_hash(1), 100.0);
941 cache.insert(make_hash(2), 101.0);
942 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
945 assert!(!cache.contains(&make_hash(1)));
946 assert!(cache.contains(&make_hash(2)));
947 assert!(cache.contains(&make_hash(3)));
948 }
949
950 #[test]
951 fn test_sig_cache_disabled_when_zero_capacity() {
952 let mut cache = AnnounceSignatureCache::new(0, 60.0);
953 let k = make_hash(1);
954 cache.insert(k, 100.0);
955 assert!(!cache.contains(&k));
956 assert_eq!(cache.len(), 0);
957 assert_eq!(cache.cull(200.0), 0);
958 }
959}