use std::{
net::SocketAddr,
time::{Duration, Instant},
};
use tracing::{debug, info};
const TRUST_UDP_ADDR_DURATION: Duration = Duration::from_millis(6500);
#[derive(Debug, Default)]
pub(super) struct BestAddr(Option<BestAddrInner>);
#[derive(Debug)]
struct BestAddrInner {
addr: AddrLatency,
trust_until: Option<Instant>,
confirmed_at: Instant,
}
impl BestAddrInner {
fn is_trusted(&self, now: Instant) -> bool {
self.trust_until
.map(|trust_until| trust_until >= now)
.unwrap_or(false)
}
fn addr(&self) -> SocketAddr {
self.addr.addr
}
}
#[derive(Debug)]
pub(super) enum Source {
ReceivedPong,
BestCandidate,
Udp,
}
impl Source {
fn trust_until(&self, from: Instant) -> Instant {
match self {
Source::ReceivedPong => from + TRUST_UDP_ADDR_DURATION,
Source::BestCandidate => from + Duration::from_secs(60 * 60),
Source::Udp => from + TRUST_UDP_ADDR_DURATION,
}
}
}
#[derive(Debug)]
pub(super) enum State<'a> {
Valid(&'a AddrLatency),
Outdated(&'a AddrLatency),
Empty,
}
#[derive(Debug, Clone, Copy)]
pub enum ClearReason {
Reset,
Inactive,
PongTimeout,
MatchesOurLocalAddr,
}
impl BestAddr {
#[cfg(test)]
pub fn from_parts(
addr: SocketAddr,
latency: Duration,
confirmed_at: Instant,
trust_until: Instant,
) -> Self {
let inner = BestAddrInner {
addr: AddrLatency { addr, latency },
confirmed_at,
trust_until: Some(trust_until),
};
Self(Some(inner))
}
pub fn is_empty(&self) -> bool {
self.0.is_none()
}
pub fn clear(&mut self, reason: ClearReason, has_relay: bool) {
let old = self.0.take();
if let Some(old_addr) = old.as_ref().map(BestAddrInner::addr) {
info!(?reason, ?has_relay, %old_addr, "clearing best_addr");
}
}
pub fn clear_if_equals(&mut self, addr: SocketAddr, reason: ClearReason, has_relay: bool) {
if self.addr() == Some(addr) {
self.clear(reason, has_relay)
}
}
pub fn clear_trust(&mut self, why: &'static str) {
if let Some(state) = self.0.as_mut() {
info!(
%why,
prev_trust_until = ?state.trust_until,
"clearing best_addr trust",
);
state.trust_until = None;
}
}
pub fn insert_if_better_or_reconfirm(
&mut self,
addr: SocketAddr,
latency: Duration,
source: Source,
confirmed_at: Instant,
) {
match self.0.as_mut() {
None => {
self.insert(addr, latency, source, confirmed_at);
}
Some(state) => {
let candidate = AddrLatency { addr, latency };
if !state.is_trusted(confirmed_at) || candidate.is_better_than(&state.addr) {
self.insert(addr, latency, source, confirmed_at);
} else if state.addr.addr == addr {
state.confirmed_at = confirmed_at;
state.trust_until = Some(source.trust_until(confirmed_at));
}
}
}
}
pub fn reconfirm_if_used(&mut self, addr: SocketAddr, source: Source, confirmed_at: Instant) {
if let Some(state) = self.0.as_mut() {
if state.addr.addr == addr {
state.confirmed_at = confirmed_at;
state.trust_until = Some(source.trust_until(confirmed_at));
}
}
}
fn insert(
&mut self,
addr: SocketAddr,
latency: Duration,
source: Source,
confirmed_at: Instant,
) {
let trust_until = source.trust_until(confirmed_at);
if self
.0
.as_ref()
.map(|prev| prev.addr.addr == addr)
.unwrap_or_default()
{
debug!(
%addr,
latency = ?latency,
trust_for = ?trust_until.duration_since(Instant::now()),
"re-selecting direct path for node"
);
} else {
info!(
%addr,
latency = ?latency,
trust_for = ?trust_until.duration_since(Instant::now()),
"selecting new direct path for node"
);
}
let inner = BestAddrInner {
addr: AddrLatency { addr, latency },
trust_until: Some(trust_until),
confirmed_at,
};
self.0 = Some(inner);
}
pub fn state(&self, now: Instant) -> State {
match &self.0 {
None => State::Empty,
Some(state) => match state.trust_until {
Some(expiry) if now < expiry => State::Valid(&state.addr),
Some(_) | None => State::Outdated(&state.addr),
},
}
}
pub fn addr(&self) -> Option<SocketAddr> {
self.0.as_ref().map(BestAddrInner::addr)
}
}
#[derive(Debug, Clone)]
pub struct AddrLatency {
pub addr: SocketAddr,
pub latency: Duration,
}
impl AddrLatency {
fn is_better_than(&self, other: &Self) -> bool {
if self.addr == other.addr {
return false;
}
if self.addr.is_ipv6() && other.addr.is_ipv4() {
if self.latency / 10 * 9 < other.latency {
return true;
}
} else if self.addr.is_ipv4() && other.addr.is_ipv6() && other.is_better_than(self) {
return false;
}
self.latency < other.latency
}
}