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 pub addresses: Vec<CandidateAddr>,
36}
37
38impl Contact {
39 pub fn new(peer_id: &PeerId, mut addresses: Vec<CandidateAddr>) -> Self {
43 sort_and_cap_addresses(&mut addresses);
44 Contact {
45 peer_id: peer_id.to_hex(),
46 addresses,
47 }
48 }
49
50 pub fn peer_id(&self) -> Option<PeerId> {
52 PeerId::from_hex(&self.peer_id)
53 }
54
55 pub fn key(&self) -> Option<Key> {
57 self.peer_id().as_ref().map(Key::from_peer_id)
58 }
59
60 pub fn best_address(&self) -> Option<&CandidateAddr> {
63 self.addresses.iter().find(|a| a.kind.is_dialable())
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum InsertOutcome {
71 Inserted,
73 Full {
77 lru: Contact,
79 },
80}
81
82#[derive(Debug, Clone)]
85struct KBucket {
86 contacts: Vec<Contact>,
87 capacity: usize,
88}
89
90impl KBucket {
91 fn new(capacity: usize) -> Self {
92 KBucket {
93 contacts: Vec::new(),
94 capacity,
95 }
96 }
97
98 fn offer(&mut self, contact: Contact) -> InsertOutcome {
103 if let Some(pos) = self
104 .contacts
105 .iter()
106 .position(|c| c.peer_id == contact.peer_id)
107 {
108 self.contacts.remove(pos);
110 self.contacts.push(contact);
111 return InsertOutcome::Inserted;
112 }
113 if self.contacts.len() < self.capacity {
114 self.contacts.push(contact);
115 return InsertOutcome::Inserted;
116 }
117 InsertOutcome::Full {
119 lru: self.contacts[0].clone(),
120 }
121 }
122
123 fn replace(&mut self, lru_peer_id: &str, candidate: Contact) -> bool {
127 match self.contacts.first() {
128 Some(front) if front.peer_id == lru_peer_id => {
129 self.contacts.remove(0);
130 self.contacts.push(candidate);
131 true
132 }
133 _ => false,
134 }
135 }
136
137 fn remove(&mut self, peer_id: &str) -> bool {
138 if let Some(pos) = self.contacts.iter().position(|c| c.peer_id == peer_id) {
139 self.contacts.remove(pos);
140 true
141 } else {
142 false
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
153pub struct RoutingTable {
154 local_key: Key,
155 buckets: Vec<KBucket>,
156 k: usize,
157}
158
159impl RoutingTable {
160 pub fn new(local_id: &PeerId, k: usize) -> Self {
162 RoutingTable {
163 local_key: Key::from_peer_id(local_id),
164 buckets: (0..256).map(|_| KBucket::new(k)).collect(),
165 k,
166 }
167 }
168
169 pub fn local_key(&self) -> Key {
171 self.local_key
172 }
173
174 fn bucket_for(&self, key: &Key) -> Option<usize> {
176 self.local_key.distance(key).bucket_index()
177 }
178
179 pub fn insert(&mut self, contact: Contact) -> InsertOutcome {
183 let Some(key) = contact.key() else {
184 return InsertOutcome::Inserted;
186 };
187 match self.bucket_for(&key) {
188 None => InsertOutcome::Inserted, Some(idx) => self.buckets[idx].offer(contact),
190 }
191 }
192
193 pub fn replace(&mut self, lru: &Contact, candidate: Contact) -> bool {
197 let Some(key) = candidate.key() else {
198 return false;
199 };
200 match self.bucket_for(&key) {
201 None => false,
202 Some(idx) => self.buckets[idx].replace(&lru.peer_id, candidate),
203 }
204 }
205
206 pub fn remove(&mut self, peer_id: &str) -> bool {
208 let Some(pid) = PeerId::from_hex(peer_id) else {
209 return false;
210 };
211 match self.bucket_for(&Key::from_peer_id(&pid)) {
212 None => false,
213 Some(idx) => self.buckets[idx].remove(peer_id),
214 }
215 }
216
217 pub fn closest(&self, target: &Key) -> Vec<Contact> {
222 let mut all: Vec<(Contact, crate::key::Distance)> = self
223 .buckets
224 .iter()
225 .flat_map(|b| b.contacts.iter())
226 .filter_map(|c| c.key().map(|k| (c.clone(), target.distance(&k))))
227 .collect();
228 all.sort_by_key(|(_, dist)| *dist);
229 all.into_iter().take(self.k).map(|(c, _)| c).collect()
230 }
231
232 pub fn len(&self) -> usize {
234 self.buckets.iter().map(|b| b.contacts.len()).sum()
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.len() == 0
240 }
241
242 pub fn non_empty_bucket_indices(&self) -> Vec<usize> {
245 self.buckets
246 .iter()
247 .enumerate()
248 .filter(|(_, b)| !b.contacts.is_empty())
249 .map(|(i, _)| i)
250 .collect()
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use crate::record::{AddressKind, CandidateAddr};
258
259 fn pid(bytes: [u8; 32]) -> PeerId {
260 PeerId::from_bytes(bytes)
261 }
262
263 fn contact(bytes: [u8; 32]) -> Contact {
264 Contact::new(&pid(bytes), vec![CandidateAddr::direct("h", 1)])
265 }
266
267 fn local() -> PeerId {
268 pid([0u8; 32])
269 }
270
271 #[test]
272 fn insert_and_len() {
273 let mut t = RoutingTable::new(&local(), 20);
274 assert!(t.is_empty());
275 let mut b = [0u8; 32];
276 b[0] = 0x80;
277 assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
278 assert_eq!(t.len(), 1);
279 }
280
281 #[test]
282 fn own_id_is_never_stored() {
283 let mut t = RoutingTable::new(&local(), 20);
284 assert_eq!(t.insert(contact([0u8; 32])), InsertOutcome::Inserted);
285 assert_eq!(t.len(), 0);
286 }
287
288 #[test]
289 fn reinsert_refreshes_recency_not_count() {
290 let mut t = RoutingTable::new(&local(), 20);
291 let mut b = [0u8; 32];
292 b[0] = 0x01;
293 t.insert(contact(b));
294 t.insert(contact(b));
295 assert_eq!(t.len(), 1);
296 }
297
298 #[test]
299 fn full_bucket_reports_lru_and_does_not_grow() {
300 let mut t = RoutingTable::new(&local(), 2);
303 let mut a = [0u8; 32];
304 a[0] = 0x80;
305 a[31] = 0x01;
306 let mut b = [0u8; 32];
307 b[0] = 0x80;
308 b[31] = 0x02;
309 let mut c = [0u8; 32];
310 c[0] = 0x80;
311 c[31] = 0x03;
312 assert_eq!(t.insert(contact(a)), InsertOutcome::Inserted);
313 assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
314 match t.insert(contact(c)) {
316 InsertOutcome::Full { lru } => assert_eq!(lru, contact(a)),
317 other => panic!("expected Full, got {other:?}"),
318 }
319 assert_eq!(t.len(), 2, "full bucket must not grow past k");
320 }
321
322 #[test]
323 fn replace_evicts_lru_for_candidate() {
324 let mut t = RoutingTable::new(&local(), 2);
325 let mut a = [0u8; 32];
326 a[0] = 0x80;
327 a[31] = 0x01;
328 let mut b = [0u8; 32];
329 b[0] = 0x80;
330 b[31] = 0x02;
331 let mut c = [0u8; 32];
332 c[0] = 0x80;
333 c[31] = 0x03;
334 t.insert(contact(a));
335 t.insert(contact(b));
336 let lru = match t.insert(contact(c)) {
337 InsertOutcome::Full { lru } => lru,
338 other => panic!("expected Full, got {other:?}"),
339 };
340 assert!(t.replace(&lru, contact(c)));
342 assert_eq!(t.len(), 2);
343 let keys: Vec<String> = t
345 .buckets
346 .iter()
347 .flat_map(|bk| bk.contacts.iter())
348 .map(|x| x.peer_id.clone())
349 .collect();
350 assert!(!keys.contains(&contact(a).peer_id));
351 assert!(keys.contains(&contact(c).peer_id));
352 }
353
354 #[test]
355 fn replace_is_noop_if_lru_slot_changed() {
356 let mut t = RoutingTable::new(&local(), 2);
357 let mut a = [0u8; 32];
358 a[0] = 0x80;
359 a[31] = 0x01;
360 let mut b = [0u8; 32];
361 b[0] = 0x80;
362 b[31] = 0x02;
363 let mut c = [0u8; 32];
364 c[0] = 0x80;
365 c[31] = 0x03;
366 t.insert(contact(a));
367 t.insert(contact(b));
368 let lru = match t.insert(contact(c)) {
369 InsertOutcome::Full { lru } => lru,
370 _ => unreachable!(),
371 };
372 t.insert(contact(a));
374 assert!(!t.replace(&lru, contact(c)));
376 assert_eq!(t.len(), 2);
377 }
378
379 #[test]
380 fn remove_contact() {
381 let mut t = RoutingTable::new(&local(), 20);
382 let mut b = [0u8; 32];
383 b[0] = 0x40;
384 let c = contact(b);
385 t.insert(c.clone());
386 assert!(t.remove(&c.peer_id));
387 assert!(!t.remove(&c.peer_id));
388 assert!(t.is_empty());
389 }
390
391 #[test]
392 fn closest_returns_k_sorted_by_distance() {
393 let mut t = RoutingTable::new(&local(), 20);
394 for i in 1u8..=10 {
396 let mut b = [0u8; 32];
397 b[0] = i; t.insert(contact(b));
399 }
400 let target = Key::from_bytes([0u8; 32]);
401 let closest = t.closest(&target);
402 assert_eq!(closest.len(), 10);
403 let dists: Vec<_> = closest
405 .iter()
406 .map(|c| target.distance(&c.key().unwrap()))
407 .collect();
408 for w in dists.windows(2) {
409 assert!(w[0] <= w[1], "closest must be distance-sorted");
410 }
411 }
412
413 #[test]
414 fn closest_caps_at_k() {
415 let mut t = RoutingTable::new(&local(), 3);
416 for i in 1u8..=10 {
417 let mut b = [0u8; 32];
418 b[0] = i;
419 t.insert(contact(b));
420 }
421 assert_eq!(t.closest(&Key::from_bytes([0u8; 32])).len(), 3);
422 }
423
424 #[test]
425 fn non_empty_bucket_indices_tracks_population() {
426 let mut t = RoutingTable::new(&local(), 20);
427 assert!(t.non_empty_bucket_indices().is_empty());
428 let mut b = [0u8; 32];
429 b[0] = 0x80; t.insert(contact(b));
431 assert_eq!(t.non_empty_bucket_indices(), vec![255]);
432 }
433
434 #[test]
435 fn contact_key_and_best_address() {
436 let c = contact({
437 let mut b = [0u8; 32];
438 b[0] = 0x22;
439 b
440 });
441 assert!(c.key().is_some());
442 assert_eq!(c.best_address().unwrap().kind, AddressKindDirect());
443 }
444
445 #[allow(non_snake_case)]
447 fn AddressKindDirect() -> crate::record::AddressKind {
448 crate::record::AddressKind::Direct
449 }
450
451 #[test]
452 fn contact_new_sorts_addresses_ipv6_first() {
453 let c = Contact::new(
454 &pid([1u8; 32]),
455 vec![
456 CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), CandidateAddr {
459 host: "198.51.100.2".into(),
460 port: 1,
461 kind: AddressKind::Reflexive,
462 },
463 CandidateAddr {
464 host: "2001:db8::2".into(),
465 port: 1,
466 kind: AddressKind::Reflexive,
467 },
468 ],
469 );
470 let hosts: Vec<&str> = c.addresses.iter().map(|a| a.host.as_str()).collect();
471 assert_eq!(
472 hosts,
473 vec!["2001:db8::1", "2001:db8::2", "203.0.113.7", "198.51.100.2"],
474 "contact addresses must be IPv6-first, then ranked by AddressKind"
475 );
476 }
477
478 #[test]
479 fn contact_best_address_prefers_ipv6_over_ipv4_at_same_rank() {
480 let c = Contact::new(
481 &pid([1u8; 32]),
482 vec![
483 CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), ],
486 );
487 assert_eq!(c.best_address().unwrap().host, "2001:db8::1");
488 }
489
490 #[test]
491 fn contact_new_caps_addresses_at_the_constant() {
492 let many: Vec<CandidateAddr> = (0..1000)
495 .map(|i| CandidateAddr::direct(format!("203.0.113.{}", i % 255), 9444))
496 .collect();
497 let c = Contact::new(&pid([1u8; 32]), many);
498 assert_eq!(c.addresses.len(), crate::record::MAX_ADDRESSES_PER_RECORD);
499 }
500}