/__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 | | } |