use std::{collections::BTreeMap, net::SocketAddr};
use n0_future::time::Instant;
use tracing::{Level, event};
use super::{IpPort, path_state::PathState};
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
pub(super) enum UdpSendAddr {
Valid(SocketAddr),
Outdated(SocketAddr),
Unconfirmed(SocketAddr),
#[default]
None,
}
impl UdpSendAddr {
pub fn get_addr(&self) -> Option<SocketAddr> {
match self {
UdpSendAddr::Valid(addr)
| UdpSendAddr::Outdated(addr)
| UdpSendAddr::Unconfirmed(addr) => Some(*addr),
UdpSendAddr::None => None,
}
}
}
#[derive(Debug, Default)]
pub(super) struct NodeUdpPaths {
paths: BTreeMap<IpPort, PathState>,
best: UdpSendAddr,
best_ipv4: UdpSendAddr,
}
pub(super) struct MutAccess<'a> {
now: Instant,
inner: &'a mut NodeUdpPaths,
}
impl<'a> MutAccess<'a> {
pub fn paths(&mut self) -> &mut BTreeMap<IpPort, PathState> {
&mut self.inner.paths
}
pub fn has_best_addr_changed(self) -> bool {
let changed = self.inner.update_to_best_addr(self.now);
std::mem::forget(self); changed
}
}
impl Drop for MutAccess<'_> {
fn drop(&mut self) {
self.inner.update_to_best_addr(self.now);
}
}
impl NodeUdpPaths {
pub(super) fn new() -> Self {
Default::default()
}
#[cfg(test)]
pub(super) fn from_parts(paths: BTreeMap<IpPort, PathState>, best: UdpSendAddr) -> Self {
Self {
paths,
best_ipv4: best, best,
}
}
pub(super) fn send_addr(&self, have_ipv6: bool) -> &UdpSendAddr {
if !have_ipv6 {
if matches!(&self.best, UdpSendAddr::Valid(_)) {
return &self.best;
}
return &self.best_ipv4;
}
&self.best
}
pub(super) fn access_mut(&mut self, now: Instant) -> MutAccess<'_> {
MutAccess { now, inner: self }
}
pub(super) fn paths(&self) -> &BTreeMap<IpPort, PathState> {
&self.paths
}
fn update_to_best_addr(&mut self, now: Instant) -> bool {
let best_ipv4 = self.best_addr(false, now);
let best = self.best_addr(true, now);
let mut changed = false;
if best_ipv4 != self.best_ipv4 {
event!(
target: "iroh::_events::udp::best_ipv4",
Level::DEBUG,
?best_ipv4,
);
changed = true;
}
if best != self.best {
event!(
target: "iroh::_events::udp::best",
Level::DEBUG,
?best,
);
changed = true;
}
self.best_ipv4 = best_ipv4;
self.best = best;
changed
}
fn best_addr(&self, have_ipv6: bool, now: Instant) -> UdpSendAddr {
let Some((ipp, path)) = self
.paths
.iter()
.filter(|(ipp, _)| have_ipv6 || ipp.ip.is_ipv4())
.max_by_key(|(ipp, path)| {
let is_ipv6 = ipp.ip.is_ipv6();
if let Some(latency) = path.validity.latency_if_valid(now) {
(Some(ReverseOrd(latency)), None, is_ipv6)
} else if let Some(latency) = path.validity.latency_if_outdated(now) {
(None, Some(ReverseOrd(latency)), is_ipv6)
} else {
(None, None, is_ipv6)
}
})
else {
return UdpSendAddr::None;
};
if path.validity.is_valid(now) {
UdpSendAddr::Valid((*ipp).into())
} else if path.validity.is_outdated(now) {
UdpSendAddr::Outdated((*ipp).into())
} else {
UdpSendAddr::Unconfirmed((*ipp).into())
}
}
}
#[derive(PartialEq, Eq)]
struct ReverseOrd<N: PartialOrd + Ord + PartialEq + Eq>(N);
impl<N: PartialOrd + Ord + PartialEq + Eq> Ord for ReverseOrd<N> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0).reverse()
}
}
impl<N: PartialOrd + Ord + PartialEq + Eq> PartialOrd for ReverseOrd<N> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}