1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_possible_wrap,
4 clippy::cast_sign_loss,
5 clippy::unchecked_time_subtraction,
6 reason = "M175: routing table — node arithmetic and time deltas use post-bootstrap Instants; remaining unchecked-time sites are test fixtures"
7)]
8
9use std::collections::HashSet;
16use std::net::{IpAddr, SocketAddr};
17use std::time::{Duration, Instant};
18
19use irontide_core::Id20;
20
21pub const K: usize = 8;
23
24const MAX_BUCKETS: usize = 160;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum NodeStatus {
30 Good,
32 Questionable,
34 Bad,
36}
37
38const ACTIVE_THRESHOLD: Duration = Duration::from_mins(15);
40
41#[derive(Debug, Clone)]
43pub struct RoutingNode {
44 pub id: Id20,
46 pub addr: SocketAddr,
48 pub last_seen: Instant,
50 pub fail_count: u32,
52 pub last_response: Option<Instant>,
54 pub last_query: Option<Instant>,
56}
57
58impl RoutingNode {
59 #[must_use]
72 pub fn status(&self) -> NodeStatus {
73 if self.fail_count >= 2 {
74 return NodeStatus::Bad;
75 }
76 let now = Instant::now();
77 let recent = |t: Instant| now.duration_since(t) <= ACTIVE_THRESHOLD;
78 let active = self.last_response.is_some_and(recent) || self.last_query.is_some_and(recent);
79 if active {
80 NodeStatus::Good
81 } else {
82 NodeStatus::Questionable
83 }
84 }
85}
86
87#[derive(Debug, Clone)]
89struct KBucket {
90 nodes: Vec<RoutingNode>,
91}
92
93impl KBucket {
94 fn new() -> Self {
95 Self {
96 nodes: Vec::with_capacity(K),
97 }
98 }
99
100 fn is_full(&self) -> bool {
101 self.nodes.len() >= K
102 }
103
104 fn find(&self, id: &Id20) -> Option<usize> {
105 self.nodes.iter().position(|n| n.id == *id)
106 }
107
108 fn worst_node(&self) -> Option<usize> {
110 self.nodes
111 .iter()
112 .enumerate()
113 .max_by(|(_, a), (_, b)| {
114 a.fail_count
115 .cmp(&b.fail_count)
116 .then(b.last_seen.cmp(&a.last_seen))
117 })
118 .map(|(i, _)| i)
119 }
120}
121
122const DEFAULT_MAX_NODES: usize = 512;
125
126#[derive(Debug, Clone)]
128pub struct RoutingTable {
129 own_id: Id20,
130 buckets: Vec<KBucket>,
131 ip_set: HashSet<IpAddr>,
133 restrict_ips: bool,
135 max_nodes: usize,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub enum InsertResult {
142 Inserted,
144 BucketFull,
146 Rejected,
148}
149
150impl RoutingTable {
151 #[must_use]
153 pub fn new(own_id: Id20) -> Self {
154 Self::with_config(own_id, false, DEFAULT_MAX_NODES)
155 }
156
157 #[must_use]
159 pub fn new_with_config(own_id: Id20, restrict_ips: bool) -> Self {
160 Self::with_config(own_id, restrict_ips, DEFAULT_MAX_NODES)
161 }
162
163 #[must_use]
165 pub fn with_config(own_id: Id20, restrict_ips: bool, max_nodes: usize) -> Self {
166 Self {
167 own_id,
168 buckets: vec![KBucket::new()],
169 ip_set: HashSet::new(),
170 restrict_ips,
171 max_nodes,
172 }
173 }
174
175 #[must_use]
177 pub fn own_id(&self) -> &Id20 {
178 &self.own_id
179 }
180
181 #[must_use]
183 pub fn len(&self) -> usize {
184 self.buckets.iter().map(|b| b.nodes.len()).sum()
185 }
186
187 #[must_use]
189 pub fn is_empty(&self) -> bool {
190 self.len() == 0
191 }
192
193 #[must_use]
195 pub fn bucket_count(&self) -> usize {
196 self.buckets.len()
197 }
198
199 pub fn insert(&mut self, id: Id20, addr: SocketAddr) -> bool {
201 if id == self.own_id {
202 return false; }
204 self.insert_inner(id, addr)
205 }
206
207 fn insert_inner(&mut self, id: Id20, addr: SocketAddr) -> bool {
208 let ip = addr.ip();
209
210 if self.restrict_ips && self.ip_set.contains(&ip) {
212 let bucket_idx = self.bucket_index(&id);
213 if self.buckets[bucket_idx].find(&id).is_none() {
214 return false; }
216 }
217
218 let bucket_idx = self.bucket_index(&id);
219
220 if let Some(pos) = self.buckets[bucket_idx].find(&id) {
222 let old_ip = self.buckets[bucket_idx].nodes[pos].addr.ip();
223 self.buckets[bucket_idx].nodes[pos].last_seen = Instant::now();
224 self.buckets[bucket_idx].nodes[pos].addr = addr;
225 self.buckets[bucket_idx].nodes[pos].fail_count = 0;
226 if self.restrict_ips && old_ip != ip {
228 self.ip_set.remove(&old_ip);
229 self.ip_set.insert(ip);
230 }
231 return true;
232 }
233
234 let at_cap = self.len() >= self.max_nodes;
238 if at_cap {
239 if let Some(worst_idx) = self.buckets[bucket_idx].worst_node()
240 && self.buckets[bucket_idx].nodes[worst_idx].fail_count > 0
241 {
242 if self.restrict_ips {
243 self.ip_set
244 .remove(&self.buckets[bucket_idx].nodes[worst_idx].addr.ip());
245 }
246 self.buckets[bucket_idx].nodes[worst_idx] = RoutingNode {
247 id,
248 addr,
249 last_seen: Instant::now(),
250 fail_count: 0,
251 last_response: None,
252 last_query: None,
253 };
254 if self.restrict_ips {
255 self.ip_set.insert(ip);
256 }
257 return true;
258 }
259 return false;
261 }
262
263 if !self.buckets[bucket_idx].is_full() {
265 self.buckets[bucket_idx].nodes.push(RoutingNode {
266 id,
267 addr,
268 last_seen: Instant::now(),
269 fail_count: 0,
270 last_response: None,
271 last_query: None,
272 });
273 if self.restrict_ips {
274 self.ip_set.insert(ip);
275 }
276 return true;
277 }
278
279 if let Some(worst_idx) = self.buckets[bucket_idx].worst_node()
281 && self.buckets[bucket_idx].nodes[worst_idx].fail_count > 0
282 {
283 if self.restrict_ips {
285 self.ip_set
286 .remove(&self.buckets[bucket_idx].nodes[worst_idx].addr.ip());
287 }
288 self.buckets[bucket_idx].nodes[worst_idx] = RoutingNode {
289 id,
290 addr,
291 last_seen: Instant::now(),
292 fail_count: 0,
293 last_response: None,
294 last_query: None,
295 };
296 if self.restrict_ips {
297 self.ip_set.insert(ip);
298 }
299 return true;
300 }
301
302 if self.can_split(bucket_idx) {
304 self.split(bucket_idx);
305 return self.insert_inner(id, addr);
307 }
308
309 false
310 }
311
312 pub fn remove(&mut self, id: &Id20) -> bool {
314 let bucket_idx = self.bucket_index(id);
315 let bucket = &mut self.buckets[bucket_idx];
316 if let Some(pos) = bucket.find(id) {
317 if self.restrict_ips {
318 self.ip_set.remove(&bucket.nodes[pos].addr.ip());
319 }
320 bucket.nodes.remove(pos);
321 true
322 } else {
323 false
324 }
325 }
326
327 pub fn mark_seen(&mut self, id: &Id20) {
329 let bucket_idx = self.bucket_index(id);
330 if let Some(pos) = self.buckets[bucket_idx].find(id) {
331 self.buckets[bucket_idx].nodes[pos].last_seen = Instant::now();
332 self.buckets[bucket_idx].nodes[pos].fail_count = 0;
333 }
334 }
335
336 pub fn mark_failed(&mut self, id: &Id20) {
338 let bucket_idx = self.bucket_index(id);
339 if let Some(pos) = self.buckets[bucket_idx].find(id) {
340 self.buckets[bucket_idx].nodes[pos].fail_count += 1;
341 }
342 }
343
344 #[must_use]
346 pub fn closest(&self, target: &Id20, count: usize) -> Vec<RoutingNode> {
347 let mut all: Vec<&RoutingNode> = self.buckets.iter().flat_map(|b| &b.nodes).collect();
348 all.sort_by_key(|n| n.id.xor_distance(target));
349 all.into_iter().take(count).cloned().collect()
350 }
351
352 #[must_use]
358 pub fn stale_buckets(&self, max_age: std::time::Duration) -> Vec<usize> {
359 let now = Instant::now();
360 self.buckets
361 .iter()
362 .enumerate()
363 .filter(|(_, b)| {
364 b.nodes.is_empty()
365 || b.nodes
366 .iter()
367 .all(|n| now.duration_since(n.last_seen) > max_age)
368 })
369 .map(|(i, _)| i)
370 .collect()
371 }
372
373 #[must_use]
376 pub fn random_id_in_bucket(&self, bucket_idx: usize) -> Id20 {
377 let mut id = self.own_id;
378 if bucket_idx < MAX_BUCKETS {
381 let byte_idx = bucket_idx / 8;
382 let bit_idx = 7 - (bucket_idx % 8);
383 id.0[byte_idx] ^= 1 << bit_idx;
384 }
385 id
386 }
387
388 #[must_use]
390 pub fn all_nodes(&self) -> Vec<(Id20, SocketAddr)> {
391 self.buckets
392 .iter()
393 .flat_map(|b| b.nodes.iter().map(|n| (n.id, n.addr)))
394 .collect()
395 }
396
397 #[must_use]
399 pub fn get(&self, id: &Id20) -> Option<&RoutingNode> {
400 let bucket_idx = self.bucket_index(id);
401 self.buckets[bucket_idx]
402 .find(id)
403 .map(|pos| &self.buckets[bucket_idx].nodes[pos])
404 }
405
406 pub fn get_mut(&mut self, id: &Id20) -> Option<&mut RoutingNode> {
408 let bucket_idx = self.bucket_index(id);
409 let pos = self.buckets[bucket_idx].find(id)?;
410 Some(&mut self.buckets[bucket_idx].nodes[pos])
411 }
412
413 pub fn mark_response(&mut self, id: &Id20) {
415 let bucket_idx = self.bucket_index(id);
416 if let Some(pos) = self.buckets[bucket_idx].find(id) {
417 self.buckets[bucket_idx].nodes[pos].last_response = Some(Instant::now());
418 self.buckets[bucket_idx].nodes[pos].fail_count = 0;
419 }
420 }
421
422 pub fn mark_query(&mut self, id: &Id20) {
424 let bucket_idx = self.bucket_index(id);
425 if let Some(pos) = self.buckets[bucket_idx].find(id) {
426 self.buckets[bucket_idx].nodes[pos].last_query = Some(Instant::now());
427 }
428 }
429
430 pub fn mark_all_questionable(&mut self) {
437 for bucket in &mut self.buckets {
438 for node in &mut bucket.nodes {
439 node.last_response = None;
440 node.last_query = None;
441 node.fail_count = 0;
442 }
443 }
444 }
445
446 #[must_use]
448 pub fn questionable_nodes(&self) -> Vec<(Id20, SocketAddr)> {
449 self.buckets
450 .iter()
451 .flat_map(|b| {
452 b.nodes
453 .iter()
454 .filter(|n| n.status() == NodeStatus::Questionable)
455 .map(|n| (n.id, n.addr))
456 })
457 .collect()
458 }
459
460 fn bucket_index(&self, id: &Id20) -> usize {
468 let distance = self.own_id.xor_distance(id);
469 let leading_zeros = leading_zeros_160(&distance);
470 leading_zeros.min(self.buckets.len() - 1)
472 }
473
474 fn can_split(&self, idx: usize) -> bool {
477 idx == self.buckets.len() - 1 && self.buckets.len() < MAX_BUCKETS
478 }
479
480 fn split(&mut self, idx: usize) {
482 debug_assert!(idx == self.buckets.len() - 1);
483 let old_bucket = &mut self.buckets[idx];
484 let nodes = std::mem::take(&mut old_bucket.nodes);
485
486 let mut near = KBucket::new();
487 let mut far = KBucket::new();
488
489 let new_depth = self.buckets.len(); for node in nodes {
492 let distance = self.own_id.xor_distance(&node.id);
493 let lz = leading_zeros_160(&distance);
494 if lz >= new_depth {
495 near.nodes.push(node);
496 } else {
497 far.nodes.push(node);
498 }
499 }
500
501 self.buckets[idx] = far;
503 self.buckets.push(near);
504 }
505}
506
507fn leading_zeros_160(id: &Id20) -> usize {
509 let mut zeros = 0;
510 for &byte in id.as_bytes() {
511 if byte == 0 {
512 zeros += 8;
513 } else {
514 zeros += byte.leading_zeros() as usize;
515 break;
516 }
517 }
518 zeros
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use pretty_assertions::assert_eq;
525
526 fn id(byte: u8) -> Id20 {
527 let mut bytes = [0u8; 20];
528 bytes[19] = byte;
529 Id20(bytes)
530 }
531
532 fn addr(port: u16) -> SocketAddr {
533 format!("10.0.0.1:{port}").parse().unwrap()
534 }
535
536 #[test]
537 fn insert_and_retrieve() {
538 let mut rt = RoutingTable::new(Id20::ZERO);
539 assert!(rt.insert(id(1), addr(1)));
540 assert_eq!(rt.len(), 1);
541 assert!(rt.get(&id(1)).is_some());
542 }
543
544 #[test]
550 fn routing_node_status_does_not_panic_on_fresh_process() {
551 let now = Instant::now();
552 let node = RoutingNode {
553 id: id(1),
554 addr: addr(1),
555 last_seen: now,
556 fail_count: 0,
557 last_response: Some(now),
558 last_query: None,
559 };
560 assert_eq!(node.status(), NodeStatus::Good);
562
563 let stale = RoutingNode {
565 id: id(2),
566 addr: addr(2),
567 last_seen: now,
568 fail_count: 0,
569 last_response: None,
570 last_query: None,
571 };
572 assert_eq!(stale.status(), NodeStatus::Questionable);
573 }
574
575 #[test]
579 fn stale_buckets_does_not_panic_on_large_max_age() {
580 let rt = RoutingTable::new(Id20::ZERO);
581 let _ = rt.stale_buckets(std::time::Duration::from_hours(24));
583 }
584
585 #[test]
586 fn insert_self_rejected() {
587 let mut rt = RoutingTable::new(Id20::ZERO);
588 assert!(!rt.insert(Id20::ZERO, addr(1)));
589 assert_eq!(rt.len(), 0);
590 }
591
592 #[test]
593 fn update_existing_node() {
594 let mut rt = RoutingTable::new(Id20::ZERO);
595 rt.insert(id(1), addr(1));
596 rt.insert(id(1), addr(2)); assert_eq!(rt.len(), 1);
598 assert_eq!(rt.get(&id(1)).unwrap().addr, addr(2));
599 }
600
601 #[test]
602 fn remove_node() {
603 let mut rt = RoutingTable::new(Id20::ZERO);
604 rt.insert(id(1), addr(1));
605 assert!(rt.remove(&id(1)));
606 assert_eq!(rt.len(), 0);
607 assert!(!rt.remove(&id(1))); }
609
610 #[test]
611 fn closest_nodes_sorted() {
612 let mut rt = RoutingTable::new(Id20::ZERO);
613 rt.insert(id(1), addr(1));
614 rt.insert(id(5), addr(5));
615 rt.insert(id(3), addr(3));
616 rt.insert(id(10), addr(10));
617
618 let closest = rt.closest(&Id20::ZERO, 3);
619 assert_eq!(closest.len(), 3);
620 assert_eq!(closest[0].id, id(1));
622 assert_eq!(closest[1].id, id(3));
623 assert_eq!(closest[2].id, id(5));
624 }
625
626 #[test]
627 fn closest_fewer_than_count() {
628 let mut rt = RoutingTable::new(Id20::ZERO);
629 rt.insert(id(1), addr(1));
630 let closest = rt.closest(&Id20::ZERO, 10);
631 assert_eq!(closest.len(), 1);
632 }
633
634 #[test]
635 fn bucket_splitting() {
636 let mut rt = RoutingTable::new(Id20::ZERO);
637 for i in 1..=9u8 {
639 rt.insert(id(i), addr(u16::from(i)));
640 }
641 assert!(rt.bucket_count() > 1);
642 assert_eq!(rt.len(), 9);
643 }
644
645 #[test]
646 fn evict_failed_node() {
647 let mut rt = RoutingTable::new(Id20::ZERO);
648 for i in 1..=K as u8 {
650 rt.insert(id(i), addr(u16::from(i)));
651 }
652 assert_eq!(rt.len(), K);
653
654 rt.mark_failed(&id(1));
656 rt.mark_failed(&id(1));
657
658 let new_id = id(100);
660 assert!(rt.insert(new_id, addr(100)));
661 assert!(rt.get(&new_id).is_some());
662 }
663
664 #[test]
665 fn mark_seen_resets_fail_count() {
666 let mut rt = RoutingTable::new(Id20::ZERO);
667 rt.insert(id(1), addr(1));
668 rt.mark_failed(&id(1));
669 assert_eq!(rt.get(&id(1)).unwrap().fail_count, 1);
670 rt.mark_seen(&id(1));
671 assert_eq!(rt.get(&id(1)).unwrap().fail_count, 0);
672 }
673
674 #[test]
675 fn stale_buckets_detection() {
676 let mut rt = RoutingTable::new(Id20::ZERO);
677 let stale = rt.stale_buckets(std::time::Duration::from_mins(15));
679 assert_eq!(stale.len(), 1);
680
681 rt.insert(id(1), addr(1));
683 let stale = rt.stale_buckets(std::time::Duration::from_mins(15));
684 assert!(stale.is_empty());
685 }
686
687 #[test]
688 fn leading_zeros_correct() {
689 assert_eq!(leading_zeros_160(&Id20::ZERO), 160);
690 assert_eq!(leading_zeros_160(&id(1)), 159);
691 assert_eq!(leading_zeros_160(&id(128)), 152);
692 let mut full = [0xFFu8; 20];
693 assert_eq!(leading_zeros_160(&Id20(full)), 0);
694 full[0] = 0x01;
695 full[1..].fill(0);
696 assert_eq!(leading_zeros_160(&Id20(full)), 7);
697 }
698
699 #[test]
700 fn random_id_in_bucket_differs() {
701 let rt = RoutingTable::new(Id20::ZERO);
702 let rand_id = rt.random_id_in_bucket(0);
703 assert_ne!(rand_id, Id20::ZERO);
704 }
705
706 #[test]
709 fn restrict_ips_rejects_second_node_same_ip() {
710 let mut rt = RoutingTable::new_with_config(Id20::ZERO, true);
711 let ip_addr: SocketAddr = "10.0.0.1:6881".parse().unwrap();
712 assert!(rt.insert(id(1), ip_addr));
713 let ip_addr2: SocketAddr = "10.0.0.1:6882".parse().unwrap();
715 assert!(!rt.insert(id(2), ip_addr2));
716 assert_eq!(rt.len(), 1);
717 }
718
719 #[test]
720 fn restrict_ips_allows_same_node_update() {
721 let mut rt = RoutingTable::new_with_config(Id20::ZERO, true);
722 let addr1: SocketAddr = "10.0.0.1:6881".parse().unwrap();
723 let addr2: SocketAddr = "10.0.0.1:6882".parse().unwrap();
724 assert!(rt.insert(id(1), addr1));
725 assert!(rt.insert(id(1), addr2));
727 assert_eq!(rt.len(), 1);
728 assert_eq!(rt.get(&id(1)).unwrap().addr, addr2);
729 }
730
731 #[test]
732 fn no_restrict_ips_allows_multiple_nodes_same_ip() {
733 let mut rt = RoutingTable::new_with_config(Id20::ZERO, false);
734 let addr1: SocketAddr = "10.0.0.1:6881".parse().unwrap();
735 let addr2: SocketAddr = "10.0.0.1:6882".parse().unwrap();
736 assert!(rt.insert(id(1), addr1));
737 assert!(rt.insert(id(2), addr2));
738 assert_eq!(rt.len(), 2);
739 }
740
741 #[test]
742 fn restrict_ips_remove_frees_ip_slot() {
743 let mut rt = RoutingTable::new_with_config(Id20::ZERO, true);
744 let addr: SocketAddr = "10.0.0.1:6881".parse().unwrap();
745 assert!(rt.insert(id(1), addr));
746 assert!(rt.remove(&id(1)));
747 assert!(rt.insert(id(2), addr));
749 assert_eq!(rt.len(), 1);
750 }
751
752 #[test]
755 fn node_status_bad_on_two_failures() {
756 let mut rt = RoutingTable::new(Id20::ZERO);
757 rt.insert(id(1), addr(1));
758 rt.mark_failed(&id(1));
759 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Questionable);
760 rt.mark_failed(&id(1));
761 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Bad);
762 }
763
764 #[test]
765 fn node_status_good_after_mark_response() {
766 let mut rt = RoutingTable::new(Id20::ZERO);
767 rt.insert(id(1), addr(1));
768 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Questionable);
770 rt.mark_response(&id(1));
771 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Good);
772 }
773
774 #[test]
775 fn node_status_good_after_mark_query() {
776 let mut rt = RoutingTable::new(Id20::ZERO);
777 rt.insert(id(1), addr(1));
778 rt.mark_query(&id(1));
779 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Good);
780 }
781
782 #[test]
783 fn mark_response_resets_fail_count() {
784 let mut rt = RoutingTable::new(Id20::ZERO);
785 rt.insert(id(1), addr(1));
786 rt.mark_failed(&id(1));
787 rt.mark_failed(&id(1));
788 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Bad);
789 rt.mark_response(&id(1));
790 assert_eq!(rt.get(&id(1)).unwrap().fail_count, 0);
791 assert_eq!(rt.get(&id(1)).unwrap().status(), NodeStatus::Good);
792 }
793
794 #[test]
795 fn mark_response_noop_for_unknown_node() {
796 let mut rt = RoutingTable::new(Id20::ZERO);
797 rt.mark_response(&id(42));
799 }
800
801 #[test]
802 fn mark_query_noop_for_unknown_node() {
803 let mut rt = RoutingTable::new(Id20::ZERO);
804 rt.mark_query(&id(42));
805 }
806
807 #[test]
808 fn get_mut_finds_node() {
809 let mut rt = RoutingTable::new(Id20::ZERO);
810 rt.insert(id(1), addr(1));
811 let node = rt.get_mut(&id(1));
812 assert!(node.is_some());
813 node.unwrap().fail_count = 99;
815 assert_eq!(rt.get(&id(1)).unwrap().fail_count, 99);
816 }
817
818 #[test]
819 fn get_mut_returns_none_for_missing_node() {
820 let mut rt = RoutingTable::new(Id20::ZERO);
821 assert!(rt.get_mut(&id(1)).is_none());
822 }
823
824 #[test]
825 fn questionable_nodes_filters_correctly() {
826 let mut rt = RoutingTable::new(Id20::ZERO);
827 rt.insert(id(1), addr(1)); rt.insert(id(2), addr(2)); rt.insert(id(3), addr(3)); rt.mark_response(&id(2));
831 rt.mark_failed(&id(3));
832 rt.mark_failed(&id(3));
833
834 let q = rt.questionable_nodes();
835 assert_eq!(q.len(), 1);
836 assert_eq!(q[0].0, id(1));
837 }
838
839 #[test]
840 fn questionable_nodes_empty_when_all_good_or_bad() {
841 let mut rt = RoutingTable::new(Id20::ZERO);
842 rt.insert(id(1), addr(1));
843 rt.insert(id(2), addr(2));
844 rt.mark_response(&id(1));
845 rt.mark_failed(&id(2));
846 rt.mark_failed(&id(2));
847
848 assert!(rt.questionable_nodes().is_empty());
849 }
850
851 #[test]
852 fn worst_node_evicts_oldest_on_tied_fail_counts() {
853 let mut bucket = KBucket::new();
855 let now = Instant::now();
856 bucket.nodes.push(RoutingNode {
858 id: id(1),
859 addr: addr(1),
860 last_seen: now - std::time::Duration::from_secs(100),
861 fail_count: 1,
862 last_response: None,
863 last_query: None,
864 });
865 bucket.nodes.push(RoutingNode {
867 id: id(2),
868 addr: addr(2),
869 last_seen: now - std::time::Duration::from_secs(10),
870 fail_count: 1,
871 last_response: None,
872 last_query: None,
873 });
874 let worst = bucket.worst_node().unwrap();
876 assert_eq!(
877 bucket.nodes[worst].id,
878 id(1),
879 "oldest node should be evicted on tied fail counts"
880 );
881 }
882
883 #[test]
884 fn worst_node_prefers_highest_fail_count() {
885 let mut bucket = KBucket::new();
886 let now = Instant::now();
887 bucket.nodes.push(RoutingNode {
888 id: id(1),
889 addr: addr(1),
890 last_seen: now,
891 fail_count: 3,
892 last_response: None,
893 last_query: None,
894 });
895 bucket.nodes.push(RoutingNode {
896 id: id(2),
897 addr: addr(2),
898 last_seen: now - std::time::Duration::from_secs(1000),
899 fail_count: 1,
900 last_response: None,
901 last_query: None,
902 });
903 let worst = bucket.worst_node().unwrap();
904 assert_eq!(
905 bucket.nodes[worst].id,
906 id(1),
907 "highest fail_count should win regardless of age"
908 );
909 }
910
911 #[test]
912 fn restrict_ips_eviction_frees_ip_slot() {
913 let mut rt = RoutingTable::new_with_config(Id20::ZERO, true);
914 for i in 1..=K as u8 {
916 let a: SocketAddr = format!("10.0.0.{i}:6881").parse().unwrap();
917 rt.insert(id(i), a);
918 }
919 assert_eq!(rt.len(), K);
920
921 rt.mark_failed(&id(1));
923 rt.mark_failed(&id(1));
924
925 let new_addr: SocketAddr = "10.0.0.100:6881".parse().unwrap();
927 assert!(rt.insert(id(100), new_addr));
928 assert_eq!(rt.len(), K);
929 let old_addr: SocketAddr = "10.0.0.1:6882".parse().unwrap();
931 assert!(rt.insert(id(200), old_addr));
932 }
933
934 #[test]
937 fn routing_table_node_cap_rejects_at_limit() {
938 let mut rt = RoutingTable::with_config(Id20::ZERO, false, 4);
941 for i in 1..=4u8 {
942 assert!(
943 rt.insert(id(i), addr(u16::from(i))),
944 "insert {i} should succeed"
945 );
946 }
947 assert_eq!(rt.len(), 4);
948 assert!(!rt.insert(id(5), addr(5)));
950 assert_eq!(rt.len(), 4);
951 }
952
953 #[test]
954 fn routing_table_node_cap_allows_eviction() {
955 let mut rt = RoutingTable::with_config(Id20::ZERO, false, 4);
958 for i in 1..=4u8 {
959 rt.insert(id(i), addr(u16::from(i)));
960 }
961 assert_eq!(rt.len(), 4);
962
963 rt.mark_failed(&id(1));
965
966 assert!(rt.insert(id(5), addr(5)));
968 assert_eq!(rt.len(), 4);
969 assert!(rt.get(&id(5)).is_some());
970 assert!(rt.get(&id(1)).is_none());
971 }
972
973 #[test]
974 fn routing_table_node_cap_allows_update() {
975 let mut rt = RoutingTable::with_config(Id20::ZERO, false, 4);
977 for i in 1..=4u8 {
978 rt.insert(id(i), addr(u16::from(i)));
979 }
980 assert_eq!(rt.len(), 4);
981
982 assert!(rt.insert(id(2), addr(200)));
984 assert_eq!(rt.len(), 4);
985 assert_eq!(rt.get(&id(2)).unwrap().addr, addr(200));
986 }
987
988 #[test]
989 fn routing_table_default_cap_512() {
990 let rt = RoutingTable::new(Id20::ZERO);
991 assert_eq!(rt.max_nodes, DEFAULT_MAX_NODES);
993 assert_eq!(rt.max_nodes, 512);
994 }
995
996 #[test]
997 fn mark_all_questionable_resets_liveness() {
998 let own_id = Id20([0x00; 20]);
999 let mut rt = RoutingTable::new(own_id);
1000
1001 let node1 = Id20([0x80; 20]);
1003 let node2 = Id20([0x40; 20]);
1004 let addr1: SocketAddr = "192.0.2.1:6881".parse().unwrap();
1005 let addr2: SocketAddr = "192.0.2.2:6881".parse().unwrap();
1006
1007 rt.insert(node1, addr1);
1008 rt.insert(node2, addr2);
1009 rt.mark_response(&node1);
1010 rt.mark_response(&node2);
1011
1012 assert_eq!(rt.get(&node1).unwrap().status(), NodeStatus::Good);
1014 assert_eq!(rt.get(&node2).unwrap().status(), NodeStatus::Good);
1015
1016 rt.mark_all_questionable();
1018
1019 assert_eq!(rt.get(&node1).unwrap().status(), NodeStatus::Questionable);
1021 assert_eq!(rt.get(&node2).unwrap().status(), NodeStatus::Questionable);
1022 assert_eq!(rt.get(&node1).unwrap().fail_count, 0);
1023 assert_eq!(rt.get(&node2).unwrap().fail_count, 0);
1024 }
1025}