use serde::{Deserialize, Serialize};
use dig_nat::PeerId;
use crate::key::Key;
use crate::record::{sort_and_cap_addresses, CandidateAddr};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Contact {
pub peer_id: String,
pub addresses: Vec<CandidateAddr>,
}
impl Contact {
pub fn new(peer_id: &PeerId, mut addresses: Vec<CandidateAddr>) -> Self {
sort_and_cap_addresses(&mut addresses);
Contact {
peer_id: peer_id.to_hex(),
addresses,
}
}
pub fn peer_id(&self) -> Option<PeerId> {
PeerId::from_hex(&self.peer_id)
}
pub fn key(&self) -> Option<Key> {
self.peer_id().as_ref().map(Key::from_peer_id)
}
pub fn best_address(&self) -> Option<&CandidateAddr> {
self.addresses.iter().find(|a| a.kind.is_dialable())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InsertOutcome {
Inserted,
Full {
lru: Contact,
},
}
#[derive(Debug, Clone)]
struct KBucket {
contacts: Vec<Contact>,
capacity: usize,
}
impl KBucket {
fn new(capacity: usize) -> Self {
KBucket {
contacts: Vec::new(),
capacity,
}
}
fn offer(&mut self, contact: Contact) -> InsertOutcome {
if let Some(pos) = self
.contacts
.iter()
.position(|c| c.peer_id == contact.peer_id)
{
self.contacts.remove(pos);
self.contacts.push(contact);
return InsertOutcome::Inserted;
}
if self.contacts.len() < self.capacity {
self.contacts.push(contact);
return InsertOutcome::Inserted;
}
InsertOutcome::Full {
lru: self.contacts[0].clone(),
}
}
fn replace(&mut self, lru_peer_id: &str, candidate: Contact) -> bool {
match self.contacts.first() {
Some(front) if front.peer_id == lru_peer_id => {
self.contacts.remove(0);
self.contacts.push(candidate);
true
}
_ => false,
}
}
fn remove(&mut self, peer_id: &str) -> bool {
if let Some(pos) = self.contacts.iter().position(|c| c.peer_id == peer_id) {
self.contacts.remove(pos);
true
} else {
false
}
}
}
#[derive(Debug, Clone)]
pub struct RoutingTable {
local_key: Key,
buckets: Vec<KBucket>,
k: usize,
}
impl RoutingTable {
pub fn new(local_id: &PeerId, k: usize) -> Self {
RoutingTable {
local_key: Key::from_peer_id(local_id),
buckets: (0..256).map(|_| KBucket::new(k)).collect(),
k,
}
}
pub fn local_key(&self) -> Key {
self.local_key
}
fn bucket_for(&self, key: &Key) -> Option<usize> {
self.local_key.distance(key).bucket_index()
}
pub fn insert(&mut self, contact: Contact) -> InsertOutcome {
let Some(key) = contact.key() else {
return InsertOutcome::Inserted;
};
match self.bucket_for(&key) {
None => InsertOutcome::Inserted, Some(idx) => self.buckets[idx].offer(contact),
}
}
pub fn replace(&mut self, lru: &Contact, candidate: Contact) -> bool {
let Some(key) = candidate.key() else {
return false;
};
match self.bucket_for(&key) {
None => false,
Some(idx) => self.buckets[idx].replace(&lru.peer_id, candidate),
}
}
pub fn remove(&mut self, peer_id: &str) -> bool {
let Some(pid) = PeerId::from_hex(peer_id) else {
return false;
};
match self.bucket_for(&Key::from_peer_id(&pid)) {
None => false,
Some(idx) => self.buckets[idx].remove(peer_id),
}
}
pub fn closest(&self, target: &Key) -> Vec<Contact> {
let mut all: Vec<(Contact, crate::key::Distance)> = self
.buckets
.iter()
.flat_map(|b| b.contacts.iter())
.filter_map(|c| c.key().map(|k| (c.clone(), target.distance(&k))))
.collect();
all.sort_by_key(|(_, dist)| *dist);
all.into_iter().take(self.k).map(|(c, _)| c).collect()
}
pub fn len(&self) -> usize {
self.buckets.iter().map(|b| b.contacts.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn non_empty_bucket_indices(&self) -> Vec<usize> {
self.buckets
.iter()
.enumerate()
.filter(|(_, b)| !b.contacts.is_empty())
.map(|(i, _)| i)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::record::{AddressKind, CandidateAddr};
fn pid(bytes: [u8; 32]) -> PeerId {
PeerId::from_bytes(bytes)
}
fn contact(bytes: [u8; 32]) -> Contact {
Contact::new(&pid(bytes), vec![CandidateAddr::direct("h", 1)])
}
fn local() -> PeerId {
pid([0u8; 32])
}
#[test]
fn insert_and_len() {
let mut t = RoutingTable::new(&local(), 20);
assert!(t.is_empty());
let mut b = [0u8; 32];
b[0] = 0x80;
assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
assert_eq!(t.len(), 1);
}
#[test]
fn own_id_is_never_stored() {
let mut t = RoutingTable::new(&local(), 20);
assert_eq!(t.insert(contact([0u8; 32])), InsertOutcome::Inserted);
assert_eq!(t.len(), 0);
}
#[test]
fn reinsert_refreshes_recency_not_count() {
let mut t = RoutingTable::new(&local(), 20);
let mut b = [0u8; 32];
b[0] = 0x01;
t.insert(contact(b));
t.insert(contact(b));
assert_eq!(t.len(), 1);
}
#[test]
fn full_bucket_reports_lru_and_does_not_grow() {
let mut t = RoutingTable::new(&local(), 2);
let mut a = [0u8; 32];
a[0] = 0x80;
a[31] = 0x01;
let mut b = [0u8; 32];
b[0] = 0x80;
b[31] = 0x02;
let mut c = [0u8; 32];
c[0] = 0x80;
c[31] = 0x03;
assert_eq!(t.insert(contact(a)), InsertOutcome::Inserted);
assert_eq!(t.insert(contact(b)), InsertOutcome::Inserted);
match t.insert(contact(c)) {
InsertOutcome::Full { lru } => assert_eq!(lru, contact(a)),
other => panic!("expected Full, got {other:?}"),
}
assert_eq!(t.len(), 2, "full bucket must not grow past k");
}
#[test]
fn replace_evicts_lru_for_candidate() {
let mut t = RoutingTable::new(&local(), 2);
let mut a = [0u8; 32];
a[0] = 0x80;
a[31] = 0x01;
let mut b = [0u8; 32];
b[0] = 0x80;
b[31] = 0x02;
let mut c = [0u8; 32];
c[0] = 0x80;
c[31] = 0x03;
t.insert(contact(a));
t.insert(contact(b));
let lru = match t.insert(contact(c)) {
InsertOutcome::Full { lru } => lru,
other => panic!("expected Full, got {other:?}"),
};
assert!(t.replace(&lru, contact(c)));
assert_eq!(t.len(), 2);
let keys: Vec<String> = t
.buckets
.iter()
.flat_map(|bk| bk.contacts.iter())
.map(|x| x.peer_id.clone())
.collect();
assert!(!keys.contains(&contact(a).peer_id));
assert!(keys.contains(&contact(c).peer_id));
}
#[test]
fn replace_is_noop_if_lru_slot_changed() {
let mut t = RoutingTable::new(&local(), 2);
let mut a = [0u8; 32];
a[0] = 0x80;
a[31] = 0x01;
let mut b = [0u8; 32];
b[0] = 0x80;
b[31] = 0x02;
let mut c = [0u8; 32];
c[0] = 0x80;
c[31] = 0x03;
t.insert(contact(a));
t.insert(contact(b));
let lru = match t.insert(contact(c)) {
InsertOutcome::Full { lru } => lru,
_ => unreachable!(),
};
t.insert(contact(a));
assert!(!t.replace(&lru, contact(c)));
assert_eq!(t.len(), 2);
}
#[test]
fn remove_contact() {
let mut t = RoutingTable::new(&local(), 20);
let mut b = [0u8; 32];
b[0] = 0x40;
let c = contact(b);
t.insert(c.clone());
assert!(t.remove(&c.peer_id));
assert!(!t.remove(&c.peer_id));
assert!(t.is_empty());
}
#[test]
fn closest_returns_k_sorted_by_distance() {
let mut t = RoutingTable::new(&local(), 20);
for i in 1u8..=10 {
let mut b = [0u8; 32];
b[0] = i; t.insert(contact(b));
}
let target = Key::from_bytes([0u8; 32]);
let closest = t.closest(&target);
assert_eq!(closest.len(), 10);
let dists: Vec<_> = closest
.iter()
.map(|c| target.distance(&c.key().unwrap()))
.collect();
for w in dists.windows(2) {
assert!(w[0] <= w[1], "closest must be distance-sorted");
}
}
#[test]
fn closest_caps_at_k() {
let mut t = RoutingTable::new(&local(), 3);
for i in 1u8..=10 {
let mut b = [0u8; 32];
b[0] = i;
t.insert(contact(b));
}
assert_eq!(t.closest(&Key::from_bytes([0u8; 32])).len(), 3);
}
#[test]
fn non_empty_bucket_indices_tracks_population() {
let mut t = RoutingTable::new(&local(), 20);
assert!(t.non_empty_bucket_indices().is_empty());
let mut b = [0u8; 32];
b[0] = 0x80; t.insert(contact(b));
assert_eq!(t.non_empty_bucket_indices(), vec![255]);
}
#[test]
fn contact_key_and_best_address() {
let c = contact({
let mut b = [0u8; 32];
b[0] = 0x22;
b
});
assert!(c.key().is_some());
assert_eq!(c.best_address().unwrap().kind, AddressKindDirect());
}
#[allow(non_snake_case)]
fn AddressKindDirect() -> crate::record::AddressKind {
crate::record::AddressKind::Direct
}
#[test]
fn contact_new_sorts_addresses_ipv6_first() {
let c = Contact::new(
&pid([1u8; 32]),
vec![
CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), CandidateAddr {
host: "198.51.100.2".into(),
port: 1,
kind: AddressKind::Reflexive,
},
CandidateAddr {
host: "2001:db8::2".into(),
port: 1,
kind: AddressKind::Reflexive,
},
],
);
let hosts: Vec<&str> = c.addresses.iter().map(|a| a.host.as_str()).collect();
assert_eq!(
hosts,
vec!["2001:db8::1", "2001:db8::2", "203.0.113.7", "198.51.100.2"],
"contact addresses must be IPv6-first, then ranked by AddressKind"
);
}
#[test]
fn contact_best_address_prefers_ipv6_over_ipv4_at_same_rank() {
let c = Contact::new(
&pid([1u8; 32]),
vec![
CandidateAddr::direct("203.0.113.7", 9444), CandidateAddr::direct("2001:db8::1", 9444), ],
);
assert_eq!(c.best_address().unwrap().host, "2001:db8::1");
}
#[test]
fn contact_new_caps_addresses_at_the_constant() {
let many: Vec<CandidateAddr> = (0..1000)
.map(|i| CandidateAddr::direct(format!("203.0.113.{}", i % 255), 9444))
.collect();
let c = Contact::new(&pid([1u8; 32]), many);
assert_eq!(c.addresses.len(), crate::record::MAX_ADDRESSES_PER_RECORD);
}
}