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