Implement fast userspace multihop mesh routing
Replace the one-intermediate-peer relay with protocol-scoped connectivity graphs and precomputed shortest-path/ECMP forwarding snapshots. Independent transport readers forward opaque transit frames without a plugin or TUN round trip. Carry source, destination, a bounded hop limit and stable flow tags; preserve end-to-end WireGuard links across topology changes. Classify IP flows before encryption and preserve their tags through the WireGuard pending queue. Add offline four-agent path-change coverage, loop and isolation tests, and an opt-in release forwarding microbenchmark. Bump control/data ALPNs while preserving persistent network identities and state. Also include the pending Windows Mainline idle-timeout fix and its regression test, using a reproducible vendored dependency patch. Validation: fmt, workspace Clippy with warnings denied, and 307 release tests passed. Two pre-existing Windows SQLite wipe failures were excluded; public DHT and the manual benchmark remain ignored by default. The forwarding microbenchmark measured 103 ns (64 B) and 202 ns (1280 B) per transit packet, excluding encryption and socket I/O.
This commit is contained in:
1 parent
d1a0eca723
commit
b4f3e57c8d
50 files changed
+11073
-857
No files matched your search
Vendored
+354
@@ -0,0 +1,354 @@
|
||||
//! Kademlia node Id or a lookup target
|
||||
use crc::{Crc, CRC_32_ISCSI};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// The size of node IDs in bits.
|
||||
pub const ID_SIZE: usize = 20;
|
||||
pub const MAX_DISTANCE: u8 = ID_SIZE as u8 * 8;
|
||||
|
||||
const IPV4_MASK: u32 = 0x030f3fff;
|
||||
const CASTAGNOLI: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Ord, PartialOrd, Eq, Hash, Serialize, Deserialize)]
|
||||
/// Kademlia node Id or a lookup target
|
||||
pub struct Id([u8; ID_SIZE]);
|
||||
|
||||
impl Id {
|
||||
/// Generate a random Id
|
||||
pub fn random() -> Id {
|
||||
let mut bytes: [u8; 20] = [0; 20];
|
||||
getrandom::fill(&mut bytes).expect("getrandom");
|
||||
Id(bytes)
|
||||
}
|
||||
|
||||
/// Create a new Id from some bytes. Returns Err if the input is not 20 bytes long.
|
||||
pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Id, InvalidIdSize> {
|
||||
let bytes = bytes.as_ref();
|
||||
if bytes.len() != ID_SIZE {
|
||||
return Err(InvalidIdSize(bytes.len()));
|
||||
}
|
||||
|
||||
let mut tmp: [u8; ID_SIZE] = [0; ID_SIZE];
|
||||
tmp[..ID_SIZE].clone_from_slice(&bytes[..ID_SIZE]);
|
||||
|
||||
Ok(Id(tmp))
|
||||
}
|
||||
|
||||
/// Simplified XOR distance between this Id and a target Id.
|
||||
///
|
||||
/// The distance is the number of trailing non zero bits in the XOR result.
|
||||
///
|
||||
/// Distance to self is 0
|
||||
/// Distance to the furthest Id is 160
|
||||
/// Distance to an Id with 5 leading matching bits is 155
|
||||
pub fn distance(&self, other: &Id) -> u8 {
|
||||
MAX_DISTANCE - self.xor(other).leading_zeros()
|
||||
}
|
||||
|
||||
/// Returns the number of leading zeros in the binary representation of `self`.
|
||||
pub fn leading_zeros(&self) -> u8 {
|
||||
for (i, byte) in self.0.iter().enumerate() {
|
||||
if *byte != 0 {
|
||||
// leading zeros so far + laedinge zeros of this byte
|
||||
return (i as u32 * 8 + byte.leading_zeros()) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
160
|
||||
}
|
||||
|
||||
/// Performs bitwise XOR between two Ids
|
||||
pub fn xor(&self, other: &Id) -> Id {
|
||||
let mut result = [0_u8; 20];
|
||||
|
||||
for (i, (a, b)) in self.0.iter().zip(other.0).enumerate() {
|
||||
result[i] = a ^ b;
|
||||
}
|
||||
|
||||
result.into()
|
||||
}
|
||||
|
||||
/// Returns a byte slice of this Id.
|
||||
pub fn as_bytes(&self) -> &[u8; 20] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Create a new Id according to [BEP_0042](http://bittorrent.org/beps/bep_0042.html).
|
||||
pub fn from_addr(addr: &SocketAddr) -> Id {
|
||||
let ip = addr.ip();
|
||||
|
||||
Id::from_ip(ip)
|
||||
}
|
||||
|
||||
/// Create a new Id from an Ipv4 address according to [BEP_0042](http://bittorrent.org/beps/bep_0042.html).
|
||||
pub fn from_ip(ip: IpAddr) -> Id {
|
||||
match ip {
|
||||
IpAddr::V4(addr) => Id::from_ipv4(addr),
|
||||
IpAddr::V6(_addr) => unimplemented!("Ipv6 is not supported"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Id from an Ipv4 address according to [BEP_0042](http://bittorrent.org/beps/bep_0042.html).
|
||||
pub fn from_ipv4(ipv4: Ipv4Addr) -> Id {
|
||||
let mut bytes = [0_u8; 21];
|
||||
getrandom::fill(&mut bytes).expect("getrandom");
|
||||
|
||||
from_ipv4_and_r(bytes[1..].try_into().expect("infallible"), ipv4, bytes[0])
|
||||
}
|
||||
|
||||
/// Validate that this Id is valid with respect to [BEP_0042](http://bittorrent.org/beps/bep_0042.html).
|
||||
pub fn is_valid_for_ip(&self, ipv4: Ipv4Addr) -> bool {
|
||||
if ipv4.is_private() || ipv4.is_link_local() || ipv4.is_loopback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let expected = first_21_bits(&id_prefix_ipv4(ipv4, self.0[ID_SIZE - 1]));
|
||||
|
||||
self.first_21_bits() == expected
|
||||
}
|
||||
|
||||
pub(crate) fn first_21_bits(&self) -> [u8; 3] {
|
||||
first_21_bits(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn first_21_bits(bytes: &[u8]) -> [u8; 3] {
|
||||
[bytes[0], bytes[1], bytes[2] & 0xf8]
|
||||
}
|
||||
|
||||
fn from_ipv4_and_r(bytes: [u8; 20], ip: Ipv4Addr, r: u8) -> Id {
|
||||
let mut bytes = bytes;
|
||||
let prefix = id_prefix_ipv4(ip, r);
|
||||
|
||||
// Set first 21 bits to the prefix
|
||||
bytes[0] = prefix[0];
|
||||
bytes[1] = prefix[1];
|
||||
// set the first 5 bits of the 3rd byte to the remaining 5 bits of the prefix
|
||||
bytes[2] = (prefix[2] & 0xf8) | (bytes[2] & 0x7);
|
||||
|
||||
// Set the last byte to the random r
|
||||
bytes[ID_SIZE - 1] = r;
|
||||
|
||||
Id(bytes)
|
||||
}
|
||||
|
||||
fn id_prefix_ipv4(ip: Ipv4Addr, r: u8) -> [u8; 3] {
|
||||
let r32: u32 = r.into();
|
||||
let ip_int: u32 = u32::from_be_bytes(ip.octets());
|
||||
let masked_ip: u32 = (ip_int & IPV4_MASK) | (r32 << 29);
|
||||
|
||||
let mut digest = CASTAGNOLI.digest();
|
||||
digest.update(&masked_ip.to_be_bytes());
|
||||
|
||||
let crc = digest.finalize();
|
||||
|
||||
crc.to_be_bytes()[..3]
|
||||
.try_into()
|
||||
.expect("Failed to convert bytes 0-2 of the crc into a 3-byte array")
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
#[allow(clippy::format_collect)]
|
||||
let hex_chars: String = self.0.iter().map(|byte| format!("{:02x}", byte)).collect();
|
||||
|
||||
write!(f, "{}", hex_chars)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; ID_SIZE]> for Id {
|
||||
fn from(bytes: [u8; ID_SIZE]) -> Id {
|
||||
Id(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8; ID_SIZE]> for Id {
|
||||
fn from(bytes: &[u8; ID_SIZE]) -> Id {
|
||||
Id(*bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for [u8; ID_SIZE] {
|
||||
fn from(value: Id) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Id {
|
||||
type Err = DecodeIdError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Id, DecodeIdError> {
|
||||
if s.len() % 2 != 0 {
|
||||
return Err(DecodeIdError::OddNumberOfCharacters);
|
||||
}
|
||||
|
||||
let mut bytes = Vec::with_capacity(s.len() / 2);
|
||||
|
||||
for i in 0..s.len() / 2 {
|
||||
let byte_str = &s[i * 2..(i * 2) + 2];
|
||||
if let Ok(byte) = u8::from_str_radix(byte_str, 16) {
|
||||
bytes.push(byte);
|
||||
} else {
|
||||
return Err(DecodeIdError::InvalidHexCharacter(byte_str.into()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Id::from_bytes(bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Id {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Id({})", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidIdSize(usize);
|
||||
|
||||
impl std::error::Error for InvalidIdSize {}
|
||||
|
||||
impl std::fmt::Display for InvalidIdSize {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Invalid Id size, expected 20, got {0}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Mainline crate error enum.
|
||||
pub enum DecodeIdError {
|
||||
/// Id is expected to by 20 bytes.
|
||||
#[error(transparent)]
|
||||
InvalidIdSize(#[from] InvalidIdSize),
|
||||
|
||||
#[error("Hex encoding should contain an even number of hex characters")]
|
||||
/// Hex encoding should contain an even number of hex characters
|
||||
OddNumberOfCharacters,
|
||||
|
||||
/// Invalid hex character
|
||||
#[error("Invalid Id encoding: {0}")]
|
||||
InvalidHexCharacter(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn distance_to_self() {
|
||||
let id = Id::random();
|
||||
let distance = id.distance(&id);
|
||||
assert_eq!(distance, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_to_id() {
|
||||
let id = Id::from_str("0639A1E24FBB8AB277DF033476AB0DE10FAB3BDC").unwrap();
|
||||
|
||||
let target = Id::from_str("035b1aeb9737ade1a80933594f405d3f772aa08e").unwrap();
|
||||
|
||||
let distance = id.distance(&target);
|
||||
|
||||
assert_eq!(distance, 155)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_to_random_id() {
|
||||
let id = Id::random();
|
||||
let target = Id::random();
|
||||
|
||||
let distance = id.distance(&target);
|
||||
|
||||
assert_ne!(distance, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_to_furthest() {
|
||||
let id = Id::random();
|
||||
|
||||
let mut opposite = [0_u8; 20];
|
||||
for (i, &value) in id.as_bytes().iter().enumerate() {
|
||||
opposite[i] = value ^ 0xff;
|
||||
}
|
||||
let target = Id::from_bytes(opposite).unwrap();
|
||||
|
||||
let distance = id.distance(&target);
|
||||
|
||||
assert_eq!(distance, MAX_DISTANCE)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_u8_20() {
|
||||
let bytes = [8; 20];
|
||||
|
||||
let id: Id = bytes.into();
|
||||
|
||||
assert_eq!(*id.as_bytes(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_ipv4() {
|
||||
let vectors = vec![
|
||||
(Ipv4Addr::new(124, 31, 75, 21), 1, [0x5f, 0xbf, 0xbf]),
|
||||
(Ipv4Addr::new(21, 75, 31, 124), 86, [0x5a, 0x3c, 0xe9]),
|
||||
(Ipv4Addr::new(65, 23, 51, 170), 22, [0xa5, 0xd4, 0x32]),
|
||||
(Ipv4Addr::new(84, 124, 73, 14), 65, [0x1b, 0x03, 0x21]),
|
||||
(Ipv4Addr::new(43, 213, 53, 83), 90, [0xe5, 0x6f, 0x6c]),
|
||||
];
|
||||
|
||||
for vector in vectors {
|
||||
test(vector.0, vector.1, vector.2);
|
||||
}
|
||||
|
||||
fn test(ip: Ipv4Addr, r: u8, expected_prefix: [u8; 3]) {
|
||||
let id = Id::random();
|
||||
let result = from_ipv4_and_r(*id.as_bytes(), ip, r);
|
||||
let prefix = first_21_bits(result.as_bytes());
|
||||
|
||||
assert_eq!(prefix, first_21_bits(&expected_prefix));
|
||||
assert_eq!(result.as_bytes()[ID_SIZE - 1], r);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_valid_for_ipv4() {
|
||||
let valid_vectors = vec![
|
||||
(
|
||||
Ipv4Addr::new(124, 31, 75, 21),
|
||||
"5fbfbff10c5d6a4ec8a88e4c6ab4c28b95eee401",
|
||||
),
|
||||
(
|
||||
Ipv4Addr::new(21, 75, 31, 124),
|
||||
"5a3ce9c14e7a08645677bbd1cfe7d8f956d53256",
|
||||
),
|
||||
(
|
||||
Ipv4Addr::new(65, 23, 51, 170),
|
||||
"a5d43220bc8f112a3d426c84764f8c2a1150e616",
|
||||
),
|
||||
(
|
||||
Ipv4Addr::new(84, 124, 73, 14),
|
||||
"1b0321dd1bb1fe518101ceef99462b947a01ff41",
|
||||
),
|
||||
(
|
||||
Ipv4Addr::new(43, 213, 53, 83),
|
||||
"e56f6cbf5b7c4be0237986d5243b87aa6d51305a",
|
||||
),
|
||||
];
|
||||
|
||||
for vector in valid_vectors {
|
||||
test(vector.0, vector.1);
|
||||
}
|
||||
|
||||
fn test(ip: Ipv4Addr, hex: &str) {
|
||||
let id = Id::from_str(hex).unwrap();
|
||||
|
||||
assert!(id.is_valid_for_ip(ip));
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
//! Helper functions for immutable items.
|
||||
|
||||
use sha1_smol::Sha1;
|
||||
|
||||
use super::ID_SIZE;
|
||||
use crate::Id;
|
||||
|
||||
pub fn validate_immutable(v: &[u8], target: Id) -> bool {
|
||||
hash_immutable(v) == *target.as_bytes()
|
||||
}
|
||||
|
||||
pub fn hash_immutable(v: &[u8]) -> [u8; ID_SIZE] {
|
||||
let mut encoded = Vec::with_capacity(v.len() + 3);
|
||||
encoded.extend(format!("{}:", v.len()).bytes());
|
||||
encoded.extend_from_slice(v);
|
||||
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&encoded);
|
||||
|
||||
hasher.digest().bytes()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_validate_immutable() {
|
||||
let v = vec![
|
||||
171, 118, 111, 111, 174, 109, 195, 32, 138, 140, 113, 176, 76, 135, 116, 132, 156, 126,
|
||||
75, 173,
|
||||
];
|
||||
|
||||
let target = Id::from_bytes([
|
||||
2, 23, 113, 43, 67, 11, 185, 26, 26, 30, 204, 238, 204, 1, 13, 84, 52, 40, 86, 231,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert!(validate_immutable(&v, target));
|
||||
assert!(!validate_immutable(&v[1..], target));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
fn test_hash_immutable() {
|
||||
let v = b"From the river to the sea, Palestine will be free";
|
||||
let target = Id::from_str("4238af8aff56cf6e0007d9d2003bf23d33eea7c3").unwrap();
|
||||
|
||||
assert_eq!(hash_immutable(v), *target.as_bytes());
|
||||
}
|
||||
}
|
||||
+1255
File diff suppressed because it is too large.
Load diff
+333
@@ -0,0 +1,333 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_bytes::ByteBuf;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTMessage {
|
||||
#[serde(rename = "t", with = "serde_bytes")]
|
||||
// Only few messages received seems to not use exactly 2 bytes,
|
||||
// and they don't seem to have a version.
|
||||
pub transaction_id: [u8; 4],
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "v", with = "serde_bytes")]
|
||||
pub version: Option<[u8; 4]>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub variant: DHTMessageVariant,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(with = "serde_bytes")]
|
||||
// Ipv6 is not supported anyways.
|
||||
pub ip: Option<[u8; 6]>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde(rename = "ro")]
|
||||
pub read_only: Option<i32>,
|
||||
}
|
||||
|
||||
impl DHTMessage {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<DHTMessage, serde_bencode::Error> {
|
||||
let obj = serde_bencode::from_bytes(bytes)?;
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, serde_bencode::Error> {
|
||||
serde_bencode::to_bytes(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(tag = "y")]
|
||||
pub enum DHTMessageVariant {
|
||||
#[serde(rename = "q")]
|
||||
Request(DHTRequestSpecific),
|
||||
|
||||
#[serde(rename = "r")]
|
||||
Response(DHTResponseSpecific),
|
||||
|
||||
#[serde(rename = "e")]
|
||||
Error(DHTErrorSpecific),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(tag = "q")]
|
||||
pub enum DHTRequestSpecific {
|
||||
#[serde(rename = "ping")]
|
||||
Ping {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTPingRequestArguments,
|
||||
},
|
||||
|
||||
#[serde(rename = "find_node")]
|
||||
FindNode {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTFindNodeRequestArguments,
|
||||
},
|
||||
|
||||
#[serde(rename = "get_peers")]
|
||||
GetPeers {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTGetPeersRequestArguments,
|
||||
},
|
||||
|
||||
#[serde(rename = "announce_peer")]
|
||||
AnnouncePeer {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTAnnouncePeerRequestArguments,
|
||||
},
|
||||
|
||||
#[serde(rename = "get")]
|
||||
GetValue {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTGetValueRequestArguments,
|
||||
},
|
||||
|
||||
#[serde(rename = "put")]
|
||||
PutValue {
|
||||
#[serde(rename = "a")]
|
||||
arguments: DHTPutValueRequestArguments,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)] // This means order matters! Order these from most to least detailed
|
||||
pub enum DHTResponseSpecific {
|
||||
GetMutable {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTGetMutableResponseArguments,
|
||||
},
|
||||
|
||||
NoMoreRecentValue {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTNoMoreRecentValueResponseArguments,
|
||||
},
|
||||
|
||||
GetImmutable {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTGetImmutableResponseArguments,
|
||||
},
|
||||
|
||||
GetPeers {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTGetPeersResponseArguments,
|
||||
},
|
||||
|
||||
NoValues {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTNoValuesResponseArguments,
|
||||
},
|
||||
|
||||
FindNode {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTFindNodeResponseArguments,
|
||||
},
|
||||
|
||||
Ping {
|
||||
#[serde(rename = "r")]
|
||||
arguments: DHTPingResponseArguments,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTErrorSpecific {
|
||||
#[serde(rename = "e")]
|
||||
pub error_info: (i32, String),
|
||||
}
|
||||
|
||||
// === PING ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTPingRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTPingResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
}
|
||||
|
||||
// === FIND NODE ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTFindNodeRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub target: [u8; 20],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTFindNodeResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub nodes: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTNoValuesResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub nodes: Option<Box<[u8]>>,
|
||||
}
|
||||
|
||||
// === Get Peers ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTGetPeersRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub info_hash: [u8; 20],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTGetPeersResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub nodes: Option<Box<[u8]>>,
|
||||
|
||||
// values are not optional, because if they are missing this missing
|
||||
// we can just treat this as DHTNoValuesResponseArguments
|
||||
pub values: Vec<ByteBuf>,
|
||||
}
|
||||
|
||||
// === Announce Peer ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTAnnouncePeerRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub info_hash: [u8; 20],
|
||||
|
||||
pub port: u16,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(default)]
|
||||
pub implied_port: Option<u8>,
|
||||
}
|
||||
|
||||
// === Get Value ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTGetValueRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub target: [u8; 20],
|
||||
|
||||
#[serde(default)]
|
||||
pub seq: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTGetImmutableResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub nodes: Option<Box<[u8]>>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub v: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTNoMoreRecentValueResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub nodes: Option<Box<[u8]>>,
|
||||
|
||||
pub seq: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTGetMutableResponseArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub nodes: Option<Box<[u8]>>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub v: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub k: [u8; 32],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub sig: [u8; 64],
|
||||
|
||||
pub seq: i64,
|
||||
}
|
||||
|
||||
// === Put Value ===
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct DHTPutValueRequestArguments {
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub id: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub target: [u8; 20],
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub token: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
pub v: Box<[u8]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub k: Option<[u8; 32]>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub sig: Option<[u8; 64]>,
|
||||
|
||||
#[serde(default)]
|
||||
pub seq: Option<i64>,
|
||||
|
||||
#[serde(default)]
|
||||
pub cas: Option<i64>,
|
||||
|
||||
#[serde(with = "serde_bytes")]
|
||||
#[serde(default)]
|
||||
pub salt: Option<Box<[u8]>>,
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
//! Helper functions and structs for mutable items.
|
||||
|
||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha1_smol::Sha1;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::Id;
|
||||
|
||||
use super::PutMutableRequestArguments;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
/// [BEP_0044](https://www.bittorrent.org/beps/bep_0044.html)'s Mutable item.
|
||||
pub struct MutableItem {
|
||||
/// hash of the key and optional salt
|
||||
target: Id,
|
||||
/// ed25519 public key
|
||||
key: [u8; 32],
|
||||
/// sequence number
|
||||
pub(crate) seq: i64,
|
||||
/// mutable value
|
||||
pub(crate) value: Box<[u8]>,
|
||||
/// ed25519 signature
|
||||
#[serde(with = "serde_bytes")]
|
||||
signature: [u8; 64],
|
||||
/// Optional salt
|
||||
salt: Option<Box<[u8]>>,
|
||||
}
|
||||
|
||||
impl MutableItem {
|
||||
/// Create a new mutable item from a signing key, value, sequence number and optional salt.
|
||||
pub fn new(signer: SigningKey, value: &[u8], seq: i64, salt: Option<&[u8]>) -> Self {
|
||||
let signable = encode_signable(seq, value, salt);
|
||||
let signature = signer.sign(&signable);
|
||||
|
||||
Self::new_signed_unchecked(
|
||||
signer.verifying_key().to_bytes(),
|
||||
signature.into(),
|
||||
value,
|
||||
seq,
|
||||
salt,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the target of a [MutableItem] by hashing its `public_key` and an optional `salt`
|
||||
pub fn target_from_key(public_key: &[u8; 32], salt: Option<&[u8]>) -> Id {
|
||||
let mut encoded = vec![];
|
||||
|
||||
encoded.extend(public_key);
|
||||
|
||||
if let Some(salt) = salt {
|
||||
encoded.extend(salt);
|
||||
}
|
||||
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(&encoded);
|
||||
let bytes = hasher.digest().bytes();
|
||||
|
||||
bytes.into()
|
||||
}
|
||||
|
||||
/// Create a new mutable item from an already signed value.
|
||||
pub fn new_signed_unchecked(
|
||||
key: [u8; 32],
|
||||
signature: [u8; 64],
|
||||
value: &[u8],
|
||||
seq: i64,
|
||||
salt: Option<&[u8]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
target: MutableItem::target_from_key(&key, salt),
|
||||
key,
|
||||
value: value.into(),
|
||||
seq,
|
||||
signature,
|
||||
salt: salt.map(|s| s.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_dht_message(
|
||||
target: Id,
|
||||
key: &[u8],
|
||||
v: Box<[u8]>,
|
||||
seq: i64,
|
||||
signature: &[u8],
|
||||
salt: Option<Box<[u8]>>,
|
||||
) -> Result<Self, MutableError> {
|
||||
let key = VerifyingKey::try_from(key).map_err(|_| MutableError::InvalidMutablePublicKey)?;
|
||||
|
||||
let signature =
|
||||
Signature::from_slice(signature).map_err(|_| MutableError::InvalidMutableSignature)?;
|
||||
|
||||
key.verify(&encode_signable(seq, &v, salt.as_deref()), &signature)
|
||||
.map_err(|_| MutableError::InvalidMutableSignature)?;
|
||||
|
||||
if Self::target_from_key(&key.to_bytes(), salt.as_deref()) != target {
|
||||
return Err(MutableError::InvalidMutableTarget);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
target,
|
||||
key: key.to_bytes(),
|
||||
value: v,
|
||||
seq,
|
||||
signature: signature.to_bytes(),
|
||||
salt,
|
||||
})
|
||||
}
|
||||
|
||||
// === Getters ===
|
||||
|
||||
/// Returns the target (info hash) of this item.
|
||||
pub fn target(&self) -> &Id {
|
||||
&self.target
|
||||
}
|
||||
|
||||
/// Returns a reference to the 32 bytes Ed25519 public key of this item.
|
||||
pub fn key(&self) -> &[u8; 32] {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Returns a byte slice of the value of this item.
|
||||
pub fn value(&self) -> &[u8] {
|
||||
&self.value
|
||||
}
|
||||
|
||||
/// Returns the `seq` (sequence) number of this item.
|
||||
pub fn seq(&self) -> i64 {
|
||||
self.seq
|
||||
}
|
||||
|
||||
/// Returns the signature over this item.
|
||||
pub fn signature(&self) -> &[u8; 64] {
|
||||
&self.signature
|
||||
}
|
||||
|
||||
/// Returns the `Salt` value used for generating the
|
||||
/// [Self::target] if any.
|
||||
pub fn salt(&self) -> Option<&[u8]> {
|
||||
self.salt.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_signable(seq: i64, value: &[u8], salt: Option<&[u8]>) -> Box<[u8]> {
|
||||
let mut signable = vec![];
|
||||
|
||||
if let Some(salt) = salt {
|
||||
signable.extend(format!("4:salt{}:", salt.len()).into_bytes());
|
||||
signable.extend(salt);
|
||||
}
|
||||
|
||||
signable.extend(format!("3:seqi{}e1:v{}:", seq, value.len()).into_bytes());
|
||||
signable.extend(value);
|
||||
|
||||
signable.into()
|
||||
}
|
||||
|
||||
pub(crate) fn most_recent_mutable_item(
|
||||
most_recent: Option<MutableItem>,
|
||||
item: MutableItem,
|
||||
) -> Option<MutableItem> {
|
||||
match most_recent {
|
||||
Some(mr)
|
||||
if mr.seq() > item.seq() || (mr.seq() == item.seq() && mr.value() >= item.value()) =>
|
||||
{
|
||||
Some(mr)
|
||||
}
|
||||
_ => Some(item),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Mainline crate error enum.
|
||||
pub enum MutableError {
|
||||
#[error("Invalid mutable item signature")]
|
||||
/// Invalid mutable item signature
|
||||
InvalidMutableSignature,
|
||||
|
||||
#[error("Invalid mutable item public key")]
|
||||
/// Invalid mutable item public key
|
||||
InvalidMutablePublicKey,
|
||||
|
||||
#[error("Mutable item target does not match its public key and salt")]
|
||||
/// Mutable item target does not match its public key and salt
|
||||
InvalidMutableTarget,
|
||||
}
|
||||
|
||||
impl PutMutableRequestArguments {
|
||||
/// Create a [PutMutableRequestArguments] from a [MutableItem],
|
||||
/// and an optional CAS condition, which is usually the [MutableItem::seq]
|
||||
/// of the most recent known [MutableItem]
|
||||
pub fn from(item: MutableItem, cas: Option<i64>) -> Self {
|
||||
Self {
|
||||
target: item.target,
|
||||
v: item.value,
|
||||
k: item.key,
|
||||
seq: item.seq,
|
||||
sig: item.signature,
|
||||
salt: item.salt,
|
||||
cas,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
#[test]
|
||||
fn signable_without_salt() {
|
||||
let signable = encode_signable(4, b"Hello world!", None);
|
||||
|
||||
assert_eq!(&*signable, b"3:seqi4e1:v12:Hello world!");
|
||||
}
|
||||
#[test]
|
||||
fn signable_with_salt() {
|
||||
let signable = encode_signable(4, b"Hello world!", Some(b"foobar"));
|
||||
|
||||
assert_eq!(&*signable, b"4:salt6:foobar3:seqi4e1:v12:Hello world!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn most_recent_mutable_item_selects_by_seq_then_value() {
|
||||
let signer = SigningKey::from_bytes(&[
|
||||
56, 171, 62, 85, 105, 58, 155, 209, 189, 8, 59, 109, 137, 84, 84, 201, 221, 115, 7,
|
||||
228, 127, 70, 4, 204, 182, 64, 77, 98, 92, 215, 27, 103,
|
||||
]);
|
||||
|
||||
let lower_seq = MutableItem::new(signer.clone(), b"lower-seq", 999, None);
|
||||
let current = MutableItem::new(signer.clone(), b"current1", 1000, None);
|
||||
let higher_seq = MutableItem::new(signer.clone(), b"higher-seq", 1001, None);
|
||||
let same_seq_lower_value = MutableItem::new(signer.clone(), b"current0", 1000, None);
|
||||
let same_seq_higher_value = MutableItem::new(signer, b"current2", 1000, None);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(None, current.clone()),
|
||||
Some(current.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(Some(current.clone()), higher_seq.clone()),
|
||||
Some(higher_seq)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(Some(current.clone()), lower_seq),
|
||||
Some(current.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(Some(current.clone()), same_seq_higher_value.clone()),
|
||||
Some(same_seq_higher_value)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(Some(current.clone()), same_seq_lower_value),
|
||||
Some(current.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
most_recent_mutable_item(Some(current.clone()), current.clone()),
|
||||
Some(current)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_dht_message_rejects_a_signed_item_for_a_different_target() {
|
||||
let signer = SigningKey::from_bytes(&[42; 32]);
|
||||
let item = MutableItem::new(signer, b"value", 1, Some(b"salt"));
|
||||
let mut wrong_target = *item.target().as_bytes();
|
||||
wrong_target[0] ^= 1;
|
||||
|
||||
let result = MutableItem::from_dht_message(
|
||||
wrong_target.into(),
|
||||
item.key(),
|
||||
item.value().into(),
|
||||
item.seq(),
|
||||
item.signature(),
|
||||
item.salt().map(Into::into),
|
||||
);
|
||||
|
||||
assert!(matches!(result, Err(MutableError::InvalidMutableTarget)));
|
||||
}
|
||||
}
|
||||
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
//! Struct and implementation of the Node entry in the Kademlia routing table
|
||||
use std::{
|
||||
fmt::{self, Debug, Formatter},
|
||||
net::SocketAddrV4,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::common::Id;
|
||||
|
||||
/// The age of a node's last_seen time before it is considered stale and removed from a full bucket
|
||||
/// on inserting a new node.
|
||||
pub const STALE_TIME: Duration = Duration::from_secs(15 * 60);
|
||||
const MIN_PING_BACKOFF_INTERVAL: Duration = Duration::from_secs(10);
|
||||
pub const TOKEN_ROTATE_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub(crate) struct NodeInner {
|
||||
pub(crate) id: Id,
|
||||
pub(crate) address: SocketAddrV4,
|
||||
pub(crate) token: Option<Box<[u8]>>,
|
||||
pub(crate) last_seen: Instant,
|
||||
}
|
||||
|
||||
impl NodeInner {
|
||||
pub fn random() -> Self {
|
||||
Self {
|
||||
id: Id::random(),
|
||||
address: SocketAddrV4::new(0.into(), 0),
|
||||
token: None,
|
||||
last_seen: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
/// Node entry in Kademlia routing table
|
||||
pub struct Node(pub(crate) Arc<NodeInner>);
|
||||
|
||||
impl Debug for Node {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Node")
|
||||
.field("id", &self.0.id)
|
||||
.field("address", &self.0.address)
|
||||
.field("last_seen", &self.0.last_seen.elapsed().as_secs())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Creates a new Node from an id and socket address.
|
||||
pub fn new(id: Id, address: SocketAddrV4) -> Node {
|
||||
Node(Arc::new(NodeInner {
|
||||
id,
|
||||
address,
|
||||
token: None,
|
||||
last_seen: Instant::now(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_token(id: Id, address: SocketAddrV4, token: Box<[u8]>) -> Self {
|
||||
Node(Arc::new(NodeInner {
|
||||
id,
|
||||
address,
|
||||
token: Some(token),
|
||||
last_seen: Instant::now(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Creates a node with random Id for testing purposes.
|
||||
pub fn random() -> Node {
|
||||
Node(Arc::new(NodeInner::random()))
|
||||
}
|
||||
|
||||
/// Create a node that is unique per `i` as it has a random Id and sets IP and port to `i`
|
||||
#[cfg(test)]
|
||||
pub fn unique(i: usize) -> Node {
|
||||
Node::new(Id::random(), SocketAddrV4::new((i as u32).into(), i as u16))
|
||||
}
|
||||
|
||||
// === Getters ===
|
||||
|
||||
/// Returns the id of this node
|
||||
pub fn id(&self) -> &Id {
|
||||
&self.0.id
|
||||
}
|
||||
|
||||
/// Returns the address of this node
|
||||
pub fn address(&self) -> SocketAddrV4 {
|
||||
self.0.address
|
||||
}
|
||||
|
||||
/// Returns the token we received from this node if any.
|
||||
pub fn token(&self) -> Option<Box<[u8]>> {
|
||||
self.0.token.clone()
|
||||
}
|
||||
|
||||
/// Node is last seen more than a threshold ago.
|
||||
pub fn is_stale(&self) -> bool {
|
||||
self.0.last_seen.elapsed() > STALE_TIME
|
||||
}
|
||||
|
||||
/// Node's token was received 5 minutes ago or less
|
||||
pub fn valid_token(&self) -> bool {
|
||||
self.0.last_seen.elapsed() <= TOKEN_ROTATE_INTERVAL
|
||||
}
|
||||
|
||||
pub(crate) fn should_ping(&self) -> bool {
|
||||
self.0.last_seen.elapsed() > MIN_PING_BACKOFF_INTERVAL
|
||||
}
|
||||
|
||||
/// Returns true if both nodes have the same ip and port
|
||||
pub fn same_address(&self, other: &Self) -> bool {
|
||||
self.0.address == other.0.address
|
||||
}
|
||||
|
||||
/// Returns true if both nodes have the same ip
|
||||
pub fn same_ip(&self, other: &Self) -> bool {
|
||||
self.0.address.ip() == other.0.address.ip()
|
||||
}
|
||||
|
||||
/// Node [Id] is valid for its IP address.
|
||||
///
|
||||
/// Check [BEP_0042](https://www.bittorrent.org/beps/bep_0042.html).
|
||||
pub fn is_secure(&self) -> bool {
|
||||
self.0.id.is_valid_for_ip(*self.0.address.ip())
|
||||
}
|
||||
|
||||
/// Returns true if Any of the existing nodes:
|
||||
/// - Have the same IP as this node, And:
|
||||
/// = The existing nodes is Not secure.
|
||||
/// = The existing nodes is secure And shares the same first 21 bits.
|
||||
///
|
||||
/// Effectively, allows only One non-secure node or Eight secure nodes from the same IP, in the routing table or ClosestNodes.
|
||||
pub(crate) fn already_exists(&self, nodes: &[Self]) -> bool {
|
||||
nodes.iter().any(|existing| {
|
||||
self.same_ip(existing)
|
||||
&& (!existing.is_secure()
|
||||
|| self.id().first_21_bits() == existing.id().first_21_bits())
|
||||
})
|
||||
}
|
||||
}
|
||||
+649
@@ -0,0 +1,649 @@
|
||||
//! Simplified Kademlia routing table
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::slice::Iter;
|
||||
|
||||
use crate::common::{Id, Node};
|
||||
use crate::rpc::ClosestNodes;
|
||||
|
||||
/// K = the default maximum size of a k-bucket.
|
||||
pub const MAX_BUCKET_SIZE_K: usize = 20;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Simplified Kademlia routing table
|
||||
pub struct RoutingTable {
|
||||
id: Id,
|
||||
buckets: BTreeMap<u8, KBucket>,
|
||||
}
|
||||
|
||||
impl RoutingTable {
|
||||
/// Create a new [RoutingTable] with a given id.
|
||||
pub fn new(id: Id) -> Self {
|
||||
let buckets = BTreeMap::new();
|
||||
|
||||
RoutingTable { id, buckets }
|
||||
}
|
||||
|
||||
/// Returns the [Id] of this node, where the distance is measured from.
|
||||
pub fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Returns the map of distances and their [KBucket]
|
||||
pub(crate) fn buckets(&self) -> &BTreeMap<u8, KBucket> {
|
||||
&self.buckets
|
||||
}
|
||||
|
||||
// === Public Methods ===
|
||||
|
||||
/// Attempts to add a node to this routing table, and return `true` if it did.
|
||||
pub fn add(&mut self, node: Node) -> bool {
|
||||
let distance = self.id.distance(node.id());
|
||||
|
||||
if distance == 0 {
|
||||
// Do not add self to the routing_table
|
||||
return false;
|
||||
}
|
||||
|
||||
if self
|
||||
.buckets()
|
||||
.values()
|
||||
.any(|bucket| node.already_exists(&bucket.nodes))
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
let bucket = self.buckets.entry(distance).or_default();
|
||||
|
||||
bucket.add(node)
|
||||
}
|
||||
|
||||
/// Remove a node from this routing table.
|
||||
pub fn remove(&mut self, node_id: &Id) {
|
||||
let distance = self.id.distance(node_id);
|
||||
|
||||
if let Some(bucket) = self.buckets.get_mut(&distance) {
|
||||
bucket.remove(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the closest nodes to the target while prioritizing secure nodes,
|
||||
/// as defined in [BEP_0042](https://www.bittorrent.org/beps/bep_0042.html)
|
||||
pub fn closest(&self, target: Id) -> Box<[Node]> {
|
||||
let mut closest = ClosestNodes::new(target);
|
||||
|
||||
for bucket in self.buckets.values() {
|
||||
for node in &bucket.nodes {
|
||||
closest.add(node.clone());
|
||||
}
|
||||
}
|
||||
|
||||
closest.nodes()[..MAX_BUCKET_SIZE_K.min(closest.len())].into()
|
||||
}
|
||||
|
||||
/// Secure version of [Self::closest] that tries to circumvent sybil attacks.
|
||||
pub fn closest_secure(
|
||||
&self,
|
||||
target: Id,
|
||||
dht_size_estimate: usize,
|
||||
subnets: usize,
|
||||
) -> Vec<Node> {
|
||||
let mut closest = ClosestNodes::new(target);
|
||||
|
||||
for node in self.nodes() {
|
||||
closest.add(node);
|
||||
}
|
||||
|
||||
closest
|
||||
.take_until_secure(dht_size_estimate, subnets)
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// Returns `true` if this routing table is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buckets.values().all(|bucket| bucket.is_empty())
|
||||
}
|
||||
|
||||
/// Return the number of nodes in this routing table.
|
||||
pub fn size(&self) -> usize {
|
||||
self.buckets
|
||||
.values()
|
||||
.fold(0, |acc, bucket| acc + bucket.nodes.len())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the nodes in this routing table.
|
||||
pub fn nodes(&self) -> RoutingTableIterator<'_> {
|
||||
RoutingTableIterator {
|
||||
bucket_index: 1,
|
||||
node_index: 0,
|
||||
table: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Export an owned vector of nodes from this routing table.
|
||||
pub fn to_owned_nodes(&self) -> Vec<Node> {
|
||||
self.nodes().collect()
|
||||
}
|
||||
|
||||
/// Turn this routing table to a list of bootstrapping nodes.
|
||||
pub fn to_bootstrap(&self) -> Vec<String> {
|
||||
self.nodes()
|
||||
.filter(|n| !n.is_stale())
|
||||
.map(|n| n.address().to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// === Private Methods ===
|
||||
|
||||
#[cfg(test)]
|
||||
fn contains(&self, node_id: &Id) -> bool {
|
||||
let distance = self.id.distance(node_id);
|
||||
|
||||
if let Some(bucket) = self.buckets.get(&distance) {
|
||||
if bucket.contains(node_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RoutingTableIterator<'a> {
|
||||
bucket_index: u8,
|
||||
node_index: usize,
|
||||
table: &'a RoutingTable,
|
||||
}
|
||||
|
||||
impl Iterator for RoutingTableIterator<'_> {
|
||||
type Item = Node;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.bucket_index <= 160 {
|
||||
if let Some(current_bucket) = self.table.buckets.get(&self.bucket_index) {
|
||||
if let Some(current_node) = current_bucket.nodes.get(self.node_index) {
|
||||
self.node_index += 1;
|
||||
|
||||
if self.node_index == current_bucket.nodes.len() {
|
||||
self.node_index = 0;
|
||||
self.bucket_index += 1;
|
||||
}
|
||||
|
||||
return Some(current_node.clone());
|
||||
}
|
||||
};
|
||||
|
||||
self.bucket_index += 1;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Kbuckets are similar to LRU caches that checks and evicts unresponsive nodes,
|
||||
/// without dropping any responsive nodes in the process.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KBucket {
|
||||
/// Nodes in the k-bucket, sorted by the least recently seen.
|
||||
nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
impl KBucket {
|
||||
pub fn new() -> Self {
|
||||
KBucket {
|
||||
nodes: Vec::with_capacity(MAX_BUCKET_SIZE_K),
|
||||
}
|
||||
}
|
||||
|
||||
// === Getters ===
|
||||
|
||||
// === Public Methods ===
|
||||
|
||||
pub fn add(&mut self, incoming: Node) -> bool {
|
||||
if let Some(index) = self.iter().position(|n| n.id() == incoming.id()) {
|
||||
let existing = self.nodes[index].clone();
|
||||
|
||||
// If the incoming node is secure, then we trust its IP address for this Id,
|
||||
// and even if it changed its port number, we should accept it.
|
||||
//
|
||||
// If neither nodes are secure for this Id, but the incoming is the same IP,
|
||||
// then add the incoming one, effectively updating the node's
|
||||
// `last_seen` and moving it to the end of the bucket.
|
||||
// Possibly also updating the port, which is a good thing, instead of waiting
|
||||
// for the old port to timeout (not responding to Pings).
|
||||
//
|
||||
// Using same ip instead of same address, allow
|
||||
if incoming.is_secure() || (!existing.is_secure() && existing.same_ip(&incoming)) {
|
||||
self.nodes.remove(index);
|
||||
self.nodes.push(incoming);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if self.nodes.len() < MAX_BUCKET_SIZE_K {
|
||||
self.nodes.push(incoming);
|
||||
true
|
||||
} else if self.nodes[0].is_stale() {
|
||||
// Remove the least recently seen node and add the new one
|
||||
self.nodes.remove(0);
|
||||
self.nodes.push(incoming);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, node_id: &Id) {
|
||||
self.nodes.retain(|node| node.id() != node_id);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.nodes.is_empty()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_, Node> {
|
||||
self.nodes.iter()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn contains(&self, id: &Id) -> bool {
|
||||
self.iter().any(|node| node.id() == id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KBucket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::SocketAddrV4;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::common::{Id, KBucket, Node, NodeInner, RoutingTable, MAX_BUCKET_SIZE_K};
|
||||
|
||||
#[test]
|
||||
fn table_is_empty() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
assert!(table.is_empty());
|
||||
|
||||
table.add(Node::random());
|
||||
assert!(!table.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_vec() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
|
||||
let mut expected_nodes: Vec<Node> = vec![];
|
||||
|
||||
for i in 0..MAX_BUCKET_SIZE_K {
|
||||
expected_nodes.push(Node::unique(i));
|
||||
}
|
||||
|
||||
for node in &expected_nodes {
|
||||
table.add(node.clone());
|
||||
}
|
||||
|
||||
let mut sorted_table = table.nodes().collect::<Vec<_>>();
|
||||
sorted_table.sort_by(|a, b| a.id().cmp(b.id()));
|
||||
|
||||
let mut sorted_expected = expected_nodes.to_vec();
|
||||
sorted_expected.sort_by(|a, b| a.id().cmp(b.id()));
|
||||
|
||||
assert_eq!(sorted_table, sorted_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
|
||||
let node = Node::random();
|
||||
|
||||
assert!(!table.contains(node.id()));
|
||||
|
||||
table.add(node.clone());
|
||||
assert!(table.contains(node.id()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
|
||||
let node = Node::random();
|
||||
|
||||
table.add(node.clone());
|
||||
assert!(table.contains(node.id()));
|
||||
|
||||
table.remove(node.id());
|
||||
assert!(!table.contains(node.id()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buckets_are_sets() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
|
||||
let node1 = Node::random();
|
||||
let node2 = Node::new(*node1.id(), node1.address());
|
||||
|
||||
table.add(node1);
|
||||
table.add(node2);
|
||||
|
||||
assert_eq!(table.size(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_add_self() {
|
||||
let mut table = RoutingTable::new(Id::random());
|
||||
let node = Node::new(*table.id(), SocketAddrV4::new(0.into(), 0));
|
||||
|
||||
table.add(node.clone());
|
||||
|
||||
assert!(!table.add(node));
|
||||
assert!(table.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_add_more_than_k() {
|
||||
let mut bucket = KBucket::new();
|
||||
|
||||
for i in 0..MAX_BUCKET_SIZE_K {
|
||||
let node = Node::random();
|
||||
assert!(bucket.add(node), "Failed to add node {}", i);
|
||||
}
|
||||
|
||||
let node = Node::random();
|
||||
|
||||
assert!(!bucket.add(node));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_update_existing_node() {
|
||||
// Same address
|
||||
{
|
||||
let mut bucket = KBucket::new();
|
||||
|
||||
let node1 = Node::random();
|
||||
let node2 = Node::new(*node1.id(), node1.address());
|
||||
|
||||
bucket.add(node1.clone());
|
||||
bucket.add(Node::random());
|
||||
|
||||
assert_ne!(bucket.nodes[1].id(), node1.id());
|
||||
|
||||
bucket.add(node2);
|
||||
|
||||
assert_eq!(bucket.nodes.len(), 2);
|
||||
assert_eq!(bucket.nodes[1].id(), node1.id());
|
||||
}
|
||||
|
||||
// Different port
|
||||
{
|
||||
let mut bucket = KBucket::new();
|
||||
|
||||
let node1 = Node::random();
|
||||
let node2 = Node::new(*node1.id(), SocketAddrV4::new(*node1.address().ip(), 1));
|
||||
|
||||
bucket.add(node1.clone());
|
||||
bucket.add(Node::random());
|
||||
|
||||
assert_ne!(bucket.nodes[1].id(), node1.id());
|
||||
|
||||
bucket.add(node2.clone());
|
||||
|
||||
assert_eq!(bucket.nodes.len(), 2);
|
||||
assert_eq!(bucket.nodes[1].id(), node1.id());
|
||||
}
|
||||
|
||||
{
|
||||
let mut bucket = KBucket::new();
|
||||
|
||||
let secure = Node(Arc::new(NodeInner {
|
||||
id: Id::from_str("5a3ce9c14e7a08645677bbd1cfe7d8f956d53256").unwrap(),
|
||||
address: SocketAddrV4::new([21, 75, 31, 124].into(), 0),
|
||||
token: None,
|
||||
last_seen: Instant::now(),
|
||||
}));
|
||||
|
||||
let unsecure = Node::new(*secure.id(), SocketAddrV4::new([0, 0, 0, 0].into(), 1));
|
||||
|
||||
{
|
||||
bucket.add(unsecure.clone());
|
||||
bucket.add(secure.clone());
|
||||
|
||||
assert_eq!(bucket.nodes[0].address(), secure.address())
|
||||
}
|
||||
|
||||
{
|
||||
bucket.add(secure.clone());
|
||||
bucket.add(unsecure.clone());
|
||||
|
||||
assert_eq!(bucket.nodes[0].address(), secure.address())
|
||||
}
|
||||
}
|
||||
|
||||
// Different ip
|
||||
{
|
||||
let mut bucket = KBucket::new();
|
||||
|
||||
let node1 = Node::random();
|
||||
let node2 = Node::new(*node1.id(), SocketAddrV4::new([0, 0, 0, 1].into(), 1));
|
||||
|
||||
bucket.add(node1.clone());
|
||||
bucket.add(Node::random());
|
||||
|
||||
assert_ne!(bucket.nodes[1].id(), node1.id());
|
||||
|
||||
bucket.add(node2.clone());
|
||||
|
||||
assert_eq!(bucket.nodes.len(), 2);
|
||||
assert_ne!(bucket.nodes[1].id(), node1.id());
|
||||
assert_ne!(bucket.nodes[1].address(), node2.address());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closest() {
|
||||
let ids = [
|
||||
"fb449c17f6c34fadea26a5a83e1952e815e001ea",
|
||||
"e63b72f95aacee40ad087f83afb475645739f669",
|
||||
"58c65677e3833cb0f15733a6363cc4cb1352f90a",
|
||||
"fd042ff1404b495720ad8345404ff5f25acd02a8",
|
||||
"dbed34a2c8db568fe59c10adcca9e81825b3dcfd",
|
||||
"079d40b746b5721f59972ebde423429739844914",
|
||||
"094f1d2fb4b95ba2c3250b014a9f06d13cd9eb9a",
|
||||
"98805a55523458c56d59339266bdcecc82370ecd",
|
||||
"0a1d6cce47c60f2c7357e9fec2910192de6eb336",
|
||||
"fb689ce0e18c2c22f316976d3ae524aed4137773",
|
||||
"0d01c32b4cf386b0b784b718b999d0e9dac07876",
|
||||
"9465e80d80f707b222c4ae6ee81c02b62f607629",
|
||||
"6cdc012328cc7a3a9a5b967e93387686e19c9f75",
|
||||
"99719dfc220b145e2aac71d6b3e276731d85be1c",
|
||||
"94d2037bbc534a5f1d672ce3e3350576c2b78ed1",
|
||||
"b48d0aeb94cd3766f23d2ac098bbccf01485dc20",
|
||||
"3b6e1c05f199edd7dee87d3cc8422c8f0ed02358",
|
||||
"d9b50c6ca730c89f8fc9f518136cef6139dd2252",
|
||||
"15827c92e6efbc4f56e507e548409c4bc04360bf",
|
||||
"3c8ff1e484c21132f8e6b8112a2feab984536f57",
|
||||
"c9a8163fa3e85065d46567bfac39b5452cfb3ae8",
|
||||
"ef79f77e9eed9ad51094ce2747e2c4fdc3a81326",
|
||||
"81f038cabb8a845f39da0d40716bf0707da55187",
|
||||
"907fdf0aa137200b395bc210763ed947b03dfc2e",
|
||||
"b0bce9873042aee29cbc7ec395647f6cc7a482f8",
|
||||
"e6b8d5567bc05d9b68f23d562645bc030729abc9",
|
||||
"74667cb7c629fb7e63749134b16e27446984c517",
|
||||
"cdc7f4d5825dc316de20d998bc0f1c5e91e36a5e",
|
||||
"701e7b5af5fabcf0bc3de97cb05a7c00da3e53c6",
|
||||
"36eb09b1db4af2b11312742faa2bb42621fce753",
|
||||
"9e4923966754c02b036698e95f95cec8fc40a9d2",
|
||||
"0e43d66e9da1bfc7e2581155dfd1b8f4be57d3f1",
|
||||
"647679a0d8816d2f62200e7b6ef6171297756dd5",
|
||||
"c03d9008add37f8414cb41549448bb2dcb5c6c9b",
|
||||
"dff82b028a6ec033e00b387df8e386417b92a47c",
|
||||
"42e8b38494b0ee11003592da11b5cbe43332190e",
|
||||
"03161976385301ac9b965202e8f3922cef840790",
|
||||
"7d598e5726fb58501d8cc65faf6b676bab7cb4bc",
|
||||
"54ddde105d3f2c6ea7a5e7641ff24522eea2e784",
|
||||
"3a75532b5916c772c1b7a18627bf170cf915aeb3",
|
||||
"fa2b38321419e63cb890f8a8b5c53a1c4728a10a",
|
||||
"a3ba598bee9da287092f4f2f3864322af38e1824",
|
||||
"a94df01f21d870a006748b6ab3c04d31428c959d",
|
||||
"396aabc66c603617f376409053d1e2cec3813101",
|
||||
"a7b4becc2304da63792eb6c33f95677b2e7c9f8c",
|
||||
"58b1623af15a9828ccf41b8cee47d123c5cfe8b6",
|
||||
"3cb7eeac7be3a0195a9243537d452f790ccf1ca9",
|
||||
"e0296cfc4726d91a1f7f041e24638a1276a08bed",
|
||||
"aeb03edad3edc7c54a3c5f7916ecba981e65ce91",
|
||||
"4a81a4596b7c4b8706fd8b5c88ddfde18ca72293",
|
||||
"0d4e9ae7c486e5a0361bd4e3b918b6bdca89cfcb",
|
||||
"81d394b44403315f9845c3da6f018b8daedd89ef",
|
||||
"345630675ff0f319c8f2bb355edf59f9bd93072f",
|
||||
"b61fbd992a13af05feba939f597b5f6ee61188e3",
|
||||
"5ea45447e2e79a5f3b3d8c2f68aebdabf71c42f9",
|
||||
"84325dd6fbd9a93f4ab61d091a9562a6c6111df4",
|
||||
"e7c796aeecd47cfd01a2d62fd3fb1d41aafa2464",
|
||||
"897457b33c4eb1ffcab08331877108cbf3fac6de",
|
||||
"833843b1f33e720c17bccfb75647a49040861b4c",
|
||||
"06b49c253d3fc9800cfd75605d26426f8ccb89af",
|
||||
"5024212c42bed9f45e48c450147fecb3e934fc4e",
|
||||
"5a9de8041b045a7a4f85b71a6dc6a794a7fcd4ea",
|
||||
"70cad33774ddacb51ed1918adedeb67ff13a3b1e",
|
||||
"840d201e3c213c01b4ab85983efaac44f0671552",
|
||||
"aa7ffc7999a1b1bb79ce19b61c37f70331f492d6",
|
||||
"e2ec0c07e15411564292b5fa75246e4c385f4411",
|
||||
"38c1a0d14f548d4d81655920ec564b08e9fcf5e6",
|
||||
"1d128b8343569c7e9a8985879fafd325d458d31c",
|
||||
"4fcd30cbe02b74cece57babac93aded26ecdc893",
|
||||
"57d8a6d782ee1df62ceebd5d10884805ed382336",
|
||||
"54443ed3476d1d542f37bf069973bbd2b64c1b27",
|
||||
"0e7ba6c5e4c29cf4fff25733892b63cf2a6efdfc",
|
||||
"18824378226a6d33bcdbe39dd3bc9ee656ce20a2",
|
||||
"93b0cb01befc90b65a0026acf85bea2fefec7d44",
|
||||
"d65e378a1ec70cc79ae5b4469ae7f0e8939033fe",
|
||||
"9230a2f8ac81e73f16c63dd60adb030328fbc983",
|
||||
"302de797c9d73275ea184d7f6a8bf77364a8fd52",
|
||||
"cea92f6e6612ef408d8c22ad5c1ed602bb2aedbf",
|
||||
"353f2ff278f4ee038e7b217276a82d6ed0617130",
|
||||
"e962e3a1946afa0d3ee97f3a0418cb3489a5f84c",
|
||||
"a4e42b6cf98e957684aa4e7006940d31bcb76b1f",
|
||||
"57af8f960b2450ffa0dc5bc7314fece53996d4d0",
|
||||
"28e73f73084bc8e91fe9ec0a5581b583ef468d8c",
|
||||
"9481589ddec9a6d9ad2cee7f73e8319aab3f1e95",
|
||||
"edec09cc7476cd019560874def4af852bfeaffe3",
|
||||
"6c3ae2cf5f9452d5176788e15635c5958581c931",
|
||||
"f547b9717e84036c3d5eefec6d6ee3bfa5af89cb",
|
||||
"87b51f4bf1ccd41cda3aa85c71da5de56aeeda33",
|
||||
"e743092a576b92c8c05e04d5d2b23f2838825fd1",
|
||||
"e713b84894b761e2a4e20fd0e5a81ae48a6b6f9d",
|
||||
"6b1abca34099d2436bac8ab25aa17a57cbfe1564",
|
||||
"93cb2977e536a680c043b158345254c14b946d52",
|
||||
"8c2754fa9e93cbf1cccfd9241ebe0cc141199cfe",
|
||||
"13e4abf95a8a9e6525419b4db7b1704ed0a2789d",
|
||||
"8d53d453d7cfbb9bc386e128fa68aca388a5ddc6",
|
||||
"caebf39e9c9b48d87277f2a13faa5931a24819a4",
|
||||
"5025ca6cda98f31bc3ef321dd9a015b7f06b8bfa",
|
||||
"531fbf18fdf3e513091614f20d65e920a505ca41",
|
||||
"2f81e6159f7de0bc90c8a1db661b33bffbee85fd",
|
||||
"85d4d9954f3a28228a2786b320ad58a46a13f37b",
|
||||
];
|
||||
|
||||
let nodes: Vec<Node> = ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, str)| {
|
||||
let id = Id::from_str(str).unwrap();
|
||||
Node(Arc::new(NodeInner {
|
||||
id,
|
||||
address: SocketAddrV4::new((i as u32).into(), i as u16),
|
||||
token: None,
|
||||
last_seen: Instant::now(),
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let local_id = Id::from_str("ba3042eb2d373b19e7c411ce6826e31b37be0b2e").unwrap();
|
||||
|
||||
let mut table = RoutingTable::new(local_id);
|
||||
|
||||
for node in nodes {
|
||||
table.add(node);
|
||||
}
|
||||
|
||||
{
|
||||
let expected_closest_ids: Vec<_> = [
|
||||
"897457b33c4eb1ffcab08331877108cbf3fac6de",
|
||||
"907fdf0aa137200b395bc210763ed947b03dfc2e",
|
||||
"9230a2f8ac81e73f16c63dd60adb030328fbc983",
|
||||
"93b0cb01befc90b65a0026acf85bea2fefec7d44",
|
||||
"93cb2977e536a680c043b158345254c14b946d52",
|
||||
"9465e80d80f707b222c4ae6ee81c02b62f607629",
|
||||
"9481589ddec9a6d9ad2cee7f73e8319aab3f1e95",
|
||||
"94d2037bbc534a5f1d672ce3e3350576c2b78ed1",
|
||||
"98805a55523458c56d59339266bdcecc82370ecd",
|
||||
"99719dfc220b145e2aac71d6b3e276731d85be1c",
|
||||
"9e4923966754c02b036698e95f95cec8fc40a9d2",
|
||||
"a3ba598bee9da287092f4f2f3864322af38e1824",
|
||||
"a4e42b6cf98e957684aa4e7006940d31bcb76b1f",
|
||||
"a7b4becc2304da63792eb6c33f95677b2e7c9f8c",
|
||||
"a94df01f21d870a006748b6ab3c04d31428c959d",
|
||||
"aa7ffc7999a1b1bb79ce19b61c37f70331f492d6",
|
||||
"aeb03edad3edc7c54a3c5f7916ecba981e65ce91",
|
||||
"b0bce9873042aee29cbc7ec395647f6cc7a482f8",
|
||||
"b48d0aeb94cd3766f23d2ac098bbccf01485dc20",
|
||||
"b61fbd992a13af05feba939f597b5f6ee61188e3",
|
||||
]
|
||||
.iter()
|
||||
.map(|id| Id::from_str(id).unwrap())
|
||||
.collect();
|
||||
|
||||
let target = local_id;
|
||||
let closest = table.closest(target);
|
||||
|
||||
let mut closest_ids: Vec<Id> = closest.iter().map(|n| *n.id()).collect();
|
||||
closest_ids.sort();
|
||||
|
||||
assert_eq!(closest_ids, expected_closest_ids);
|
||||
}
|
||||
|
||||
{
|
||||
let expected_closest_ids: Vec<_> = [
|
||||
"c03d9008add37f8414cb41549448bb2dcb5c6c9b",
|
||||
"c9a8163fa3e85065d46567bfac39b5452cfb3ae8",
|
||||
"cdc7f4d5825dc316de20d998bc0f1c5e91e36a5e",
|
||||
"cea92f6e6612ef408d8c22ad5c1ed602bb2aedbf",
|
||||
"d65e378a1ec70cc79ae5b4469ae7f0e8939033fe",
|
||||
"d9b50c6ca730c89f8fc9f518136cef6139dd2252",
|
||||
"dbed34a2c8db568fe59c10adcca9e81825b3dcfd",
|
||||
"dff82b028a6ec033e00b387df8e386417b92a47c",
|
||||
"e0296cfc4726d91a1f7f041e24638a1276a08bed",
|
||||
"e2ec0c07e15411564292b5fa75246e4c385f4411",
|
||||
"e63b72f95aacee40ad087f83afb475645739f669",
|
||||
"e6b8d5567bc05d9b68f23d562645bc030729abc9",
|
||||
"e7c796aeecd47cfd01a2d62fd3fb1d41aafa2464",
|
||||
"e962e3a1946afa0d3ee97f3a0418cb3489a5f84c",
|
||||
"edec09cc7476cd019560874def4af852bfeaffe3",
|
||||
"ef79f77e9eed9ad51094ce2747e2c4fdc3a81326",
|
||||
"fa2b38321419e63cb890f8a8b5c53a1c4728a10a",
|
||||
"fb449c17f6c34fadea26a5a83e1952e815e001ea",
|
||||
"fb689ce0e18c2c22f316976d3ae524aed4137773",
|
||||
"fd042ff1404b495720ad8345404ff5f25acd02a8",
|
||||
]
|
||||
.iter()
|
||||
.map(|str| Id::from_str(str).unwrap())
|
||||
.collect();
|
||||
|
||||
let target = Id::from_str("d1406a3d3a8354d566f21dba8bd06c537cde2a20").unwrap();
|
||||
let closest = table.closest(target);
|
||||
|
||||
let mut closest_ids: Vec<Id> = closest.iter().map(|n| *n.id()).collect();
|
||||
closest_ids.sort();
|
||||
|
||||
assert_eq!(closest_ids, expected_closest_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user