Coverage Report

Created: 2024-05-16 12:16

/__w/smoldot/smoldot/repo/lib/src/network/kademlia/kbuckets.rs
Line
Count
Source (jump to first uncovered line)
1
// Smoldot
2
// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
3
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
15
// You should have received a copy of the GNU General Public License
16
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18
//! K-buckets are a collection used to store a partial view of the list of nodes in a
19
//! peer-to-peer network.
20
//!
21
//! # How it works
22
//!
23
//! The k-buckets consist in 256 so-called "buckets", each containing up to `ENTRIES_PER_BUCKET`
24
//! elements. The value for `ENTRIES_PER_BUCKET` is configurable as a parameter of the [`KBuckets`]
25
//! struct, but is typically equal to 20. Therefore, the k-buckets cannot contain more than `256 *
26
//! ENTRIES_PER_BUCKET` elements in total.
27
//!
28
//! The API of the [`KBuckets`] struct is similar to the one of a `key => value` map. In addition
29
//! to its value, each element also contains a [`PeerState`] indicating whether this node is
30
//! connected to the local node.
31
//!
32
//! In order to insert an element, its key is first hashed using the SHA-256 algorithm, then this
33
//! hash is compared with the hash of a `local_key` that was passed at initialization. The
34
//! position of the highest non-matching bit in this comparison determines in which of the 256
35
//! buckets the element will be inserted. It is forbidden to insert the `local_key` itself.
36
//!
37
//! Within a bucket, each new element is inserted one after the other, except that the elements
38
//! whose [`PeerState`] is [`PeerState::Connected`] are always earlier in the list compared to the
39
//! ones whose state is [`PeerState::Disconnected`]. If the state of an element is updated using
40
//! [`OccupiedEntry::set_state`], the elements are re-ordered accordingly.
41
//!
42
//! Each bucket can only contain `ENTRIES_PER_BUCKET` elements. If a bucket is full and contains
43
//! only elements in the [`PeerState::Connected`] state, then no new element can be added. If a
44
//! bucket is full and contains at least one [`PeerState::Disconnected`] elements, then the last
45
//! element in the bucket will expire after a certain time after which it can be replaced with a
46
//! new one.
47
//!
48
//! # Properties
49
//!
50
//! While this data structure is generic, the `local_key` passed at initialization is typically
51
//! the network identity of the local node, and the keys being inserted are typically the network
52
//! identities of the other nodes of the peer-to-peer network.
53
//!
54
//! Assuming that all the network identities that exist are distributed uniformly, the k-buckets
55
//! will hold more network identities that are close to the the local node's network identity, and
56
//! fewer network identities that are far away. In other words, the k-buckets store the neighbors
57
//! of the local node, and a few far-away nodes.
58
//!
59
//! Since all the nodes of the network do the same, one can find all the node whose identity is
60
//! closest to a certain key `K` by doing the following:
61
//!
62
//! - Find in our local k-buckets the node `N` closest to `K`.
63
//! - Ask `N` to look into its own k-buckets what is the node closest to `K`.
64
//! - If `N` has a node `N2` where `distance(K, N2) < distance(K, N)`, then repeat the previous
65
//! step but with `N2`.
66
//! - If no closer node is found, we know that `N` is the node closest to `K` in the network.
67
//!
68
69
use alloc::vec::Vec;
70
use core::{fmt, ops::Add, time::Duration};
71
use sha2::{Digest as _, Sha256};
72
73
/// K-buckets, as popularized by the Kademlia algorithm, and defined by the libp2p specification.
74
pub struct KBuckets<K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
75
    /// Key of the "local" node, that holds the buckets.
76
    local_key: (K, Key),
77
    /// List of buckets, ordered by increasing distance. In other words, the first elements of
78
    /// this field are the ones that are the closest to [`KBuckets::local_key`].
79
    buckets: Vec<Bucket<K, V, TNow, ENTRIES_PER_BUCKET>>,
80
    /// Duration after which the last entry of each bucket will expired if it is disconnected.
81
    pending_timeout: Duration,
82
}
83
84
impl<K, V, TNow, const ENTRIES_PER_BUCKET: usize> KBuckets<K, V, TNow, ENTRIES_PER_BUCKET>
85
where
86
    K: Clone + PartialEq + AsRef<[u8]>,
87
    TNow: Clone + Add<Duration, Output = TNow> + Ord,
