use super::bencode::Dict;
use super::flight::{Flight, Query};
use super::krpc::{self, Message, Node, NodeId};
use std::collections::{BTreeMap, BTreeSet};
use std::net::SocketAddr;
#[derive(Debug, Default)]
pub(crate) struct Outcome {
pub(crate) replies: Vec<(Node, Dict)>,
pub(crate) errors: Vec<String>,
}
pub(crate) struct Frontier {
pub(crate) next: Option<SocketAddr>,
pub(crate) waiting: bool,
pub(crate) walking: usize,
pub(crate) door: bool,
}
pub(crate) struct Walk {
target: NodeId,
k: usize,
pub(crate) asked: BTreeSet<SocketAddr>,
replied: BTreeSet<SocketAddr>,
pool: BTreeMap<([u8; 20], SocketAddr), Node>,
pub(crate) out: Outcome,
pub(crate) claims: Vec<SocketAddr>,
}
impl Walk {
pub(crate) fn new(target: NodeId, k: usize) -> Walk {
Walk {
target,
k,
asked: BTreeSet::new(),
replied: BTreeSet::new(),
pool: BTreeMap::new(),
out: Outcome::default(),
claims: Vec::new(),
}
}
pub(crate) fn frontier(&self, flight: &Flight) -> Frontier {
let airborne: BTreeSet<SocketAddr> = flight
.values()
.filter(|q| !q.door)
.map(|q| q.addr)
.collect();
let front: Vec<SocketAddr> = self
.pool
.values()
.map(|n| n.addr)
.filter(|a| self.replied.contains(a) || !self.asked.contains(a) || airborne.contains(a))
.take(self.k)
.collect();
Frontier {
next: front.iter().copied().find(|a| !self.asked.contains(a)),
waiting: front.iter().any(|a| airborne.contains(a)),
walking: flight.values().filter(|q| !q.door).count(),
door: flight.values().any(|q| q.door),
}
}
pub(crate) fn dry(&self) -> bool {
!self.pool.is_empty() && self.replied.len() < self.k
}
pub(crate) fn heard(&mut self, query: Query, message: Message) {
match message {
Message::Reply { r, ip, .. } => {
let Some(id) = r
.get(b"id".as_slice())
.and_then(|v| v.as_bytes())
.and_then(NodeId::parse)
else {
return;
};
let node = Node {
id,
addr: query.addr,
};
self.claims.extend(ip);
let this = (!query.door).then_some(node);
for near in krpc::nodes_of(&r).into_iter().chain(this) {
self.pool
.insert((near.id.distance(&self.target), near.addr), near);
}
if !query.door {
self.replied.insert(query.addr);
self.out.replies.push((node, r));
}
}
Message::Error { code, message, .. } if !query.door => {
self.out
.errors
.push(format!("{}: {code} {message}", query.addr));
}
Message::Error { .. } => {}
}
}
}