karyon_p2p/routing_table/
bucket.rs

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