aboutsummaryrefslogtreecommitdiff
path: root/p2p/src/routing_table/bucket.rs
blob: 0f43b1381b29227fe2f20c23a1de65584f1401a5 (plain)
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)
    }
}