karyon_p2p/routing_table/bucket.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
use super::{Entry, Key};
use rand::{rngs::OsRng, seq::SliceRandom};
/// BITFLAGS represent the status of an Entry within a bucket.
pub type EntryStatusFlag = u16;
/// The entry is connected.
pub const CONNECTED_ENTRY: EntryStatusFlag = 0b000001;
/// The entry is disconnected. This will increase the failure counter.
pub const DISCONNECTED_ENTRY: EntryStatusFlag = 0b000010;
/// The entry is ready to reconnect, meaning it has either been added and
/// has no connection attempts, or it has been refreshed.
pub const PENDING_ENTRY: EntryStatusFlag = 0b000100;
/// The entry is unreachable. This will increase the failure counter.
pub const UNREACHABLE_ENTRY: EntryStatusFlag = 0b001000;
/// The entry is unstable. This will increase the failure counter.
pub const UNSTABLE_ENTRY: EntryStatusFlag = 0b010000;
/// The entry is incompatible. This entry will not contribute to an increase in
/// failure attempts, instead, it will persist in the routing table for the
/// lookup process and will only be removed in the presence of a new entry.
pub const INCOMPATIBLE_ENTRY: EntryStatusFlag = 0b100000;
#[allow(dead_code)]
pub const ALL_ENTRY: EntryStatusFlag = 0b111111;
/// A BucketEntry represents a peer in the routing table.
#[derive(Clone, Debug)]
pub struct BucketEntry {
pub status: EntryStatusFlag,
pub entry: Entry,
pub failures: u32,
pub last_seen: i64,
}
impl BucketEntry {
pub fn is_connected(&self) -> bool {
self.status ^ CONNECTED_ENTRY == 0
}
pub fn is_incompatible(&self) -> bool {
self.status ^ INCOMPATIBLE_ENTRY == 0
}
pub fn is_unreachable(&self) -> bool {
self.status ^ UNREACHABLE_ENTRY == 0
}
pub fn is_unstable(&self) -> bool {
self.status ^ UNSTABLE_ENTRY == 0
}
}
/// The number of entries that can be stored within a single bucket.
pub const BUCKET_SIZE: usize = 20;
/// A Bucket represents a group of entries in the routing table.
#[derive(Debug, Clone)]
pub struct Bucket {
entries: Vec<BucketEntry>,
}
impl Bucket {
/// Creates a new empty Bucket
pub fn new() -> Self {
Self {
entries: Vec::with_capacity(BUCKET_SIZE),
}
}
/// Add an entry to the bucket.
pub fn add(&mut self, entry: &Entry) {
self.entries.push(BucketEntry {
status: PENDING_ENTRY,
entry: entry.clone(),
failures: 0,
last_seen: chrono::Utc::now().timestamp(),
})
}
/// Get the number of entries in the bucket.
pub fn len(&self) -> usize {
self.entries.len()
}
/// Returns an iterator over the entries in the bucket.
pub fn iter(&self) -> impl Iterator<Item = &BucketEntry> {
self.entries.iter()
}
/// Remove an entry.
pub fn remove(&mut self, key: &Key) {
let position = self.entries.iter().position(|e| &e.entry.key == key);
if let Some(i) = position {
self.entries.remove(i);
}
}
/// Returns an iterator of entries in random order.
pub fn random_iter(&self, amount: usize) -> impl Iterator<Item = &BucketEntry> {
self.entries.choose_multiple(&mut OsRng, amount)
}
/// Updates the status of an entry in the bucket identified by the given key.
///
/// If the key is not found in the bucket, no action is taken.
///
/// This will also update the last_seen field and increase the failures
/// counter for the bucket entry according to the new status.
pub fn update_entry(&mut self, key: &Key, entry_flag: EntryStatusFlag) {
if let Some(e) = self.entries.iter_mut().find(|e| &e.entry.key == key) {
e.status = entry_flag;
if e.is_unreachable() || e.is_unstable() {
e.failures += 1;
}
if !e.is_unreachable() {
e.last_seen = chrono::Utc::now().timestamp();
}
}
}
/// Check if the bucket contains the given key.
pub fn contains_key(&self, key: &Key) -> bool {
self.entries.iter().any(|e| &e.entry.key == key)
}
}