88
{
89
1
    pub fn new(local_key: K, pending_timeout: Duration) -> Self {
90
1
        let local_key_hashed = Key::new(local_key.as_ref());
91
1
92
1
        KBuckets {
93
1
            local_key: (local_key, local_key_hashed),
94
1
            buckets: (0..256)
95
256
                .map(|_| Bucket {
96
256
                    entries: arrayvec::ArrayVec::new(),
97
256
                    num_connected_entries: 0,
98
256
                    pending_entry: None,
99
256
                })
_RNCNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB4_8KBucketsINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E3new0Ba_
Line
Count
Source
95
256
                .map(|_| Bucket {
96
256
                    entries: arrayvec::ArrayVec::new(),
97
256
                    num_connected_entries: 0,
98
256
                    pending_entry: None,
99
256
                })
Unexecuted instantiation: _RNCNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE3new0Ba_
100
1
                .collect(),
101
1
            pending_timeout,
102
1
        }
103
1
    }
_RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketsINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E3newB8_
Line
Count
Source
89
1
    pub fn new(local_key: K, pending_timeout: Duration) -> Self {
90
1
        let local_key_hashed = Key::new(local_key.as_ref());
91
1
92
1
        KBuckets {
93
1
            local_key: (local_key, local_key_hashed),
94
1
            buckets: (0..256)
95
1
                .map(|_| Bucket {
96
                    entries: arrayvec::ArrayVec::new(),
97
                    num_connected_entries: 0,
98
                    pending_entry: None,
99
1
                })
100
1
                .collect(),
101
1
            pending_timeout,
102
1
        }
103
1
    }
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE3newB8_
104
105
    /// Returns the local key that was passed to [`KBuckets::new`].
106
0
    pub fn local_key(&self) -> &K {
107
0
        &self.local_key.0
108
0
    }
Unexecuted instantiation: _RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE9local_keyB8_
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE9local_keyB8_
109
110
    /// Returns the value corresponding to the given key. Returns `None` if the key can't be found.
111
0
    pub fn get(&self, key: &K) -> Option<&V> {
112
0
        let key_hashed = Key::new(key.as_ref());
113
0
        let distance = match distance_log2(&self.local_key.1, &key_hashed) {
114
0
            Some(d) => d,
115
0
            None => return None,
116
        };
117
118
0
        self.buckets[usize::from(distance)].get(key)
119
0
    }
Unexecuted instantiation: _RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE3getB8_
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE3getB8_
120
121
    /// Returns the value corresponding to the given key. Returns `None` if the key can't be found.
122
0
    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
123
0
        let key_hashed = Key::new(key.as_ref());
124
0
        let distance = match distance_log2(&self.local_key.1, &key_hashed) {
125
0
            Some(d) => d,
126
0
            None => return None,
127
        };
128
129
0
        self.buckets[usize::from(distance)].get_mut(key)
130
0
    }
Unexecuted instantiation: _RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE7get_mutB8_
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE7get_mutB8_
131
132
    /// Inserts or updates an entry in the buckets.
133
6
    pub fn entry<'a>(&'a mut self, key: &'a K) -> Entry<'a, K, V, TNow, ENTRIES_PER_BUCKET>
134
6
    where
135
6
        K: Clone,
136
6
    {
137
6
        let key_hashed = Key::new(key.as_ref());
138
6
        let distance = match distance_log2(&self.local_key.1, &key_hashed) {
139
6
            Some(d) => d,
140
0
            None => return Entry::LocalKey,
141
        };
142
143
6
        if self.buckets[usize::from(distance)].get_mut(key).is_some() {
144
0
            return Entry::Occupied(OccupiedEntry {
145
0
                inner: self,
146
0
                key,
147
0
                distance,
148
0
            });
149
6
        }
150
6
151
6
        Entry::Vacant(VacantEntry {
152
6
            inner: self,
153
6
            key,
154
6
            distance,
155
6
        })
156
6
    }
_RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketsINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E5entryB8_
Line
Count
Source
133
6
    pub fn entry<'a>(&'a mut self, key: &'a K) -> Entry<'a, K, V, TNow, ENTRIES_PER_BUCKET>
134
6
    where
135
6
        K: Clone,
136
6
    {
137
6
        let key_hashed = Key::new(key.as_ref());
138
6
        let distance = match distance_log2(&self.local_key.1, &key_hashed) {
139
6
            Some(d) => d,
140
0
            None => return Entry::LocalKey,
141
        };
142
143
6
        if self.buckets[usize::from(distance)].get_mut(key).is_some() {
144
0
            return Entry::Occupied(OccupiedEntry {
145
0
                inner: self,
146
0
                key,
147
0
                distance,
148
0
            });
149
6
        }
150
6
151
6
        Entry::Vacant(VacantEntry {
152
6
            inner: self,
153
6
            key,
154
6
            distance,
155
6
        })
156
6
    }
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE5entryB8_
157
158
    /// Returns the list of entries in the k-buckets, ordered by increasing distance with the
