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 Some(evicted) = self.queue.pop_front() else {
36 return;
37 };
38 let removed = self.set.remove(&evicted);
39 debug_assert!(removed, "evicted hash must exist in dedup set");
40 }
41
42 let inserted = self.set.insert(hash);
43 debug_assert!(inserted, "new hash must insert into dedup set");
44 self.queue.push_back(hash);
45 }
46
47 pub fn len(&self) -> usize {
49 debug_assert_eq!(self.queue.len(), self.set.len());
50 self.queue.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
54 self.len() == 0
55 }
56
57 pub fn iter(&self) -> impl Iterator<Item = &[u8; 32]> {
59 (0..self.queue.len).map(|offset| {
60 let index = (self.queue.head + offset) % self.queue.capacity();
61 &self.queue.entries[index]
62 })
63 }
64}
65
66pub struct AnnounceSignatureCache {
73 entries: BTreeMap<[u8; 32], f64>,
74 insertion_order: Vec<[u8; 32]>,
75 max_entries: usize,
76 ttl_secs: f64,
77}
78
79impl AnnounceSignatureCache {
80 pub fn new(max_entries: usize, ttl_secs: f64) -> Self {
81 Self {
82 entries: BTreeMap::new(),
83 insertion_order: Vec::new(),
84 max_entries,
85 ttl_secs,
86 }
87 }
88
89 pub fn contains(&self, key: &[u8; 32]) -> bool {
91 if self.max_entries == 0 {
92 return false;
93 }
94 self.entries.contains_key(key)
95 }
96
97 pub fn insert(&mut self, key: [u8; 32], now: f64) {
99 if self.max_entries == 0 {
100 return;
101 }
102 if self.entries.contains_key(&key) {
103 return;
104 }
105 while self.entries.len() >= self.max_entries {
107 if let Some(oldest) = self.insertion_order.first().copied() {
108 self.entries.remove(&oldest);
109 self.insertion_order.remove(0);
110 } else {
111 break;
112 }
113 }
114 self.entries.insert(key, now);
115 self.insertion_order.push(key);
116 }
117
118 pub fn cull(&mut self, now: f64) -> usize {
120 if self.max_entries == 0 {
121 return 0;
122 }
123 let cutoff = now - self.ttl_secs;
124 let before = self.entries.len();
125 self.entries.retain(|_, ts| *ts > cutoff);
126 self.insertion_order
127 .retain(|key| self.entries.contains_key(key));
128 before - self.entries.len()
129 }
130
131 pub fn len(&self) -> usize {
132 self.entries.len()
133 }
134
135 pub fn is_empty(&self) -> bool {
136 self.entries.is_empty()
137 }
138}
139
140struct PacketHashQueue {
141 entries: Vec<[u8; 32]>,
142 head: usize,
143 len: usize,
144}
145
146impl PacketHashQueue {
147 fn new(capacity: usize) -> Self {
148 Self {
149 entries: vec![[0u8; 32]; capacity],
150 head: 0,
151 len: 0,
152 }
153 }
154
155 fn capacity(&self) -> usize {
156 self.entries.len()
157 }
158
159 fn len(&self) -> usize {
160 self.len
161 }
162
163 fn push_back(&mut self, hash: [u8; 32]) {
164 debug_assert!(self.len < self.capacity());
165 if self.capacity() == 0 {
166 return;
167 }
168 let tail = (self.head + self.len) % self.capacity();
169 self.entries[tail] = hash;
170 self.len += 1;
171 }
172
173 fn pop_front(&mut self) -> Option<[u8; 32]> {
174 if self.len == 0 || self.capacity() == 0 {
175 return None;
176 }
177 let hash = self.entries[self.head];
178 self.head = (self.head + 1) % self.capacity();
179 self.len -= 1;
180 if self.len == 0 {
181 self.head = 0;
182 }
183 Some(hash)
184 }
185}
186
187struct PacketHashSet {
188 buckets: Vec<Option<[u8; 32]>>,
189 len: usize,
190}
191
192impl PacketHashSet {
193 fn new(max_entries: usize) -> Self {
194 Self {
195 buckets: vec![None; bucket_capacity(max_entries)],
196 len: 0,
197 }
198 }
199
200 fn len(&self) -> usize {
201 self.len
202 }
203
204 fn contains(&self, hash: &[u8; 32]) -> bool {
205 if self.buckets.is_empty() {
206 return false;
207 }
208
209 let mut idx = self.bucket_index(hash);
210 loop {
211 match self.buckets[idx] {
212 Some(entry) if &entry == hash => return true,
213 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
214 None => return false,
215 }
216 }
217 }
218
219 fn insert(&mut self, hash: [u8; 32]) -> bool {
220 if self.buckets.is_empty() {
221 return false;
222 }
223
224 let mut idx = self.bucket_index(&hash);
225 loop {
226 match self.buckets[idx] {
227 Some(entry) if entry == hash => return false,
228 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
229 None => {
230 self.buckets[idx] = Some(hash);
231 self.len += 1;
232 return true;
233 }
234 }
235 }
236 }
237
238 fn remove(&mut self, hash: &[u8; 32]) -> bool {
239 if self.buckets.is_empty() {
240 return false;
241 }
242
243 let mut idx = self.bucket_index(hash);
244 loop {
245 match self.buckets[idx] {
246 Some(entry) if &entry == hash => break,
247 Some(_) => idx = (idx + 1) & (self.buckets.len() - 1),
248 None => return false,
249 }
250 }
251
252 self.buckets[idx] = None;
253 self.len -= 1;
254
255 let mut next = (idx + 1) & (self.buckets.len() - 1);
256 while let Some(entry) = self.buckets[next].take() {
257 self.len -= 1;
258 let inserted = self.insert(entry);
259 debug_assert!(inserted, "cluster reinsert after removal must succeed");
260 next = (next + 1) & (self.buckets.len() - 1);
261 }
262
263 true
264 }
265
266 fn bucket_index(&self, hash: &[u8; 32]) -> usize {
267 debug_assert!(!self.buckets.is_empty());
268 (hash_bytes(hash) as usize) & (self.buckets.len() - 1)
269 }
270}
271
272fn bucket_capacity(max_entries: usize) -> usize {
273 if max_entries == 0 {
274 return 0;
275 }
276
277 let min_capacity = max_entries.saturating_mul(2).max(1);
278 min_capacity.next_power_of_two()
279}
280
281fn hash_bytes(hash: &[u8; 32]) -> u64 {
282 let mut state = 0xcbf29ce484222325u64;
283 for byte in hash {
284 state ^= u64::from(*byte);
285 state = state.wrapping_mul(0x100000001b3);
286 }
287 state
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 fn make_hash(seed: u8) -> [u8; 32] {
295 let mut h = [0u8; 32];
296 h[0] = seed;
297 h
298 }
299
300 #[test]
301 fn test_new_hash_not_duplicate() {
302 let hl = PacketHashlist::new(100);
303 assert!(!hl.is_duplicate(&make_hash(1)));
304 }
305
306 #[test]
307 fn test_added_hash_is_duplicate() {
308 let mut hl = PacketHashlist::new(100);
309 let h = make_hash(1);
310 hl.add(h);
311 assert!(hl.is_duplicate(&h));
312 }
313
314 #[test]
315 fn test_duplicate_insert_does_not_increase_len() {
316 let mut hl = PacketHashlist::new(2);
317 let h = make_hash(1);
318
319 hl.add(h);
320 hl.add(h);
321
322 assert_eq!(hl.len(), 1);
323 assert!(hl.is_duplicate(&h));
324 }
325
326 #[test]
327 fn test_full_hashlist_evicts_oldest_unique_hash() {
328 let mut hl = PacketHashlist::new(3);
329 let h1 = make_hash(1);
330 let h2 = make_hash(2);
331 let h3 = make_hash(3);
332 let h4 = make_hash(4);
333
334 hl.add(h1);
335 hl.add(h2);
336 hl.add(h3);
337 hl.add(h4);
338
339 assert!(!hl.is_duplicate(&h1));
340 assert!(hl.is_duplicate(&h2));
341 assert!(hl.is_duplicate(&h3));
342 assert!(hl.is_duplicate(&h4));
343 assert_eq!(hl.len(), 3);
344 }
345
346 #[test]
347 fn test_duplicate_does_not_refresh_recency() {
348 let mut hl = PacketHashlist::new(2);
349 let h1 = make_hash(1);
350 let h2 = make_hash(2);
351 let h3 = make_hash(3);
352
353 hl.add(h1);
354 hl.add(h2);
355 hl.add(h2);
356 hl.add(h3);
357
358 assert!(!hl.is_duplicate(&h1));
359 assert!(hl.is_duplicate(&h2));
360 assert!(hl.is_duplicate(&h3));
361 assert_eq!(hl.len(), 2);
362 }
363
364 #[test]
365 fn test_fifo_eviction_order_is_exact_across_multiple_inserts() {
366 let mut hl = PacketHashlist::new(3);
367 let h1 = make_hash(1);
368 let h2 = make_hash(2);
369 let h3 = make_hash(3);
370 let h4 = make_hash(4);
371 let h5 = make_hash(5);
372
373 hl.add(h1);
374 hl.add(h2);
375 hl.add(h3);
376 hl.add(h4);
377 hl.add(h5);
378
379 assert!(!hl.is_duplicate(&h1));
380 assert!(!hl.is_duplicate(&h2));
381 assert!(hl.is_duplicate(&h3));
382 assert!(hl.is_duplicate(&h4));
383 assert!(hl.is_duplicate(&h5));
384 assert_eq!(hl.len(), 3);
385 }
386
387 #[test]
388 fn test_zero_capacity_hashlist_is_noop() {
389 let mut hl = PacketHashlist::new(0);
390 let h = make_hash(1);
391
392 hl.add(h);
393
394 assert_eq!(hl.len(), 0);
395 assert!(!hl.is_duplicate(&h));
396 }
397
398 #[test]
401 fn test_sig_cache_insert_and_contains() {
402 let mut cache = AnnounceSignatureCache::new(100, 60.0);
403 let k = make_hash(1);
404 assert!(!cache.contains(&k));
405 cache.insert(k, 100.0);
406 assert!(cache.contains(&k));
407 assert_eq!(cache.len(), 1);
408 }
409
410 #[test]
411 fn test_sig_cache_duplicate_insert_is_noop() {
412 let mut cache = AnnounceSignatureCache::new(100, 60.0);
413 let k = make_hash(1);
414 cache.insert(k, 100.0);
415 cache.insert(k, 200.0);
416 assert_eq!(cache.len(), 1);
417 }
418
419 #[test]
420 fn test_sig_cache_ttl_expiry() {
421 let mut cache = AnnounceSignatureCache::new(100, 60.0);
422 cache.insert(make_hash(1), 100.0);
423 cache.insert(make_hash(2), 150.0);
424
425 assert_eq!(cache.cull(155.0), 0);
427 assert_eq!(cache.len(), 2);
428
429 assert_eq!(cache.cull(161.0), 1);
431 assert_eq!(cache.len(), 1);
432 assert!(!cache.contains(&make_hash(1)));
433 assert!(cache.contains(&make_hash(2)));
434 }
435
436 #[test]
437 fn test_sig_cache_capacity_eviction() {
438 let mut cache = AnnounceSignatureCache::new(2, 600.0);
439 cache.insert(make_hash(1), 100.0);
440 cache.insert(make_hash(2), 101.0);
441 cache.insert(make_hash(3), 102.0); assert_eq!(cache.len(), 2);
444 assert!(!cache.contains(&make_hash(1)));
445 assert!(cache.contains(&make_hash(2)));
446 assert!(cache.contains(&make_hash(3)));
447 }
448
449 #[test]
450 fn test_sig_cache_disabled_when_zero_capacity() {
451 let mut cache = AnnounceSignatureCache::new(0, 60.0);
452 let k = make_hash(1);
453 cache.insert(k, 100.0);
454 assert!(!cache.contains(&k));
455 assert_eq!(cache.len(), 0);
456 assert_eq!(cache.cull(200.0), 0);
457 }
458}