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