159
    /// target.
160
0
    pub fn closest_entries(&self, target: &K) -> impl Iterator<Item = (&K, &V)> {
161
0
        // TODO: this is extremely unoptimized
162
0
        let target_hashed = Key::new(target.as_ref());
163
0
        let mut list = self.iter_ordered().collect::<Vec<_>>();
164
0
        list.sort_by_key(|(key, _)| {
165
0
            let key_hashed = Key::new(key.as_ref());
166
0
            distance_log2(&key_hashed, &target_hashed).map_or(0, |d| u16::from(d) + 1)
Unexecuted instantiation: _RNCNCNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE15closest_entries00Bc_
Unexecuted instantiation: _RNCNCNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE15closest_entries00Bc_
167
0
        });
Unexecuted instantiation: _RNCNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE15closest_entries0Ba_
Unexecuted instantiation: _RNCNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE15closest_entries0Ba_
168
0
        list.into_iter()
169
0
    }
Unexecuted instantiation: _RNvMNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE15closest_entriesB8_
Unexecuted instantiation: _RNvMNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB2_8KBucketspppKpE15closest_entriesB8_
170
}
171
172
impl<K, V, TNow, const ENTRIES_PER_BUCKET: usize> KBuckets<K, V, TNow, ENTRIES_PER_BUCKET> {
173
    /// Iterates over all the peers in the k-buckets.
174
    ///
175
    /// The buckets are iterated one by one from closest to furthest away, and within each bucket
176
    /// elements are ordered by descending time since connectivity.
177
0
    pub fn iter_ordered(&self) -> impl Iterator<Item = (&K, &V)> {
178
0
        self.buckets
179
0
            .iter()
180
0
            .flat_map(|b| b.entries.iter().map(|(k, v)| (k, v)))
Unexecuted instantiation: _RNCNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE12iter_ordered0Bc_
Unexecuted instantiation: _RNCNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE12iter_ordered0Bc_
Unexecuted instantiation: _RNCNCNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB8_8KBucketspppKpE12iter_ordered00Be_
Unexecuted instantiation: _RNCNCNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB8_8KBucketspppKpE12iter_ordered00Be_
181
0
    }
Unexecuted instantiation: _RNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE12iter_orderedBa_
Unexecuted instantiation: _RNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE12iter_orderedBa_
182
183
    /// Iterates over all the peers in the k-buckets.
184
    ///
185
    /// The buckets are iterated one by one from closest to furthest away, and within each bucket
186
    /// elements are ordered by descending time since connectivity.
187
0
    pub fn iter_mut_ordered(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
188
0
        self.buckets
189
0
            .iter_mut()
190
0
            .flat_map(|b| b.entries.iter_mut().map(|(k, v)| (&*k, v)))
Unexecuted instantiation: _RNCNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE16iter_mut_ordered0Bc_
Unexecuted instantiation: _RNCNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB6_8KBucketspppKpE16iter_mut_ordered0Bc_
Unexecuted instantiation: _RNCNCNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB8_8KBucketspppKpE16iter_mut_ordered00Be_
Unexecuted instantiation: _RNCNCNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB8_8KBucketspppKpE16iter_mut_ordered00Be_
191
0
    }
Unexecuted instantiation: _RNvMs_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE16iter_mut_orderedBa_
Unexecuted instantiation: _RNvMs_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB4_8KBucketspppKpE16iter_mut_orderedBa_
192
}
193
194
impl<K, V, TNow, const ENTRIES_PER_BUCKET: usize> fmt::Debug
195
    for KBuckets<K, V, TNow, ENTRIES_PER_BUCKET>
196
where
197
    K: fmt::Debug,
198
    V: fmt::Debug,
199
{
200
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201
0
        f.debug_list().entries(self.iter_ordered()).finish()
202
0
    }
Unexecuted instantiation: _RNvXININtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketss0_0pppKpEINtB5_8KBucketspppKpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtBb_
Unexecuted instantiation: _RNvXININtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketss0_0pppKpEINtB5_8KBucketspppKpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtBb_
203
}
204
205
pub enum Entry<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
206
    /// Requested key is the same as local key. The local key is never present in the k-buckets.
207
    LocalKey,
208
    Vacant(VacantEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>),
209
    Occupied(OccupiedEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>),
