use std::{
collections::BTreeMap,
net::SocketAddr,
time::{Duration, Instant},
};
use rand::seq::IteratorRandom;
use tracing::warn;
use super::{
best_addr::{self, BestAddr},
node_state::PongReply,
path_state::PathState,
IpPort,
};
use crate::disco::SendAddr;
#[derive(Debug)]
pub(super) enum UdpSendAddr {
Valid(SocketAddr),
Outdated(SocketAddr),
Unconfirmed(SocketAddr),
None,
}
#[derive(Debug, Default)]
pub(super) struct NodeUdpPaths {
pub(super) paths: BTreeMap<IpPort, PathState>,
pub(super) best_addr: BestAddr,
chosen_candidate: Option<IpPort>,
}
impl NodeUdpPaths {
pub(super) fn new() -> Self {
Default::default()
}
#[cfg(test)]
pub(super) fn from_parts(paths: BTreeMap<IpPort, PathState>, best_addr: BestAddr) -> Self {
Self {
paths,
best_addr,
chosen_candidate: None,
}
}
pub(super) fn send_addr(&mut self, now: Instant, have_ipv6: bool) -> UdpSendAddr {
self.assign_best_addr_from_candidates_if_empty();
match self.best_addr.state(now) {
best_addr::State::Valid(addr) => UdpSendAddr::Valid(addr.addr),
best_addr::State::Outdated(addr) => UdpSendAddr::Outdated(addr.addr),
best_addr::State::Empty => {
let addr = self
.chosen_candidate
.and_then(|ipp| self.paths.get(&ipp))
.and_then(|path| path.udp_addr())
.filter(|addr| addr.is_ipv4() || have_ipv6)
.or_else(|| {
let addr = self
.paths
.values()
.filter_map(|path| path.udp_addr())
.filter(|addr| addr.is_ipv4() || have_ipv6)
.choose(&mut rand::thread_rng());
self.chosen_candidate = addr.map(IpPort::from);
addr
});
match addr {
Some(addr) => UdpSendAddr::Unconfirmed(addr),
None => UdpSendAddr::None,
}
}
}
}
fn assign_best_addr_from_candidates_if_empty(&mut self) {
if !self.best_addr.is_empty() {
return;
}
const MAX_LATENCY: Duration = Duration::from_secs(60 * 60);
let best_pong = self.paths.iter().fold(None, |best_pong, (ipp, state)| {
let best_latency = best_pong
.map(|p: &PongReply| p.latency)
.unwrap_or(MAX_LATENCY);
match state.recent_pong {
Some(ref pong)
if pong.latency < best_latency
|| (pong.latency == best_latency && ipp.ip().is_ipv6()) =>
{
Some(pong)
}
_ => best_pong,
}
});
if let Some(pong) = best_pong {
if let SendAddr::Udp(addr) = pong.from {
warn!(%addr, "No best_addr was set, choose candidate with lowest latency");
self.best_addr.insert_if_better_or_reconfirm(
addr,
pong.latency,
best_addr::Source::BestCandidate,
pong.pong_at,
)
}
}
}
}