1use serde::{Deserialize, Serialize};
16
17use dig_nat::PeerId;
18
19use crate::key::Key;
20use crate::record::{sort_and_cap_addresses, CandidateAddr};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Contact {
29 pub peer_id: String,
31 #[serde(deserialize_with = "crate::record::deserialize_capped_addresses")]
37 pub addresses: Vec<CandidateAddr>,
38}
39
40impl Contact {
41 pub fn new(peer_id: &PeerId, mut addresses: Vec<CandidateAddr>) -> Self {
45 sort_and_cap_addresses(&mut addresses);
46 Contact {
47 peer_id: peer_id.to_hex(),
48 addresses,
49 }
50 }
51
52 pub fn peer_id(&self) -> Option<PeerId> {
54 PeerId::from_hex(&self.peer_id)
55 }
56
57 pub fn key(&self) -> Option<Key> {
59 self.peer_id().as_ref().map(Key::from_peer_id)
60 }
61
62 pub fn best_address(&self) -> Option<&CandidateAddr> {
69 self.addresses.iter().find(|a| a.kind.is_dialable())
70 }
71
72 pub fn dial_candidates(&self) -> Vec<&CandidateAddr> {
75 crate::record::dial_candidates(&self.addresses)
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum InsertOutcome {
83 Inserted,
85 Full {
89 lru: Contact,
91 },
92}
93
94#[derive(Debug, Clone)]
97struct KBucket {
98 contacts: Vec<Contact>,
99 capacity: usize,
100}
101
102impl KBucket {
103 fn new(capacity: usize) -> Self {
104 KBucket {
105 contacts: Vec::new(),
106 capacity,
107 }
108 }
109
110 fn offer(&mut self, contact: Contact) -> InsertOutcome {
115 if let Some(pos) = self
116 .contacts
117 .iter()
118 .position(|c| c.peer_id == contact.peer_id)
119 {
120 self.contacts.remove(pos);
122 self.contacts.push(contact);
123 return InsertOutcome::Inserted;
124 }
125 if self.contacts.len() < self.capacity {
126 self.contacts.push(contact);
127 return InsertOutcome::Inserted;
128 }
129 InsertOutcome::Full {
131 lru: self.contacts[0].clone(),
132 }
133 }
134
135 fn replace(&mut self, lru_peer_id: &str, candidate: Contact) -> bool {
139 match self.contacts.first() {
140 Some(front) if front.peer_id == lru_peer_id => {
141 self.contacts.remove(0);
142 self.contacts.push(candidate);
143 true
144 }
145 _ => false,
146 }
147 }
148
149 fn remove(&mut self, peer_id: &str) -> bool {
150 if let Some(pos) = self.contacts.iter().position(|c| c.peer_id == peer_id) {
151 self.contacts.remove(pos);
152 true
153 } else {
154 false
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct RoutingTable {
166 local_key: Key,
167 buckets: Vec<KBucket>,
168 k: usize,
169}
170
171impl RoutingTable {
172 pub fn new(local_id: &PeerId, k: usize) -> Self {
174 RoutingTable {
175 local_key: Key::from_peer_id(local_id),
176 buckets: (0..256).map(|_| KBucket::new(k)).collect(),
177 k,
178 }
179 }
180
181 pub fn local_key(&self) -> Key {
183 self.local_key
184 }
185
186 fn bucket_for(&self, key: &Key) -> Option<usize> {
188 self.local_key.distance(key).bucket_index()
189 }
190
191 pub fn insert(&mut self, contact: Contact) -> InsertOutcome {
195 let Some(key) = contact.key() else {
196 return InsertOutcome::Inserted;
198 };
199 match self.bucket_for(&key) {
200 None => InsertOutcome::Inserted, Some(idx) => self.buckets[idx].offer(contact),
202 }
203 }
204
205 pub fn replace(&mut self, lru: &Contact, candidate: Contact) -> bool {
209 let Some(key) = candidate.key() else {
210 return false;
211 };
212 match self.bucket_for(&key) {
213 None => false,
214 Some(idx) => self.buckets[idx].replace(&lru.peer_id, candidate),
215 }
216 }
217
218 pub fn remove(&mut self, peer_id: &str) -> bool {
220 let Some(pid) = PeerId::from_hex(peer_id) else {
221 return false;
222 };
223 match self.bucket_for(&Key::from_peer_id(&pid)) {
224 None => false,
225 Some(idx) => self.buckets[idx].remove(peer_id),
226 }
227 }
228
229 pub fn closest(&self, target: &Key) -> Vec<Contact> {
234 let mut all: Vec<(Contact, crate::key::Distance)> = self
235 .buckets
236 .iter()
237 .flat_map(|b| b.contacts.iter())
238 .filter_map(|c| c.key().map(|k| (c.clone(), target.distance(&k))))
239 .collect();
240 all.sort_by_key(|(_, dist)| *dist);
241 all.into_iter().take(self.k).map(|(c, _)| c).collect()
242 }
243
244 pub fn len(&self) -> usize {
246 self.buckets.iter().map(|b| b.contacts.len()).sum()
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.len() == 0
252 }
253
254 pub fn non_empty_bucket_indices(&self) -> Vec<usize> {
257 self.buckets
258 .iter()
259 .enumerate()
260 .filter(|(_, b)| !b.contacts.is_empty())
261 .map(|(i, _)| i)
262 .collect()
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::record::{AddressKind, CandidateAddr};
270
271 fn pid(bytes: [u8; 32]) -> PeerId {
272 PeerId::from_bytes(bytes)
273 }
274
275 fn contact(bytes: [u8; 32]) -> Contact {
276 Contact::new(&pid(bytes), vec![CandidateAddr::direct("h", 1)])
277 }
278
279 fn local() -> PeerId {
280 pid([0u8; 32])
281 }
282
283 #[test]
284 fn insert_and_len() {
285 let mut t = RoutingTable::new(&local(), 20);
286 assert!(t.is_empty());
287 let mut b = [0u8; 32];
288 b[0] = 0x80;
289 assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
290 assert_eq!(t.len(), 1);
291 }
292
293 #[test]
294 fn own_id_is_never_stored() {
295 let mut t = RoutingTable::new(&local(), 20);
296 assert_eq!(t.insert(contact([0u8; 32])), InsertOutcome::Inserted);
297 assert_eq!(t.len(), 0);
298 }
299
300 #[test]
301 fn reinsert_refreshes_recency_not_count() {
302 let mut t = RoutingTable::new(&local(), 20);
303 let mut b = [0u8; 32];
304 b[0] = 0x01;
305 t.insert(contact(b));
306 t.insert(contact(b));
307 assert_eq!(t.len(), 1);
308 }
309
310 #[test]
311 fn full_bucket_reports_lru_and_does_not_grow() {
312 let mut t = RoutingTable::new(&local(), 2);
315 let mut a = [0u8; 32];
316 a[0] = 0x80;
317 a[31] = 0x01;
318 let mut b = [0u8; 32];
319 b[0] = 0x80;
320 b[31] = 0x02;
321 let mut c = [0u8; 32];
322 c[0] = 0x80;
323 c[31] = 0x03;
324 assert_eq!(t.insert(contact(a)), InsertOutcome::Inserted);
325 assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
326 match t.insert(contact(c)) {
328 InsertOutcome::Full { lru } => assert_eq!(lru, contact(a)),
329 other => panic!("expected Full, got {other:?}"),
330 }
331 assert_eq!(t.len(), 2, "full bucket must not grow past k");
332 }
333
334 #[test]
335 fn replace_evicts_lru_for_candidate() {
336 let mut t = RoutingTable::new(&local(), 2);
337 let mut a = [0u8; 32];
338 a[0] = 0x80;
339 a[31] = 0x01;
340 let mut b = [0u8; 32];
341 b[0] = 0x80;
342 b[31] = 0x02;
343 let mut c = [0u8; 32];
344 c[0] = 0x80;
345 c[31] = 0x03;
346 t.insert(contact(a));
347 t.insert(contact(b));
348 let lru = match t.insert(contact(c)) {
349 InsertOutcome::Full { lru } => lru,
350 other => panic!("expected Full, got {other:?}"),
351 };
352 assert!(t.replace(&lru, contact(c)));
354 assert_eq!(t.len(), 2);
355 let keys: Vec<String> = t
357 .buckets
358 .iter()
359 .flat_map(|bk| bk.contacts.iter())
360 .map(|x| x.peer_id.clone())
361 .collect();
362 assert!(!keys.contains(&contact(a).peer_id));
363 assert!(keys.contains(&contact(c).peer_id));
364 }
365
366 #[test]
367 fn replace_is_noop_if_lru_slot_changed() {
368 let mut t = RoutingTable::new(&local(), 2);
369 let mut a = [0u8; 32];
370 a[0] = 0x80;
371 a[31] = 0x01;
372 let mut b = [0u8; 32];
373 b[0] = 0x80;
374 b[31] = 0x02;
375 let mut c = [0u8; 32];
376 c[0] = 0x80;
377 c[31] = 0x03;
378 t.insert(contact(a));
379 t.insert(contact(b));
380 let lru = match t.insert(contact(c)) {
381 InsertOutcome::Full { lru } => lru,
382 _ => unreachable!(),
383 };
384 t.insert(contact(a));
386 assert!(!t.replace(&lru, contact(c)));
388 assert_eq!(t.len(), 2);
389 }
390
391 #[test]
392 fn remove_contact() {
393 let mut t = RoutingTable::new(&local(), 20);
394 let mut b = [0u8; 32];
395 b[0] = 0x40;
396 let c = contact(b);
397 t.insert(c.clone());
398 assert!(t.remove(&c.peer_id));
399 assert!(!t.remove(&c.peer_id));
400 assert!(t.is_empty());
401 }
402
403 #[test]
404 fn closest_returns_k_sorted_by_distance() {
405 let mut t = RoutingTable::new(&local(), 20);
406 for i in 1u8..=10 {
408 let mut b = [0u8; 32];
409 b[0] = i; t.insert(contact(b));
411 }
412 let target = Key::from_bytes([0u8; 32]);
413 let closest = t.closest(&target);
414 assert_eq!(closest.len(), 10);
415 let dists: Vec<_> = closest
417 .iter()
418 .map(|c| target.distance(&c.key().unwrap()))
419 .collect();
420 for w in dists.windows(2) {
421 assert!(w[0] <= w[1], "closest must be distance-sorted");
422 }
423 }
424
425 #[test]
426 fn closest_caps_at_k() {
427 let mut t = RoutingTable::new(&local(), 3);
428 for i in 1u8..=10 {
429 let mut b = [0u8; 32];
430 b[0] = i;
431 t.insert(contact(b));
432 }
433 assert_eq!(t.closest(&Key::from_bytes([0u8; 32])).len(), 3);
434 }
435
436 #[test]
437 fn non_empty_bucket_indices_tracks_population() {
438 let mut t = RoutingTable::new(&local(), 20);
439 assert!(t.non_empty_bucket_indices().is_empty());
440 let mut b = [0u8; 32];
441 b[0] = 0x80; t.insert(contact(b));
443 assert_eq!(t.non_empty_bucket_indices(), vec![255]);
444 }
445
446 #[test]
447 fn contact_key_and_best_address() {
448 let c = contact({
449 let mut b = [0u8; 32];
450 b[0] = 0x22;
451 b
452 });
453 assert!(c.key().is_some());
454 assert_eq!(c.best_address().unwrap().kind, AddressKindDirect());
455 }
456
457 #[allow(non_snake_case)]
459 fn AddressKindDirect() -> crate::record::AddressKind {
460 crate::record::AddressKind::Direct
461 }
462
463 #[test]
464 fn contact_new_sorts_addresses_ipv6_first() {
465 let c = Contact::new(
466 &pid([1u8; 32]),
467 vec![
468 CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), CandidateAddr {
471 host: "198.51.100.2".into(),
472 port: 1,
473 kind: AddressKind::Reflexive,
474 },
475 CandidateAddr {
476 host: "2001:db8::2".into(),
477 port: 1,
478 kind: AddressKind::Reflexive,
479 },
480 ],
481 );
482 let hosts: Vec<&str> = c.addresses.iter().map(|a| a.host.as_str()).collect();
483 assert_eq!(
484 hosts,
485 vec!["2001:db8::1", "2001:db8::2", "203.0.113.7", "198.51.100.2"],
486 "contact addresses must be IPv6-first, then ranked by AddressKind"
487 );
488 }
489
490 #[test]
491 fn contact_best_address_prefers_ipv6_over_ipv4_at_same_rank() {
492 let c = Contact::new(
493 &pid([1u8; 32]),
494 vec![
495 CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), ],
498 );
499 assert_eq!(c.best_address().unwrap().host, "2001:db8::1");
500 }
501
502 #[test]
503 fn contact_new_caps_addresses_at_the_constant() {
504 let many: Vec<CandidateAddr> = (0..1000)
507 .map(|i| CandidateAddr::direct(format!("203.0.113.{}", i % 255), 9444))
508 .collect();
509 let c = Contact::new(&pid([1u8; 32]), many);
510 assert_eq!(c.addresses.len(), crate::record::MAX_ADDRESSES_PER_RECORD);
511 }
512
513 #[test]
514 fn contact_deserialization_bounds_the_address_count() {
515 let addrs: Vec<String> = (0..1000)
519 .map(|i| {
520 format!(
521 r#"{{"host":"198.51.100.{}","port":1,"kind":"relay"}}"#,
522 i % 255
523 )
524 })
525 .collect();
526 let json = format!(
527 r#"{{"peer_id":"{}","addresses":[{}]}}"#,
528 "cc".repeat(32),
529 addrs.join(",")
530 );
531 let c: Contact = serde_json::from_str(&json).unwrap();
532 assert_eq!(c.addresses.len(), crate::record::MAX_ADDRESSES_PER_RECORD);
533 }
534
535 #[test]
536 fn contact_dial_candidates_order_v6_then_v4_then_unresolvable() {
537 let c = Contact::new(
540 &pid([1u8; 32]),
541 vec![
542 CandidateAddr::direct("not-a-literal", 9444),
543 CandidateAddr::direct("203.0.113.7", 9444),
544 CandidateAddr::direct("2001:db8::1", 9444),
545 CandidateAddr::relay_marker(),
546 ],
547 );
548 let hosts: Vec<&str> = c
549 .dial_candidates()
550 .iter()
551 .map(|a| a.host.as_str())
552 .collect();
553 assert_eq!(hosts, vec!["2001:db8::1", "203.0.113.7", "not-a-literal"]);
554 }
555}