210
}
211
212
impl<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize> Entry<'a, K, V, TNow, ENTRIES_PER_BUCKET>
213
where
214
    K: Clone + PartialEq + AsRef<[u8]>,
215
    TNow: Clone + Add<Duration, Output = TNow> + Ord,
216
{
217
    /// If `self` is [`Entry::Occupied`], returns the inner [`OccupiedEntry`]. Otherwise returns
218
    /// `None`.
219
0
    pub fn into_occupied(self) -> Option<OccupiedEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>> {
220
0
        match self {
221
0
            Entry::LocalKey | Entry::Vacant(_) => None,
222
0
            Entry::Occupied(e) => Some(e),
223
        }
224
0
    }
Unexecuted instantiation: _RNvMs1_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_5EntrypppKpE13into_occupiedBb_
Unexecuted instantiation: _RNvMs1_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_5EntrypppKpE13into_occupiedBb_
225
226
0
    pub fn or_insert(
227
0
        self,
228
0
        value: V,
229
0
        now: &TNow,
230
0
        state: PeerState,
231
0
    ) -> Result<OccupiedEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>, OrInsertError> {
232
0
        match self {
233
0
            Entry::LocalKey => Err(OrInsertError::LocalKey),
234
0
            Entry::Vacant(v) => match v.insert(value, now, state) {
235
0
                Ok(InsertResult { entry, .. }) => Ok(entry),
236
0
                Err(InsertError::Full) => Err(OrInsertError::Full),
237
            },
238
0
            Entry::Occupied(e) => Ok(e),
239
        }
240
0
    }
Unexecuted instantiation: _RNvMs1_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_5EntrypppKpE9or_insertBb_
Unexecuted instantiation: _RNvMs1_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_5EntrypppKpE9or_insertBb_
241
}
242
243
/// Error that can happen in [`Entry::or_insert`].
244
0
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
Unexecuted instantiation: _RNvXsb_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsNtB5_13OrInsertErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
Unexecuted instantiation: _RNvXsb_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsNtB5_13OrInsertErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
245
pub enum OrInsertError {
246
    /// K-bucket is full.
247
    Full,
248
    /// Can't insert the local key into the k-buckets.
249
    LocalKey,
250
}
251
252
pub struct VacantEntry<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
253
    inner: &'a mut KBuckets<K, V, TNow, ENTRIES_PER_BUCKET>,
254
    key: &'a K,
255
    distance: u8,
256
}
257
258
/// See [`VacantEntry::insert`].
259
pub struct InsertResult<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
260
    /// Entry that was just inserted.
261
    pub entry: OccupiedEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>,
262
    /// Entry that had to be removed in order to make space for the newly-inserted element, if
263
    /// any. This removed entry was always in a disconnected state.
264
    pub removed_entry: Option<(K, V)>,
265
}
266
267
impl<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize>
268
    VacantEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>
269
where
270
    K: Clone + PartialEq + AsRef<[u8]>,
271
    TNow: Clone + Add<Duration, Output = TNow> + Ord,
272
{
273
    /// Inserts the entry in the vacant slot. Returns an error if the k-buckets are full.
274
6
    pub fn insert(
275
6
        self,
276
6
        value: V,
277
6
        now: &TNow,
278
6
        state: PeerState,
279
6
    ) -> Result<InsertResult<'a, K, V, TNow, ENTRIES_PER_BUCKET>, InsertError> {
280
6
        let bucket = &mut self.inner.buckets[usize::from(self.distance)];
281
282
6
        let 
removed_entry5
= match state {
283
0
            PeerState::Connected if bucket.num_connected_entries < ENTRIES_PER_BUCKET => {
284
0
                let mut previous_entry = None;
285
0
286
0
                if bucket.entries.is_full() {
287
0
                    previous_entry = bucket.entries.pop();
288
0
                    debug_assert!(previous_entry.is_some());
289
0
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
290
0
                }
291
292
0
                bucket
293
0
                    .entries
294
0
                    .insert(bucket.num_connected_entries, (self.key.clone(), value));
295
0
                bucket.num_connected_entries += 1;
296
0
297
0
                if bucket.num_connected_entries == ENTRIES_PER_BUCKET {
298
0
                    bucket.pending_entry = None;
299
0
                }
300
301
0
                previous_entry
302
            }
303
            PeerState::Connected => {
304
0
                debug_assert!(bucket.entries.is_full());
305
0
                debug_assert_eq!(bucket.num_connected_entries, ENTRIES_PER_BUCKET);
306
0
                debug_assert!(bucket.pending_entry.is_none());
307
0
                return Err(InsertError::Full);
308
            }
309
6
            PeerState::Disconnected if bucket.entries.is_full() => {
310
2
                if bucket.num_connected_entries == ENTRIES_PER_BUCKET {
311
0
                    return Err(InsertError::Full);
312
2
                }
313
2
314
2
                if *bucket.pending_entry.as_ref().unwrap() > *now {
315
1
                    return Err(InsertError::Full);
316
1
                }
317
1
318
1
                let previous_entry = bucket.entries.pop();
319
1
                bucket.entries.push((self.key.clone(), value));
320
1
                bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
321
1
                previous_entry
322
            }
323
            PeerState::Disconnected => {
324
4
                debug_assert!(!bucket.entries.is_full());
325
4
                debug_assert!(bucket.pending_entry.is_none());
326
4
                bucket.entries.push((self.key.clone(), value));
327
4
328
4
                if bucket.entries.is_full() {
329
1
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
330
3
                }
331
332
4
                None
333
            }
334
        };
335
336
5
        Ok(InsertResult {
337
5
            entry: OccupiedEntry {
338
5
                inner: self.inner,
339
5
                key: self.key,
340
5
                distance: self.distance,
341
5
            },
342
5
            removed_entry,
343
5
        })
344
6
    }
