use std::time::Instant;
use std::net::SocketAddr;
use crate::time::*;
use crate::dht::kbucket::*;
use tox_crypto::*;
use crate::dht::dht_node::*;
use tox_packet::dht::packed_node::*;
use crate::dht::server::hole_punching::*;
pub const FRIEND_BOOTSTRAP_NODES_COUNT: u8 = 4;
pub const FRIEND_CLOSE_NODES_COUNT: u8 = 8;
#[derive(Clone, Debug)]
pub struct DhtFriend {
pub pk: PublicKey,
pub close_nodes: Kbucket<DhtNode>,
pub last_nodes_req_time: Instant,
pub random_requests_count: u32,
pub nodes_to_bootstrap: Kbucket<PackedNode>,
pub hole_punch: HolePunching,
}
impl DhtFriend {
pub fn new(pk: PublicKey) -> Self {
DhtFriend {
pk,
close_nodes: Kbucket::new(FRIEND_CLOSE_NODES_COUNT),
last_nodes_req_time: clock_now(),
random_requests_count: 0,
nodes_to_bootstrap: Kbucket::new(FRIEND_BOOTSTRAP_NODES_COUNT),
hole_punch: HolePunching::new(),
}
}
pub fn is_addr_known(&self) -> bool {
self.close_nodes.nodes.first()
.map_or(false, |node| node.pk == self.pk)
}
pub fn get_returned_addrs(&self) -> Vec<SocketAddr> {
let mut addrs = Vec::new();
for node in &self.close_nodes.nodes {
if let Some(v6) = node.assoc6.ret_saddr {
if !node.assoc6.is_bad() {
addrs.push(SocketAddr::V6(v6));
}
}
if let Some(v4) = node.assoc4.ret_saddr {
if !node.assoc4.is_bad() {
addrs.push(SocketAddr::V4(v4));
}
}
}
addrs
}
pub fn try_add_to_close(&mut self, node: PackedNode) -> bool {
self.close_nodes.try_add(&self.pk, node, true)
}
pub fn can_add_to_close(&self, node: &PackedNode) -> bool {
self.close_nodes.can_add(&self.pk, node, true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn addr_is_unknown() {
crypto_init().unwrap();
let pk = gen_keypair().0;
let mut friend = DhtFriend::new(pk);
assert!(friend.try_add_to_close(PackedNode::new("192.168.1.1:12345".parse().unwrap(), &gen_keypair().0)));
assert!(friend.try_add_to_close(PackedNode::new("192.168.1.2:12345".parse().unwrap(), &gen_keypair().0)));
assert!(!friend.is_addr_known())
}
#[test]
fn addr_is_known() {
crypto_init().unwrap();
let pk = gen_keypair().0;
let mut friend = DhtFriend::new(pk);
assert!(friend.try_add_to_close(PackedNode::new("192.168.1.1:12345".parse().unwrap(), &gen_keypair().0)));
assert!(friend.try_add_to_close(PackedNode::new("192.168.1.2:12345".parse().unwrap(), &gen_keypair().0)));
assert!(friend.try_add_to_close(PackedNode::new("192.168.1.3:12345".parse().unwrap(), &pk)));
assert!(friend.is_addr_known())
}
#[test]
fn get_returned_addrs() {
crypto_init().unwrap();
let pk = gen_keypair().0;
let mut friend = DhtFriend::new(pk);
let nodes = [
PackedNode::new("192.168.1.1:12345".parse().unwrap(), &gen_keypair().0),
PackedNode::new("192.168.1.2:12345".parse().unwrap(), &gen_keypair().0),
PackedNode::new("192.168.1.3:12345".parse().unwrap(), &gen_keypair().0),
];
let addrs: Vec<SocketAddr> = vec![
"192.168.2.1:12345".parse().unwrap(),
"192.168.2.2:12345".parse().unwrap(),
"192.168.2.3:12345".parse().unwrap(),
];
for (&node, &addr) in nodes.iter().zip(addrs.iter()) {
friend.try_add_to_close(node);
let dht_node = friend.close_nodes.get_node_mut(&pk, &node.pk).unwrap();
dht_node.update_returned_addr(addr);
}
let returned_addrs = friend.get_returned_addrs();
use std::collections::HashSet;
let addrs_set = addrs.into_iter().collect::<HashSet<_>>();
let returned_addrs_set = returned_addrs.into_iter().collect::<HashSet<_>>();
assert_eq!(returned_addrs_set, addrs_set);
}
#[tokio::test]
async fn get_returned_addrs_timed_out() {
crypto_init().unwrap();
let pk = gen_keypair().0;
let mut friend = DhtFriend::new(pk);
let nodes = [
PackedNode::new("192.168.1.1:12345".parse().unwrap(), &gen_keypair().0),
PackedNode::new("192.168.1.2:12345".parse().unwrap(), &gen_keypair().0),
PackedNode::new("192.168.1.3:12345".parse().unwrap(), &gen_keypair().0),
];
let addrs: Vec<SocketAddr> = vec![
"192.168.2.1:12345".parse().unwrap(),
"192.168.2.2:12345".parse().unwrap(),
"192.168.2.3:12345".parse().unwrap(),
];
for (&node, &addr) in nodes.iter().zip(addrs.iter()) {
friend.try_add_to_close(node);
let dht_node = friend.close_nodes.get_node_mut(&pk, &node.pk).unwrap();
dht_node.update_returned_addr(addr);
}
tokio::time::pause();
tokio::time::advance(BAD_NODE_TIMEOUT + Duration::from_secs(1)).await;
assert!(friend.get_returned_addrs().is_empty());
}
#[test]
fn can_and_try_add_to_close() {
crypto_init().unwrap();
let pk = PublicKey([0; PUBLICKEYBYTES]);
let mut friend = DhtFriend::new(pk);
for i in 0 .. 8 {
let addr = SocketAddr::new("1.2.3.4".parse().unwrap(), 12345 + u16::from(i));
let node = PackedNode::new(addr, &PublicKey([i + 2; PUBLICKEYBYTES]));
assert!(friend.try_add_to_close(node));
}
let closer_node = PackedNode::new(
"1.2.3.5:12345".parse().unwrap(),
&PublicKey([1; PUBLICKEYBYTES])
);
assert!(friend.can_add_to_close(&closer_node));
assert!(friend.try_add_to_close(closer_node));
}
}