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 pub fn is_empty(&self) -> bool {
129 self.entries.is_empty()
130 }
131}
132
133struct PacketHashQueue {
134 entries: Vec<[u8; 32]>,
135 head: usize,
136 len: usize,
137}
138
139impl PacketHashQueue {
140 fn new(capacity: usize) -> Self {
141 Self {
142 entries: vec![[0u8; 32]; capacity],
143 head: 0,
144 len: 0,
145 }
146 }
147
148 fn capacity(&self) -> usize {
149 self.entries.len()
150 }
151
152 fn len(&self) -> usize {
153 self.len
154 }
155
156 fn push_back(&mut self, hash: [u8; 32]) {
157 debug_assert!(self.len < self.capacity());
158 if self.capacity() == 0 {
159 return;
160 }
161 let tail = (self.head + self.len) % self.capacity();
162 self.entries[tail] = hash;
163 self.len += 1;
164 }
165
166 fn pop_front(&mut self) -> Option<[u8; 32]> {
167 if self.len == 0 || self.capacity() == 0 {
168 return None;
169 }
170 let hash = self.entries[self.head];
171 self.head = (self.head + 1) % self.capacity();
172 self.len -= 1;
173 if self.len == 0 {
174 self.head = 0;
175 }
176 Some(hash)
177 }
178}
179
180struct PacketHashSet {
181 buckets: Vec<Option<[u8; 32]>>,
182 len: usize,
183}
184
185impl PacketHashSet {
186 fn new(max_entries: usize) -> Self {
187 Self {
188 buckets: vec![None; bucket_capacity(max_entries)],
189 len: 0,
190 }
191 }
192
193 fn len(&self) -> usize {
194 self.len
195 }
196
197 fn contains(&self, hash: &[u8; 32]) -> bool {
198 if self.buckets.is_empty() {
199 return false;
200 }
201
202 let mut idx = self.bucket_index(hash);
203 loop {
204 match self.buckets[idx] {
205 Some(entry) if &entry == hash => return true,
206 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
207 None => return false,
208 }
209 }
210 }
211
212 fn insert(&mut self, hash: [u8; 32]) -> bool {
213 if self.buckets.is_empty() {
214 return false;
215 }
216
217 let mut idx = self.bucket_index(&hash);
218 loop {
219 match self.buckets[idx] {
220 Some(entry) if entry == hash => return false,
221 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
222 None => {
223 self.buckets[idx] = Some(hash);
224 self.len += 1;
225 return true;
226 }
227 }
228 }
229 }
230
231 fn remove(&mut self, hash: &[u8; 32]) -> bool {
232 if self.buckets.is_empty() {
233 return false;
234 }
235
236 let mut idx = self.bucket_index(hash);
237 loop {
238 match self.buckets[idx] {
239 Some(entry) if &entry == hash => break,
240 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
241 None => return false,
242 }
243 }
244
245 self.buckets[idx] = None;
246 self.len -= 1;
247
248 let mut next = (idx + 1) & (self.buckets.len() - 1);
249 while let Some(entry) = self.buckets[next].take() {
250 self.len -= 1;
251 let inserted = self.insert(entry);
252 debug_assert!(inserted, "cluster reinsert after removal must succeed");
253 next = (next + 1) & (self.buckets.len() - 1);
254 }
255
256 true
257 }
258
259 fn bucket_index(&self, hash: &[u8; 32]) -> usize {
260 debug_assert!(!self.buckets.is_empty());
261 (hash_bytes(hash) as usize) & (self.buckets.len() - 1)
262 }
263}
264
265fn bucket_capacity(max_entries: usize) -> usize {
266 if max_entries == 0 {
267 return 0;
268 }
269
270 let min_capacity = max_entries.saturating_mul(2).max(1);
271 min_capacity.next_power_of_two()
272}
273
274fn hash_bytes(hash: &[u8; 32]) -> u64 {
275 let mut state = 0xcbf29ce484222325u64;
276 for byte in hash {
277 state ^= u64::from(*byte);
278 state = state.wrapping_mul(0x100000001b3);
279 }
280 state
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 fn make_hash(seed: u8) -> [u8; 32] {
288 let mut h = [0u8; 32];
289 h[0] = seed;
290 h
291 }
292
293 #[test]
294 fn test_new_hash_not_duplicate() {
295 let hl = PacketHashlist::new(100);
296 assert!(!hl.is_duplicate(&make_hash(1)));
297 }
298
299 #[test]
300 fn test_added_hash_is_duplicate() {
301 let mut hl = PacketHashlist::new(100);
302 let h = make_hash(1);
303 hl.add(h);
304 assert!(hl.is_duplicate(&h));
305 }
306
307 #[test]
308 fn test_duplicate_insert_does_not_increase_len() {
309 let mut hl = PacketHashlist::new(2);
310 let h = make_hash(1);
311
312 hl.add(h);
313 hl.add(h);
314
315 assert_eq!(hl.len(), 1);
316 assert!(hl.is_duplicate(&h));
317 }
318
319 #[test]
320 fn test_full_hashlist_evicts_oldest_unique_hash() {
321 let mut hl = PacketHashlist::new(3);
322 let h1 = make_hash(1);
323 let h2 = make_hash(2);
324 let h3 = make_hash(3);
325 let h4 = make_hash(4);
326
327 hl.add(h1);
328 hl.add(h2);
329 hl.add(h3);
330 hl.add(h4);
331
332 assert!(!hl.is_duplicate(&h1));
333 assert!(hl.is_duplicate(&h2));
334 assert!(hl.is_duplicate(&h3));
335 assert!(hl.is_duplicate(&h4));
336 assert_eq!(hl.len(), 3);
337 }
338
339 #[test]
340 fn test_duplicate_does_not_refresh_recency() {
341 let mut hl = PacketHashlist::new(2);
342 let h1 = make_hash(1);
343 let h2 = make_hash(2);
344 let h3 = make_hash(3);
345
346 hl.add(h1);
347 hl.add(h2);
348 hl.add(h2);
349 hl.add(h3);
350
351 assert!(!hl.is_duplicate(&h1));
352 assert!(hl.is_duplicate(&h2));
353 assert!(hl.is_duplicate(&h3));
354 assert_eq!(hl.len(), 2);
355 }
356
357 #[test]
358 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
359 let mut hl = PacketHashlist::new(3);
360 let h1 = make_hash(1);
361 let h2 = make_hash(2);
362 let h3 = make_hash(3);
363 let h4 = make_hash(4);
364 let h5 = make_hash(5);
365
366 hl.add(h1);
367 hl.add(h2);
368 hl.add(h3);
369 hl.add(h4);
370 hl.add(h5);
371
372 assert!(!hl.is_duplicate(&h1));
373 assert!(!hl.is_duplicate(&h2));
374 assert!(hl.is_duplicate(&h3));
375 assert!(hl.is_duplicate(&h4));
376 assert!(hl.is_duplicate(&h5));
377 assert_eq!(hl.len(), 3);
378 }
379
380 #[test]
381 fn test_zero_capacity_hashlist_is_noop() {
382 let mut hl = PacketHashlist::new(0);
383 let h = make_hash(1);
384
385 hl.add(h);
386
387 assert_eq!(hl.len(), 0);
388 assert!(!hl.is_duplicate(&h));
389 }
390
391 #[test]
394 fn test_sig_cache_insert_and_contains() {
395 let mut cache = AnnounceSignatureCache::new(100, 60.0);
396 let k = make_hash(1);
397 assert!(!cache.contains(&k));
398 cache.insert(k, 100.0);
399 assert!(cache.contains(&k));
400 assert_eq!(cache.len(), 1);
401 }
402
403 #[test]
404 fn test_sig_cache_duplicate_insert_is_noop() {
405 let mut cache = AnnounceSignatureCache::new(100, 60.0);
406 let k = make_hash(1);
407 cache.insert(k, 100.0);
408 cache.insert(k, 200.0);
409 assert_eq!(cache.len(), 1);
410 }
411
412 #[test]
413 fn test_sig_cache_ttl_expiry() {
414 let mut cache = AnnounceSignatureCache::new(100, 60.0);
415 cache.insert(make_hash(1), 100.0);
416 cache.insert(make_hash(2), 150.0);
417
418 assert_eq!(cache.cull(155.0), 0);
420 assert_eq!(cache.len(), 2);
421
422 assert_eq!(cache.cull(161.0), 1);
424 assert_eq!(cache.len(), 1);
425 assert!(!cache.contains(&make_hash(1)));
426 assert!(cache.contains(&make_hash(2)));
427 }
428
429 #[test]
430 fn test_sig_cache_capacity_eviction() {
431 let mut cache = AnnounceSignatureCache::new(2, 600.0);
432 cache.insert(make_hash(1), 100.0);
433 cache.insert(make_hash(2), 101.0);
434 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
437 assert!(!cache.contains(&make_hash(1)));
438 assert!(cache.contains(&make_hash(2)));
439 assert!(cache.contains(&make_hash(3)));
440 }
441
442 #[test]
443 fn test_sig_cache_disabled_when_zero_capacity() {
444 let mut cache = AnnounceSignatureCache::new(0, 60.0);
445 let k = make_hash(1);
446 cache.insert(k, 100.0);
447 assert!(!cache.contains(&k));
448 assert_eq!(cache.len(), 0);
449 assert_eq!(cache.cull(200.0), 0);
450 }
451}