_RNvMs2_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_11VacantEntryINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E6insertBb_
Line
Count
Source
274
6
    pub fn insert(
275
6
        self,
276
6
        value: V,
277
6
        now: &TNow,
278
6
        state: PeerState,
279
6
    ) -> Result<InsertResult<'a, K, V, TNow, ENTRIES_PER_BUCKET>, InsertError> {
280
6
        let bucket = &mut self.inner.buckets[usize::from(self.distance)];
281
282
6
        let 
removed_entry5
= match state {
283
0
            PeerState::Connected if bucket.num_connected_entries < ENTRIES_PER_BUCKET => {
284
0
                let mut previous_entry = None;
285
0
286
0
                if bucket.entries.is_full() {
287
0
                    previous_entry = bucket.entries.pop();
288
0
                    debug_assert!(previous_entry.is_some());
289
0
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
290
0
                }
291
292
0
                bucket
293
0
                    .entries
294
0
                    .insert(bucket.num_connected_entries, (self.key.clone(), value));
295
0
                bucket.num_connected_entries += 1;
296
0
297
0
                if bucket.num_connected_entries == ENTRIES_PER_BUCKET {
298
0
                    bucket.pending_entry = None;
299
0
                }
300
301
0
                previous_entry
302
            }
303
            PeerState::Connected => {
304
0
                debug_assert!(bucket.entries.is_full());
305
0
                debug_assert_eq!(bucket.num_connected_entries, ENTRIES_PER_BUCKET);
306
0
                debug_assert!(bucket.pending_entry.is_none());
307
0
                return Err(InsertError::Full);
308
            }
309
6
            PeerState::Disconnected if bucket.entries.is_full() => {
310
2
                if bucket.num_connected_entries == ENTRIES_PER_BUCKET {
311
0
                    return Err(InsertError::Full);
312
2
                }
313
2
314
2
                if *bucket.pending_entry.as_ref().unwrap() > *now {
315
1
                    return Err(InsertError::Full);
316
1
                }
317
1
318
1
                let previous_entry = bucket.entries.pop();
319
1
                bucket.entries.push((self.key.clone(), value));
320
1
                bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
321
1
                previous_entry
322
            }
323
            PeerState::Disconnected => {
324
4
                debug_assert!(!bucket.entries.is_full());
325
4
                debug_assert!(bucket.pending_entry.is_none());
326
4
                bucket.entries.push((self.key.clone(), value));
327
4
328
4
                if bucket.entries.is_full() {
329
1
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
330
3
                }
331
332
4
                None
333
            }
334
        };
335
336
5
        Ok(InsertResult {
337
5
            entry: OccupiedEntry {
338
5
                inner: self.inner,
339
5
                key: self.key,
340
5
                distance: self.distance,
341
5
            },
342
5
            removed_entry,
343
5
        })
344
6
    }
Unexecuted instantiation: _RNvMs2_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_11VacantEntrypppKpE6insertBb_
345
}
346
347
/// Error that can happen in [`VacantEntry::insert`].
348
0
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
Unexecuted instantiation: _RNvXsh_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsNtB5_11InsertErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
Unexecuted instantiation: _RNvXsh_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsNtB5_11InsertErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
349
pub enum InsertError {
350
    /// K-bucket is full.
351
    Full,
352
}
353
354
pub struct OccupiedEntry<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
355
    inner: &'a mut KBuckets<K, V, TNow, ENTRIES_PER_BUCKET>,
