use crate::{
netdb::types::{FloodFill, Key},
primitives::RouterId,
};
use alloc::vec::Vec;
const LOG_TARGET: &str = "emissary::netdb::k-bucket";
pub struct KBucket {
floodfills: Vec<FloodFill>,
}
impl KBucket {
pub fn new() -> Self {
Self {
floodfills: Vec::with_capacity(20),
}
}
pub fn try_insert(&mut self, key: Key<RouterId>) -> bool {
if self.floodfills.iter().any(|floodfill| floodfill.key == key) {
return true;
}
if self.floodfills.len() < 20 {
self.floodfills.push(FloodFill::new(key.preimage().clone()));
return true;
}
if let Some(floodfill) = self.floodfills.iter_mut().min() {
if floodfill.score < 0 {
tracing::trace!(
target: LOG_TARGET,
old_floodfill = %floodfill.key.preimage(),
score = %floodfill.score,
new_floodfill = %key.preimage(),
"evicting floodfill",
);
floodfill.key = key;
floodfill.score = 0;
return true;
}
}
false
}
pub fn adjust_score(&mut self, key: Key<RouterId>, adjustment: isize) {
match self.floodfills.iter_mut().find(|floodfill| floodfill.key == key) {
Some(floodfill) => {
tracing::trace!(
target: LOG_TARGET,
score = %(floodfill.score + adjustment),
"router score adjusted",
);
floodfill.score += adjustment;
}
None => tracing::debug!(
target: LOG_TARGET,
router_id = %key.preimage(),
"cannot adjust score, router doesn't exist in the k-bucket",
),
}
}
pub fn closest_iter<K: Clone>(&self, target: &Key<K>) -> impl Iterator<Item = RouterId> {
let mut floodfills = self.floodfills.clone();
floodfills.sort_by(|a, b| target.distance(&a.key).cmp(&target.distance(&b.key)));
floodfills.into_iter().map(|router| router.key.preimage().clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closest_iter() {
let mut bucket = KBucket::new();
let _ = (0..10)
.map(|_| {
let peer = RouterId::random();
bucket.floodfills.push(FloodFill::new(RouterId::random()));
peer
})
.collect::<Vec<_>>();
let target = Key::from(RouterId::random());
let mut iter = bucket.closest_iter(&target);
let mut prev = None;
while let Some(router_id) = iter.next() {
if let Some(distance) = prev {
assert!(distance < target.distance(&Key::from(router_id.clone())));
}
prev = Some(target.distance(&Key::from(router_id)));
}
}
#[test]
fn floodfill_with_low_score_evicted() {
let mut bucket = KBucket::new();
let floodfills = (0..20)
.map(|_| {
let peer = RouterId::random();
bucket.floodfills.push(FloodFill::new(peer.clone()));
peer
})
.collect::<Vec<_>>();
let router_id = RouterId::random();
let key = Key::from(router_id.clone());
assert!(!bucket.try_insert(key.clone()));
bucket.adjust_score(Key::from(floodfills[0].clone()), -10);
assert!(bucket.try_insert(key));
assert!(!bucket
.floodfills
.iter()
.any(|floodfill| floodfill.key == Key::from(floodfills[0].clone())));
assert!(bucket.floodfills.iter().all(|floodfill| floodfill.score == 0));
}
}