use crate::{
netdb::{
bucket::KBucket,
types::{Distance, Key},
},
primitives::RouterId,
};
use hashbrown::HashSet;
use alloc::vec::Vec;
use core::ops::Deref;
const LOG_TARGET: &str = "emissary::netdb::routing-table";
const NUM_BUCKETS: usize = 256;
pub struct RoutingTable {
local_key: Key<RouterId>,
buckets: Vec<KBucket>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct BucketIndex(usize);
impl Deref for BucketIndex {
type Target = usize;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl BucketIndex {
fn new(d: &Distance) -> Option<BucketIndex> {
d.ilog2().map(|i| BucketIndex(i as usize))
}
}
impl RoutingTable {
pub fn new(local_key: Key<RouterId>) -> Self {
RoutingTable {
local_key,
buckets: (0..NUM_BUCKETS).map(|_| KBucket::new()).collect(),
}
}
fn bucket_index(&self, key: &Key<RouterId>) -> Option<BucketIndex> {
match BucketIndex::new(&self.local_key.distance(&key)) {
Some(index) => Some(index),
None => {
tracing::warn!(
target: LOG_TARGET,
"tried to add local router to routing table",
);
None
}
}
}
pub fn add_router(&mut self, router_id: RouterId) {
tracing::trace!(
target: LOG_TARGET,
%router_id,
"add router",
);
let key = Key::from(router_id.clone());
if let Some(index) = self.bucket_index(&key) {
if !self.buckets[*index].try_insert(key) {
tracing::trace!(
target: LOG_TARGET,
%router_id,
"failed to add floodfill to routing table",
);
}
}
}
pub fn adjust_score(&mut self, router_id: &RouterId, adjustment: isize) {
let key = Key::from(router_id.clone());
if let Some(index) = BucketIndex::new(&self.local_key.distance(&key)) {
self.buckets[*index].adjust_score(key, adjustment);
}
}
pub fn closest<'a, K: Clone + 'a>(
&'a mut self,
target: Key<K>,
limit: usize,
) -> impl Iterator<Item = RouterId> + 'a {
ClosestBucketsIter::new(self.local_key.distance(&target))
.flat_map(move |index| self.buckets[*index].closest_iter(&target))
.take(limit)
}
pub fn closest_with_ignore<'a, 'b: 'a, K: Clone + 'a>(
&'a self,
target: Key<K>,
limit: usize,
ignore: &'b HashSet<RouterId>,
) -> impl Iterator<Item = RouterId> + 'a {
ClosestBucketsIter::new(self.local_key.distance(&target))
.flat_map(move |index| self.buckets[*index].closest_iter(&target))
.filter(|router_id| !ignore.contains(router_id))
.take(limit)
}
}
struct ClosestBucketsIter {
distance: Distance,
state: ClosestBucketsIterState,
}
enum ClosestBucketsIterState {
Start(BucketIndex),
ZoomIn(BucketIndex),
ZoomOut(BucketIndex),
Done,
}
impl ClosestBucketsIter {
fn new(distance: Distance) -> Self {
let state = match BucketIndex::new(&distance) {
Some(i) => ClosestBucketsIterState::Start(i),
None => ClosestBucketsIterState::Start(BucketIndex(0)),
};
Self { distance, state }
}
fn next_in(&self, i: BucketIndex) -> Option<BucketIndex> {
(0..*i).rev().find_map(|i| self.distance.0.bit(i).then_some(BucketIndex(i)))
}
fn next_out(&self, i: BucketIndex) -> Option<BucketIndex> {
(*i + 1..NUM_BUCKETS).find_map(|i| (!self.distance.0.bit(i)).then_some(BucketIndex(i)))
}
}
impl Iterator for ClosestBucketsIter {
type Item = BucketIndex;
fn next(&mut self) -> Option<Self::Item> {
match self.state {
ClosestBucketsIterState::Start(i) => {
self.state = ClosestBucketsIterState::ZoomIn(i);
Some(i)
}
ClosestBucketsIterState::ZoomIn(i) =>
if let Some(i) = self.next_in(i) {
self.state = ClosestBucketsIterState::ZoomIn(i);
Some(i)
} else {
let i = BucketIndex(0);
self.state = ClosestBucketsIterState::ZoomOut(i);
Some(i)
},
ClosestBucketsIterState::ZoomOut(i) =>
if let Some(i) = self.next_out(i) {
self.state = ClosestBucketsIterState::ZoomOut(i);
Some(i)
} else {
self.state = ClosestBucketsIterState::Done;
None
},
ClosestBucketsIterState::Done => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::netdb::types::U256;
#[test]
fn closest_routers() {
let own_router_id = RouterId::random();
let own_key = Key::from(own_router_id);
let mut table = RoutingTable::new(own_key.clone());
for _ in 0..60 {
let router_id = RouterId::random();
table.add_router(router_id);
}
let target = Key::from(RouterId::random());
let closest = table.closest(target.clone(), 60usize);
let mut prev = None;
for router_id in closest {
if let Some(value) = prev {
assert!(value < target.distance(&Key::from(router_id.clone())));
}
prev = Some(target.distance(&Key::from(router_id)));
}
}
#[test]
fn cannot_add_own_router_id() {
let own_router_id = RouterId::random();
let own_key = Key::from(own_router_id);
let table = RoutingTable::new(own_key.clone());
assert!(table.bucket_index(&own_key).is_none());
}
#[test]
fn closest_buckets_iterator_set_lsb() {
let d = Distance(U256::from(0b10011011));
let mut iter = ClosestBucketsIter::new(d);
let expected_buckets = vec![7, 4, 3, 1, 0, 0, 2, 5, 6]
.into_iter()
.chain(8..=255)
.map(|i| BucketIndex(i));
for expected in expected_buckets {
let got = iter.next().unwrap();
assert_eq!(got, expected);
}
assert!(iter.next().is_none());
}
#[test]
fn closest_buckets_iterator_unset_lsb() {
let d = Distance(U256::from(0b01011010));
let mut iter = ClosestBucketsIter::new(d);
let expected_buckets =
vec![6, 4, 3, 1, 0, 2, 5, 7].into_iter().chain(8..=255).map(|i| BucketIndex(i));
for expected in expected_buckets {
let got = iter.next().unwrap();
assert_eq!(got, expected);
}
assert!(iter.next().is_none());
}
}