356
    key: &'a K,
357
    distance: u8,
358
}
359
360
impl<'a, K, V, TNow, const ENTRIES_PER_BUCKET: usize>
361
    OccupiedEntry<'a, K, V, TNow, ENTRIES_PER_BUCKET>
362
where
363
    K: Clone + PartialEq + AsRef<[u8]>,
364
    TNow: Clone + Add<Duration, Output = TNow> + Ord,
365
{
366
    /// Updates the state of this entry.
367
0
    pub fn set_state(&mut self, now: &TNow, state: PeerState) {
368
0
        let bucket = &mut self.inner.buckets[usize::from(self.distance)];
369
0
        let position = bucket
370
0
            .entries
371
0
            .iter()
372
0
            .position(|(k, _)| *k == *self.key)
Unexecuted instantiation: _RNCNvMs3_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB7_13OccupiedEntrypppKpE9set_state0Bd_
Unexecuted instantiation: _RNCNvMs3_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB7_13OccupiedEntrypppKpE9set_state0Bd_
373
0
            .unwrap();
374
375
0
        match state {
376
0
            PeerState::Connected if position >= bucket.num_connected_entries => {
377
0
                debug_assert!(bucket.num_connected_entries < ENTRIES_PER_BUCKET);
378
0
                let entry = bucket.entries.remove(position);
379
0
                bucket.entries.insert(bucket.num_connected_entries, entry);
380
0
                bucket.num_connected_entries += 1;
381
0
382
0
                // If the peer we switch from disconnected to connected was the last one, reset
383
0
                // the expiration.
384
0
                if position == bucket.entries.capacity() - 1 {
385
0
                    debug_assert!(bucket.pending_entry.is_some());
386
0
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
387
0
                }
388
            }
389
390
0
            PeerState::Disconnected if position < bucket.num_connected_entries => {
391
0
                let entry = bucket.entries.remove(position);
392
0
                bucket.num_connected_entries -= 1;
393
0
                bucket.entries.insert(bucket.num_connected_entries, entry);
394
0
395
0
                // If the peer we switch from connected to disconnected is now the last one, start
396
0
                // the expiration.
397
0
                if bucket.num_connected_entries == bucket.entries.capacity() - 1 {
398
0
                    debug_assert!(bucket.pending_entry.is_none());
399
0
                    bucket.pending_entry = Some(now.clone() + self.inner.pending_timeout);
400
0
                }
401
            }
402
403
0
            _ => {}
404
        }
405
0
    }
Unexecuted instantiation: _RNvMs3_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_13OccupiedEntrypppKpE9set_stateBb_
Unexecuted instantiation: _RNvMs3_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_13OccupiedEntrypppKpE9set_stateBb_
406
407
0
    pub fn get_mut(&mut self) -> &mut V {
408
0
        self.inner.buckets[usize::from(self.distance)]
409
0
            .get_mut(self.key)
410
0
            .unwrap()
411
0
    }
Unexecuted instantiation: _RNvMs3_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_13OccupiedEntrypppKpE7get_mutBb_
Unexecuted instantiation: _RNvMs3_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_13OccupiedEntrypppKpE7get_mutBb_
412
}
413
414
pub enum PeerState {
415
    Connected,
416
    Disconnected,
417
}
418
419
struct Bucket<K, V, TNow, const ENTRIES_PER_BUCKET: usize> {
420
    /// List of entries in the bucket. Ordered by decreasing importance. The first entries in
421
    /// the list are the ones we've been connected to for the longest time, while the last entries
422
    /// are the ones we've disconnected from for a long time.
423
    entries: arrayvec::ArrayVec<(K, V), ENTRIES_PER_BUCKET>,
424
    /// Number of entries in the [`Bucket::entries`] that are in the [`PeerState::Connected`]
425
    /// state.
426
    num_connected_entries: usize,
427
    /// If `Some`, the last entry in [`Bucket::entries`] is going to get kicked out after `TNow`.
428
    /// Always `None` if [`Bucket::entries`] isn't full or contains only connected entries.
429
    pending_entry: Option<TNow>,
430
}
431
432
impl<K, V, TNow, const ENTRIES_PER_BUCKET: usize> Bucket<K, V, TNow, ENTRIES_PER_BUCKET>
433
where
434
    K: PartialEq,
