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 max_size: usize,
18}
19
20impl PacketHashlist {
21 pub fn new(max_size: usize) -> Self {
22 Self::with_allocation(max_size, PacketHashlistAllocation::Eager)
23 }
24
25 pub fn with_allocation(max_size: usize, allocation: PacketHashlistAllocation) -> Self {
26 let max_size = max_size.min(MAX_ENTRIES);
28 let initial = if allocation == PacketHashlistAllocation::Eager {
29 max_size
30 } else {
31 max_size.min(64)
32 };
33 Self {
34 queue: PacketHashQueue::new(initial, allocation),
35 set: PacketHashSet::new(initial, None),
36 max_size,
37 }
38 }
39
40 pub fn seed_hashing(&mut self, seed: u64) {
44 self.set.key_seed = Some(seed);
45 if self.set.len > 0 {
48 self.set.rebuild(&self.queue);
49 }
50 }
51
52 pub(crate) fn hashing_seed(&self) -> Option<u64> {
53 self.set.key_seed
54 }
55
56 pub fn is_duplicate(&self, hash: &[u8; 32]) -> bool {
58 self.set.contains(hash, &self.queue)
59 }
60
61 pub fn prefetch(&self, hash: &[u8; 32]) {
65 self.set.prefetch(self.set.key(hash));
66 }
67
68 pub fn add(&mut self, hash: [u8; 32]) {
70 if self.max_size == 0 || self.set.contains(&hash, &self.queue) {
71 return;
72 }
73 if self.queue.len() == self.max_size {
74 let oldest = *self.queue.entries.get(self.queue.head);
75 let removed = self.set.remove(&oldest, &self.queue);
76 debug_assert!(removed, "oldest hash must exist in index");
77 self.queue.pop_front();
78 }
79 if self.queue.len() == self.queue.capacity() {
80 let capacity = self
81 .queue
82 .capacity()
83 .saturating_mul(2)
84 .max(1)
85 .min(self.max_size);
86 self.queue.grow(capacity);
87 self.set = PacketHashSet::new(capacity, self.set.key_seed);
88 self.set.rebuild(&self.queue);
89 }
90 let slot = (self.queue.head + self.queue.len) % self.queue.capacity();
91 self.queue.push_back(hash);
92 self.set.insert(slot, &self.queue);
93 if self.queue.len() == self.max_size {
94 let oldest = self.queue.entries.get(self.queue.head);
97 self.set.prefetch(self.set.key(oldest));
98 }
99 }
100
101 pub fn remove(&mut self, hash: &[u8; 32]) -> bool {
103 if !self.set.contains(hash, &self.queue) {
104 return false;
105 }
106 let removed = self.queue.remove(hash);
107 debug_assert!(removed, "indexed hash must exist in FIFO queue");
108 self.set.rebuild(&self.queue);
110 true
111 }
112
113 pub fn len(&self) -> usize {
115 debug_assert_eq!(self.queue.len(), self.set.len());
116 self.queue.len()
117 }
118
119 pub fn is_empty(&self) -> bool {
120 self.len() == 0
121 }
122
123 pub fn iter(&self) -> impl Iterator<Item = &[u8; 32]> {
125 (0..self.queue.len).map(|offset| {
126 let index = (self.queue.head + offset) % self.queue.capacity();
127 self.queue.entries.get(index)
128 })
129 }
130}
131
132struct RawHashSlots {
135 slots: Box<[MaybeUninit<[u8; 32]>]>,
136}
137
138impl RawHashSlots {
139 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
140 let mut slots = Box::<[[u8; 32]]>::new_uninit_slice(capacity);
141 if allocation == PacketHashlistAllocation::Eager {
142 for slot in &mut slots {
143 unsafe { core::ptr::write_volatile(slot.as_mut_ptr(), [0; 32]) };
146 }
147 }
148 Self { slots }
149 }
150
151 fn len(&self) -> usize {
152 self.slots.len()
153 }
154
155 fn write(&mut self, index: usize, hash: [u8; 32]) {
156 self.slots[index].write(hash);
157 }
158
159 fn read(&self, index: usize) -> [u8; 32] {
160 unsafe { self.slots[index].assume_init_read() }
163 }
164
165 fn get(&self, index: usize) -> &[u8; 32] {
166 unsafe { self.slots[index].assume_init_ref() }
169 }
170}
171
172pub struct AnnounceSignatureCache {
179 entries: BTreeMap<[u8; 32], f64>,
180 insertion_order: Vec<[u8; 32]>,
181 max_entries: usize,
182 ttl_secs: f64,
183}
184
185impl AnnounceSignatureCache {
186 pub fn new(max_entries: usize, ttl_secs: f64) -> Self {
187 Self {
188 entries: BTreeMap::new(),
189 insertion_order: Vec::new(),
190 max_entries,
191 ttl_secs,
192 }
193 }
194
195 pub fn contains(&self, key: &[u8; 32]) -> bool {
197 if self.max_entries == 0 {
198 return false;
199 }
200 self.entries.contains_key(key)
201 }
202
203 pub fn insert(&mut self, key: [u8; 32], now: f64) {
205 if self.max_entries == 0 {
206 return;
207 }
208 if self.entries.contains_key(&key) {
209 return;
210 }
211 while self.entries.len() >= self.max_entries {
213 if let Some(oldest) = self.insertion_order.first().copied() {
214 self.entries.remove(&oldest);
215 self.insertion_order.remove(0);
216 } else {
217 break;
218 }
219 }
220 self.entries.insert(key, now);
221 self.insertion_order.push(key);
222 }
223
224 pub fn cull(&mut self, now: f64) -> usize {
226 if self.max_entries == 0 {
227 return 0;
228 }
229 let cutoff = now - self.ttl_secs;
230 let before = self.entries.len();
231 self.entries.retain(|_, ts| *ts > cutoff);
232 self.insertion_order
233 .retain(|key| self.entries.contains_key(key));
234 before - self.entries.len()
235 }
236
237 pub fn len(&self) -> usize {
238 self.entries.len()
239 }
240
241 pub fn is_empty(&self) -> bool {
242 self.entries.is_empty()
243 }
244}
245
246struct PacketHashQueue {
247 entries: RawHashSlots,
248 head: usize,
249 len: usize,
250}
251
252impl PacketHashQueue {
253 fn new(capacity: usize, allocation: PacketHashlistAllocation) -> Self {
254 Self {
255 entries: RawHashSlots::new(capacity, allocation),
256 head: 0,
257 len: 0,
258 }
259 }
260
261 fn grow(&mut self, capacity: usize) {
262 debug_assert!(capacity > self.capacity());
263 let mut entries = RawHashSlots::new(capacity, PacketHashlistAllocation::Lazy);
264 for offset in 0..self.len {
265 let old = (self.head + offset) % self.capacity();
266 entries.write(offset, self.entries.read(old));
267 }
268 self.entries = entries;
269 self.head = 0;
270 }
271
272 fn capacity(&self) -> usize {
273 self.entries.len()
274 }
275
276 fn len(&self) -> usize {
277 self.len
278 }
279
280 fn push_back(&mut self, hash: [u8; 32]) {
281 debug_assert!(self.len < self.capacity());
282 if self.capacity() == 0 {
283 return;
284 }
285 let tail = (self.head + self.len) % self.capacity();
286 self.entries.write(tail, hash);
287 self.len += 1;
288 }
289
290 fn pop_front(&mut self) -> Option<[u8; 32]> {
291 if self.len == 0 || self.capacity() == 0 {
292 return None;
293 }
294 let hash = self.entries.read(self.head);
295 self.head = (self.head + 1) % self.capacity();
296 self.len -= 1;
297 if self.len == 0 {
298 self.head = 0;
299 }
300 Some(hash)
301 }
302
303 fn remove(&mut self, hash: &[u8; 32]) -> bool {
304 let Some(offset) = (0..self.len).find(|offset| {
305 let index = (self.head + offset) % self.capacity();
306 self.entries.get(index) == hash
307 }) else {
308 return false;
309 };
310
311 for current in offset..self.len - 1 {
312 let next_index = (self.head + current + 1) % self.capacity();
313 let current_index = (self.head + current) % self.capacity();
314 let next = self.entries.read(next_index);
315 self.entries.write(current_index, next);
316 }
317 self.len -= 1;
318 if self.len == 0 {
319 self.head = 0;
320 }
321 true
322 }
323}
324
325struct PacketHashSet {
333 buckets: Box<[u64]>,
334 len: usize,
335 key_seed: Option<u64>,
337}
338
339impl PacketHashSet {
340 fn new(max_entries: usize, key_seed: Option<u64>) -> Self {
341 Self {
342 buckets: vec![0; bucket_capacity(max_entries)].into_boxed_slice(),
343 len: 0,
344 key_seed,
345 }
346 }
347
348 fn key(&self, hash: &[u8; 32]) -> u64 {
350 let leading = leading_bits(hash);
351 match self.key_seed {
352 None => leading,
353 Some(seed) => {
354 use core::hash::BuildHasher;
355 foldhash::fast::FixedState::with_seed(seed).hash_one(leading)
356 }
357 }
358 }
359
360 fn len(&self) -> usize {
361 self.len
362 }
363
364 fn position(&self, hash: &[u8; 32], queue: &PacketHashQueue) -> Option<usize> {
365 if self.buckets.is_empty() {
366 return None;
367 }
368 let key = self.key(hash);
369 let tag = key as u32;
370 let mut idx = self.bucket_index(key);
371 loop {
372 let entry = self.buckets[idx];
373 if entry == 0 {
374 return None;
375 }
376 if bucket_tag(entry) == tag && queue.entries.get(bucket_slot(entry)) == hash {
377 return Some(idx);
378 }
379 idx = (idx + 1) & (self.buckets.len() - 1);
380 }
381 }
382
383 fn contains(&self, hash: &[u8; 32], queue: &PacketHashQueue) -> bool {
384 self.position(hash, queue).is_some()
385 }
386
387 fn insert(&mut self, slot: usize, queue: &PacketHashQueue) {
388 let key = self.key(queue.entries.get(slot));
389 let mut idx = self.bucket_index(key);
390 while self.buckets[idx] != 0 {
391 idx = (idx + 1) & (self.buckets.len() - 1);
392 }
393 self.buckets[idx] = (u64::from(key as u32) << 32) | (slot as u64 + 1);
394 self.len += 1;
395 }
396
397 fn remove(&mut self, hash: &[u8; 32], queue: &PacketHashQueue) -> bool {
398 let Some(idx) = self.position(hash, queue) else {
399 return false;
400 };
401 self.buckets[idx] = 0;
402 self.len -= 1;
403 let mask = self.buckets.len() - 1;
409 let mut hole = idx;
410 let mut next = (idx + 1) & mask;
411 while self.buckets[next] != 0 {
412 let home = self.home(self.buckets[next], queue);
413 let reachable_without_hole = if hole <= next {
414 hole < home && home <= next
415 } else {
416 hole < home || home <= next
417 };
418 if !reachable_without_hole {
419 self.buckets[hole] = self.buckets[next];
420 self.buckets[next] = 0;
421 hole = next;
422 }
423 next = (next + 1) & mask;
424 }
425 true
426 }
427
428 fn rebuild(&mut self, queue: &PacketHashQueue) {
429 self.buckets.fill(0);
430 self.len = 0;
431 for offset in 0..queue.len {
432 self.insert((queue.head + offset) % queue.capacity(), queue);
433 }
434 }
435
436 fn prefetch(&self, key: u64) {
437 if !self.buckets.is_empty() {
438 prefetch_read(&self.buckets[self.bucket_index(key)]);
439 }
440 }
441
442 fn bucket_index(&self, key: u64) -> usize {
443 debug_assert!(!self.buckets.is_empty());
444 (key as usize) & (self.buckets.len() - 1)
445 }
446
447 fn home(&self, entry: u64, queue: &PacketHashQueue) -> usize {
450 if self.buckets.len() as u64 <= 1 << 32 {
451 self.bucket_index(u64::from(bucket_tag(entry)))
452 } else {
453 self.bucket_index(self.key(queue.entries.get(bucket_slot(entry))))
454 }
455 }
456}
457
458#[inline(always)]
461fn prefetch_read(value: &u64) {
462 #[cfg(target_arch = "x86_64")]
463 unsafe {
466 core::arch::x86_64::_mm_prefetch::<{ core::arch::x86_64::_MM_HINT_T0 }>(
467 (value as *const u64).cast(),
468 );
469 }
470 #[cfg(target_arch = "aarch64")]
471 unsafe {
473 core::arch::asm!(
474 "prfm pldl1keep, [{0}]",
475 in(reg) value as *const u64,
476 options(nostack, readonly, preserves_flags)
477 );
478 }
479 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
480 let _ = value;
481}
482
483fn bucket_slot(entry: u64) -> usize {
484 (entry as u32) as usize - 1
485}
486
487fn bucket_tag(entry: u64) -> u32 {
488 (entry >> 32) as u32
489}
490
491const MAX_ENTRIES: usize = u32::MAX as usize - 1;
493
494fn bucket_capacity(max_entries: usize) -> usize {
495 if max_entries == 0 {
496 return 0;
497 }
498
499 let min_capacity = max_entries.saturating_mul(2).max(1);
500 min_capacity.next_power_of_two()
501}
502
503fn leading_bits(hash: &[u8; 32]) -> u64 {
507 let mut leading = [0u8; 8];
508 leading.copy_from_slice(&hash[..8]);
509 u64::from_le_bytes(leading)
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 fn make_hash(seed: u8) -> [u8; 32] {
517 let mut h = [0u8; 32];
518 h[0] = seed;
519 h
520 }
521
522 fn seeded_policies() -> impl Iterator<Item = (PacketHashlistAllocation, Option<u64>)> {
524 policies()
525 .into_iter()
526 .flat_map(|policy| [(policy, None), (policy, Some(0x9e37_79b9_7f4a_7c15))])
527 }
528
529 #[test]
530 fn seeding_keeps_retained_hashes_and_fifo_order() {
531 for policy in policies() {
532 let mut list = PacketHashlist::with_allocation(16, policy);
533 let hashes: Vec<[u8; 32]> = (0..12).map(make_hash).collect();
534 for hash in &hashes {
535 list.add(*hash);
536 }
537 list.seed_hashing(0x0123_4567_89ab_cdef);
538 assert!(hashes.iter().all(|hash| list.is_duplicate(hash)));
539 assert!(list.iter().eq(hashes.iter()));
540 assert!(!list.is_duplicate(&make_hash(200)));
541 }
542 }
543
544 fn policies() -> [PacketHashlistAllocation; 2] {
545 [
546 PacketHashlistAllocation::Eager,
547 PacketHashlistAllocation::Lazy,
548 ]
549 }
550
551 #[test]
552 fn test_new_hash_not_duplicate() {
553 for policy in policies() {
554 let hl = PacketHashlist::with_allocation(100, policy);
555 assert!(!hl.is_duplicate(&make_hash(1)));
556 }
557 }
558
559 #[test]
560 fn test_added_hash_is_duplicate() {
561 for policy in policies() {
562 let mut hl = PacketHashlist::with_allocation(100, policy);
563 let h = make_hash(1);
564 hl.add(h);
565 assert!(hl.is_duplicate(&h));
566 }
567 }
568
569 #[test]
570 fn test_duplicate_insert_does_not_increase_len() {
571 for policy in policies() {
572 let mut hl = PacketHashlist::with_allocation(2, policy);
573 let h = make_hash(1);
574 hl.add(h);
575 hl.add(h);
576 assert_eq!(hl.len(), 1);
577 assert!(hl.is_duplicate(&h));
578 }
579 }
580
581 #[test]
582 fn test_full_hashlist_evicts_oldest_unique_hash() {
583 for policy in policies() {
584 let mut hl = PacketHashlist::with_allocation(3, policy);
585 let hashes = [make_hash(1), make_hash(2), make_hash(3), make_hash(4)];
586 for hash in hashes {
587 hl.add(hash);
588 }
589 assert!(!hl.is_duplicate(&hashes[0]));
590 assert!(hashes[1..].iter().all(|hash| hl.is_duplicate(hash)));
591 assert_eq!(hl.len(), 3);
592 }
593 }
594
595 #[test]
596 fn test_duplicate_does_not_refresh_recency() {
597 for policy in policies() {
598 let mut hl = PacketHashlist::with_allocation(2, policy);
599 let h1 = make_hash(1);
600 let h2 = make_hash(2);
601 let h3 = make_hash(3);
602 hl.add(h1);
603 hl.add(h2);
604 hl.add(h2);
605 hl.add(h3);
606 assert!(!hl.is_duplicate(&h1));
607 assert!(hl.is_duplicate(&h2));
608 assert!(hl.is_duplicate(&h3));
609 }
610 }
611
612 #[test]
613 fn removal_preserves_fifo_order_after_queue_wraps() {
614 for policy in policies() {
615 let mut hl = PacketHashlist::with_allocation(3, policy);
616 for seed in 1..=4 {
617 hl.add(make_hash(seed));
618 }
619
620 assert!(hl.remove(&make_hash(3)));
621 assert!(!hl.remove(&make_hash(1)));
622 assert_eq!(
623 hl.iter().copied().collect::<Vec<_>>(),
624 vec![make_hash(2), make_hash(4)]
625 );
626
627 hl.add(make_hash(5));
628 assert_eq!(
629 hl.iter().copied().collect::<Vec<_>>(),
630 vec![make_hash(2), make_hash(4), make_hash(5)]
631 );
632 }
633 }
634
635 #[test]
636 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
637 for policy in policies() {
638 let mut hl = PacketHashlist::with_allocation(3, policy);
639 for seed in 1..=9 {
640 hl.add(make_hash(seed));
641 }
642 assert_eq!(
643 hl.iter().copied().collect::<Vec<_>>(),
644 vec![make_hash(7), make_hash(8), make_hash(9)]
645 );
646 }
647 }
648
649 #[test]
650 fn test_zero_capacity_hashlist_is_noop() {
651 for policy in policies() {
652 let mut hl = PacketHashlist::with_allocation(0, policy);
653 let h = make_hash(1);
654 hl.add(h);
655 assert_eq!(hl.len(), 0);
656 assert!(!hl.is_duplicate(&h));
657 assert_eq!(hl.iter().count(), 0);
658 }
659 }
660
661 #[test]
665 fn hashlist_matches_fifo_model_under_collisions() {
666 use alloc::collections::VecDeque;
667 for (policy, seed) in seeded_policies() {
668 for capacity in [1usize, 2, 3, 7, 16, 33] {
669 let mut list = PacketHashlist::with_allocation(capacity, policy);
670 if let Some(seed) = seed {
671 list.seed_hashing(seed);
672 }
673 let mut model: VecDeque<[u8; 32]> = VecDeque::new();
674 let mut rng = 0x2545_f491_4f6c_dd1du64 ^ capacity as u64;
675 for _ in 0..20_000 {
676 rng ^= rng << 13;
677 rng ^= rng >> 7;
678 rng ^= rng << 17;
679 let mut key = [0u8; 32];
680 key[0] = (rng % 5) as u8;
682 key[8] = (rng >> 8) as u8 % (capacity as u8 * 3 + 1);
683 match (rng >> 20) % 4 {
684 0 | 1 => {
685 list.add(key);
686 if !model.contains(&key) {
687 if model.len() == capacity {
688 model.pop_front();
689 }
690 model.push_back(key);
691 }
692 }
693 2 => {
694 let removed = list.remove(&key);
695 let position = model.iter().position(|k| *k == key);
696 assert_eq!(removed, position.is_some());
697 if let Some(position) = position {
698 model.remove(position);
699 }
700 }
701 _ => {}
702 }
703 assert_eq!(list.len(), model.len());
704 assert_eq!(list.is_duplicate(&key), model.contains(&key));
705 if rng.is_multiple_of(97) {
706 for retained in &model {
707 assert!(list.is_duplicate(retained));
708 }
709 assert!(list.iter().eq(model.iter()));
710 }
711 }
712 }
713 }
714 }
715
716 #[test]
717 fn collision_cluster_removal_preserves_remaining_entries() {
718 for policy in policies() {
719 let mut set = PacketHashSet::new(3, None);
720 let mut queue = PacketHashQueue::new(3, policy);
721 let mut colliding = Vec::new();
722 for seed in 0..=u8::MAX {
723 let hash = make_hash(seed);
724 if leading_bits(&hash) & 7 == 0 {
725 colliding.push(hash);
726 if colliding.len() == 3 {
727 break;
728 }
729 }
730 }
731 assert_eq!(colliding.len(), 3);
732 for hash in &colliding {
733 queue.push_back(*hash);
734 set.insert(queue.len - 1, &queue);
735 }
736 assert!(set.remove(&colliding[0], &queue));
737 assert!(set.contains(&colliding[1], &queue));
738 assert!(set.contains(&colliding[2], &queue));
739 }
740 }
741
742 #[test]
743 fn raw_slots_read_only_after_write() {
744 for policy in policies() {
745 let mut slots = RawHashSlots::new(2, policy);
746 slots.write(1, make_hash(42));
747 assert_eq!(slots.read(1), make_hash(42));
748 }
749 }
750
751 #[test]
754 fn lazy_storage_grows_with_occupancy_not_retention_limit() {
755 let mut table = PacketHashlist::with_allocation(250_000, PacketHashlistAllocation::Lazy);
756 assert_eq!(table.queue.capacity(), 64);
757 assert_eq!(table.set.buckets.len(), 128);
758 for n in 0u64..1000 {
759 let mut hash = [0; 32];
760 hash[..8].copy_from_slice(&n.to_le_bytes());
761 table.add(hash);
762 }
763 assert_eq!(table.len(), 1000);
764 assert_eq!(table.queue.capacity(), 1024);
765 assert_eq!(table.set.buckets.len(), 2048);
766 assert_eq!(table.max_size, 250_000);
767 }
768
769 #[test]
770 fn mixed_operations_match_fifo_model_through_growth_and_wraparound() {
771 use alloc::collections::VecDeque;
772 for (policy, seed) in seeded_policies() {
773 for limit in [0, 1, 3, 63, 64, 65, 127, 129, 257] {
774 let mut table = PacketHashlist::with_allocation(limit, policy);
775 if let Some(seed) = seed {
776 table.seed_hashing(seed);
777 }
778 let mut model = VecDeque::new();
779 let mut random = 0x123456789abcdefu64;
780 for step in 0..10_000 {
781 random ^= random << 13;
782 random ^= random >> 7;
783 random ^= random << 17;
784 let id = random % 400;
785 let mut hash = [0; 32];
786 hash[..8].copy_from_slice(&id.to_le_bytes());
787 if step % 5 == 0 {
788 let position = model.iter().position(|h| h == &hash);
789 assert_eq!(table.remove(&hash), position.is_some());
790 if let Some(position) = position {
791 model.remove(position);
792 }
793 } else {
794 table.add(hash);
795 if limit > 0 && !model.contains(&hash) {
796 if model.len() == limit {
797 model.pop_front();
798 }
799 model.push_back(hash);
800 }
801 }
802 assert_eq!(table.len(), model.len());
803 assert_eq!(table.is_duplicate(&hash), model.contains(&hash));
804 assert!(table.iter().eq(model.iter()));
805 if step % 100 == 0 {
806 for id in 0u64..400 {
807 let mut hash = [0; 32];
808 hash[..8].copy_from_slice(&id.to_le_bytes());
809 assert_eq!(table.is_duplicate(&hash), model.contains(&hash));
810 }
811 }
812 }
813 }
814 }
815 }
816
817 #[test]
818 fn test_sig_cache_insert_and_contains() {
819 let mut cache = AnnounceSignatureCache::new(100, 60.0);
820 let k = make_hash(1);
821 assert!(!cache.contains(&k));
822 cache.insert(k, 100.0);
823 assert!(cache.contains(&k));
824 assert_eq!(cache.len(), 1);
825 }
826
827 #[test]
828 fn test_sig_cache_duplicate_insert_is_noop() {
829 let mut cache = AnnounceSignatureCache::new(100, 60.0);
830 let k = make_hash(1);
831 cache.insert(k, 100.0);
832 cache.insert(k, 200.0);
833 assert_eq!(cache.len(), 1);
834 }
835
836 #[test]
837 fn test_sig_cache_ttl_expiry() {
838 let mut cache = AnnounceSignatureCache::new(100, 60.0);
839 cache.insert(make_hash(1), 100.0);
840 cache.insert(make_hash(2), 150.0);
841
842 assert_eq!(cache.cull(155.0), 0);
844 assert_eq!(cache.len(), 2);
845
846 assert_eq!(cache.cull(161.0), 1);
848 assert_eq!(cache.len(), 1);
849 assert!(!cache.contains(&make_hash(1)));
850 assert!(cache.contains(&make_hash(2)));
851 }
852
853 #[test]
854 fn test_sig_cache_capacity_eviction() {
855 let mut cache = AnnounceSignatureCache::new(2, 600.0);
856 cache.insert(make_hash(1), 100.0);
857 cache.insert(make_hash(2), 101.0);
858 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
861 assert!(!cache.contains(&make_hash(1)));
862 assert!(cache.contains(&make_hash(2)));
863 assert!(cache.contains(&make_hash(3)));
864 }
865
866 #[test]
867 fn test_sig_cache_disabled_when_zero_capacity() {
868 let mut cache = AnnounceSignatureCache::new(0, 60.0);
869 let k = make_hash(1);
870 cache.insert(k, 100.0);
871 assert!(!cache.contains(&k));
872 assert_eq!(cache.len(), 0);
873 assert_eq!(cache.cull(200.0), 0);
874 }
875}