435
{
436
0
    fn get(&self, key: &K) -> Option<&V> {
437
0
        if let Some((_, value)) = self.entries.iter().find(|e| e.0 == *key) {
Unexecuted instantiation: _RNCNvMs4_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB7_6BucketpppKpE3get0Bd_
Unexecuted instantiation: _RNCNvMs4_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB7_6BucketpppKpE3get0Bd_
438
0
            return Some(value);
439
0
        }
440
0
441
0
        // TODO: check expiration?
442
0
443
0
        None
444
0
    }
Unexecuted instantiation: _RNvMs4_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_6BucketpppKpE3getBb_
Unexecuted instantiation: _RNvMs4_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_6BucketpppKpE3getBb_
445
446
6
    fn get_mut(&mut self, key: &K) -> Option<&mut V> {
447
14
        if let Some((_, 
value0
)) =
self.entries.iter_mut().find(6
|e| e.0 == *key
)6
{
_RNCNvMs4_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB7_6BucketINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E7get_mut0Bd_
Line
Count
Source
447
14
        if let Some((_, value)) = self.entries.iter_mut().find(|e| e.0 == *key) {
Unexecuted instantiation: _RNCNvMs4_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB7_6BucketpppKpE7get_mut0Bd_
448
0
            return Some(value);
449
6
        }
450
6
451
6
        // TODO: check expiration?
452
6
453
6
        None
454
6
    }
_RNvMs4_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsINtB5_6BucketINtNtCsdZExvAaxgia_5alloc3vec3VechEuNtNtCsaYZPK01V26L_4core4time8DurationKj4_E7get_mutBb_
Line
Count
Source
446
6
    fn get_mut(&mut self, key: &K) -> Option<&mut V> {
447
6
        if let Some((_, 
value0
)) = self.entries.iter_mut().find(|e| e.0 == *key) {
448
0
            return Some(value);
449
6
        }
450
6
451
6
        // TODO: check expiration?
452
6
453
6
        None
454
6
    }
Unexecuted instantiation: _RNvMs4_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsINtB5_6BucketpppKpE7get_mutBb_
455
}
456
457
/// Key entry in a bucket.
458
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
459
struct Key {
460
    digest: [u8; 32],
461
}
462
463
impl Key {
464
9
    fn new(value: &[u8]) -> Self {
465
9
        Self {
466
9
            digest: Sha256::digest(value).into(),
467
9
        }
468
9
    }
_RNvMs5_NtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbucketsNtB5_3Key3new
Line
Count
Source
464
9
    fn new(value: &[u8]) -> Self {
465
9
        Self {
466
9
            digest: Sha256::digest(value).into(),
467
9
        }
468
9
    }
Unexecuted instantiation: _RNvMs5_NtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbucketsNtB5_3Key3new
469
470
    #[cfg(test)] // TODO: #[cfg(test)] is a bit crappy; figure out
471
6
    fn from_sha256_hash(hash: [u8; 32]) -> Self {
472
6
        Self { digest: hash }
473
6
    }
474
}
475
476
/// Returns the `log2` distance between two keys. Returns `None` if the distance is zero.
477
10
fn distance_log2(a: &Key, b: &Key) -> Option<u8> {
478
73
    for 
n72
in 0..32 {
479
72
        let a = a.digest[n];
480
72
        let b = b.digest[n];
481
72
        let xor_leading_zeroes = (a ^ b).leading_zeros();
482
72
        if xor_leading_zeroes == 8 {
483
63
            continue;
484
9
        }
485
9
486
9
        let xor_distance = u32::try_from((31 - n) * 8).unwrap() + (8 - xor_leading_zeroes);
487
9
        debug_assert!(xor_distance > 0);
488
9
        debug_assert!(xor_distance <= 256);
489
9
        return Some(u8::try_from(xor_distance - 1).unwrap());
490
    }
491
492
1
    None
493
10
}
_RNvNtNtNtCsN16ciHI6Qf_7smoldot7network8kademlia8kbuckets13distance_log2
Line
Count
Source
477
10
fn distance_log2(a: &Key, b: &Key) -> Option<u8> {
478
73
    for 
n72
in 0..32 {
479
72
        let a = a.digest[n];
480
72
        let b = b.digest[n];
481
72
        let xor_leading_zeroes = (a ^ b).leading_zeros();
482
72
        if xor_leading_zeroes == 8 {
483
63
            continue;
484
9
        }
485
9
486
9
        let xor_distance = u32::try_from((31 - n) * 8).unwrap() + (8 - xor_leading_zeroes);
487
9
        debug_assert!(xor_distance > 0);
488
9
        debug_assert!(xor_distance <= 256);
489
9
        return Some(u8::try_from(xor_distance - 1).unwrap());
490
    }
491
492
1
    None
493
10
}
Unexecuted instantiation: _RNvNtNtNtCseuYC0Zibziv_7smoldot7network8kademlia8kbuckets13distance_log2
494
495
#[cfg(test)]
496
mod tests {
497
    use core::time::Duration;
498
    use sha2::{Digest as _, Sha256};
499
500
    #[test]
501
1
    fn basic_distance_1() {
502
1
        let a = super::Key::from_sha256_hash([
503
1
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
504
1
            0, 0, 0,
505
1
        ]);
506
1
507
1
        let b = super::Key::from_sha256_hash([
508
1
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
509
1
            0, 0, 1,
510
1
        ]);
511
1
512
1
        assert_eq!(super::distance_log2(&a, &b), Some(0));
513
1
    }
514
515
    #[test]
516
1
    fn basic_distance_2() {
517
1
        let a = super::Key::from_sha256_hash([
518
1
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
519
1
            0, 0, 0,
520
1
        ]);
521
1
522
1
        let b = super::Key::from_sha256_hash([
523
1
            0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
524
1
            0, 0, 0, 0,
525
1
        ]);
526
1
527
1
        assert_eq!(super::distance_log2(&a, &b), Some(255));
528
1
    }
529
530
    #[test]
531
1
    fn basic_distance_3() {
532
1
        let a = super::Key::from_sha256_hash([
533
1
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
534
1
            0, 0, 0,
535
1
        ]);
536
1
537
1
        let b = super::Key::from_sha256_hash([
538
1
            0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 6, 5, 7, 94, 103, 94, 26, 20, 0, 0,
539
1
            1, 37, 198, 200, 57, 33, 32,
540
1
        ]);
541
1
542
1
        assert_eq!(super::distance_log2(&a, &b), Some(255));
543
1
    }
544
545
    #[test]
546
1
    fn distance_of_zero() {
547
1
        let a = super::Key::new(&[1, 2, 3, 4]);
548
1
        let b = super::Key::new(&[1, 2, 3, 4]);
549
1
        assert_eq!(super::distance_log2(&a, &b), None);
550
1
    }
551
552
    #[test]
553
1
    fn nodes_kicked_out() {
554
1
        let local_key = vec![0u8; 4];
555
1
556
1
        // Iterator that generates random keys that are in the maximum size bucket.
557
1
        let mut max_bucket_keys = {
558
1
            let local_key_hash = Sha256::digest(&local_key);
559
6
            (0..).map(move |_| loop {
560
11
                let other_key: [u8; 32] = rand::random();
561
11
                let other_key_hashed = Sha256::digest(other_key);
562
11
                if ((local_key_hash[0] ^ other_key_hashed[0]) & 0x80) != 0 {
563
6
                    break other_key.to_vec();
564
5
                }
565
6
            })
566
1
        };
567
1
568
1
        let mut buckets = super::KBuckets::<_, _, _, 4>::new(local_key, Duration::from_secs(1));
569
570
        // Insert 4 nodes in the bucket of maximum distance. Since there's only capacity for 4,
571
        // the last one is in pending mode.
572
5
        for _ in 0..4 {
573
4
            match buckets.entry(&max_bucket_keys.next().unwrap()) {
574
4
                super::Entry::Vacant(e) => {
575
4
                    e.insert((), &Duration::new(0, 0), super::PeerState::Disconnected)
576
4
                        .unwrap();
577
4
                }
578
0
                _ => panic!(),
579
            }
580
        }
581
582
        // Inserting another node in that bucket. Since it's full, the insertion must fail.
583
1
        match buckets.entry(&max_bucket_keys.next().unwrap()) {
584
1
            super::Entry::Vacant(e) => {
585
1
                assert!(e
586
1
                    .insert((), &Duration::new(0, 0), super::PeerState::Disconnected)
587
1
                    .is_err());
588
            }
589
0
            _ => panic!(),
590
        }
591
592
        // Try again, but this time after the pending node's expiration has passed. This time,
593
        // the insertion must succeed.
594
1
        match buckets.entry(&max_bucket_keys.next().unwrap()) {
595
1
            super::Entry::Vacant(e) => {
596
1
                assert!(e
597
1
                    .insert((), &Duration::new(2, 0), super::PeerState::Disconnected)
598
1
                    .is_ok());
599
            }
600
0
            _ => panic!(),
601
        }
602
1
    }
603
604
    // TODO: a lot of tests
605
}