use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct PathCandidateId(u64);
impl PathCandidateId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PathTraceId(u64);
impl PathTraceId {
#[must_use]
pub const fn new(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathKind {
LanMulticast,
ExplicitPublicUdp,
PublicIpv6,
NatPunchedUdp,
TailscaleIp,
AtpRelayUdp,
AtpRelayTcpTls443,
MasqueConnectUdp,
OfflineMailbox,
}
impl PathKind {
pub const ALL: [Self; 9] = [
Self::LanMulticast,
Self::ExplicitPublicUdp,
Self::PublicIpv6,
Self::NatPunchedUdp,
Self::TailscaleIp,
Self::AtpRelayUdp,
Self::AtpRelayTcpTls443,
Self::MasqueConnectUdp,
Self::OfflineMailbox,
];
#[must_use]
pub const fn family(self) -> PathFamily {
match self {
Self::LanMulticast
| Self::ExplicitPublicUdp
| Self::PublicIpv6
| Self::NatPunchedUdp => PathFamily::Direct,
Self::TailscaleIp => PathFamily::Tailscale,
Self::AtpRelayUdp | Self::AtpRelayTcpTls443 | Self::MasqueConnectUdp => {
PathFamily::Relay
}
Self::OfflineMailbox => PathFamily::OfflineMailbox,
}
}
#[must_use]
pub const fn is_direct(self) -> bool {
matches!(self.family(), PathFamily::Direct)
}
#[must_use]
pub const fn is_relay(self) -> bool {
matches!(self.family(), PathFamily::Relay)
}
#[must_use]
pub const fn uses_connect_udp_proxy(self) -> bool {
matches!(self, Self::MasqueConnectUdp)
}
#[must_use]
pub const fn has_nested_congestion_caveat(self) -> bool {
matches!(self, Self::AtpRelayTcpTls443 | Self::MasqueConnectUdp)
}
#[must_use]
pub const fn requires_intermediary_authority(self) -> bool {
matches!(self, Self::MasqueConnectUdp)
}
#[must_use]
pub const fn proof_summary_label(self) -> &'static str {
match self {
Self::LanMulticast => "lan_multicast",
Self::ExplicitPublicUdp => "explicit_public_udp",
Self::PublicIpv6 => "public_ipv6",
Self::NatPunchedUdp => "nat_punched_udp",
Self::TailscaleIp => "tailscale_ip",
Self::AtpRelayUdp => "atp_relay_udp",
Self::AtpRelayTcpTls443 => "atp_relay_tcp_tls_443",
Self::MasqueConnectUdp => "masque_connect_udp_adapter",
Self::OfflineMailbox => "offline_mailbox",
}
}
#[must_use]
pub const fn adapter_failure_hint(self) -> Option<&'static str> {
match self {
Self::MasqueConnectUdp => Some("proxy_auth_policy_or_connect_udp_failure"),
Self::AtpRelayTcpTls443 => Some("tcp_head_of_line_or_nested_retransmission"),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PathFamily {
Direct,
Tailscale,
Relay,
OfflineMailbox,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathSecurity {
pub authenticated_peer: bool,
pub end_to_end_encrypted: bool,
pub exposes_local_ip_to_peer: bool,
pub relay_metadata_visible: bool,
pub store_and_forward: bool,
}
impl PathSecurity {
#[must_use]
pub const fn for_kind(kind: PathKind) -> Self {
match kind {
PathKind::LanMulticast
| PathKind::ExplicitPublicUdp
| PathKind::PublicIpv6
| PathKind::NatPunchedUdp
| PathKind::TailscaleIp => Self {
authenticated_peer: true,
end_to_end_encrypted: true,
exposes_local_ip_to_peer: true,
relay_metadata_visible: false,
store_and_forward: false,
},
PathKind::AtpRelayUdp | PathKind::AtpRelayTcpTls443 | PathKind::MasqueConnectUdp => {
Self {
authenticated_peer: true,
end_to_end_encrypted: true,
exposes_local_ip_to_peer: false,
relay_metadata_visible: true,
store_and_forward: false,
}
}
PathKind::OfflineMailbox => Self {
authenticated_peer: true,
end_to_end_encrypted: true,
exposes_local_ip_to_peer: false,
relay_metadata_visible: true,
store_and_forward: true,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathBudget {
pub connect_timeout_micros: u64,
pub loser_drain_timeout_micros: u64,
pub max_probe_bytes: u64,
}
impl Default for PathBudget {
fn default() -> Self {
Self {
connect_timeout_micros: 3_000_000,
loser_drain_timeout_micros: 250_000,
max_probe_bytes: 16 * 1_200,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathSuccessKind {
DirectValidated,
TailscaleSelected,
RelaySelected,
MailboxAccepted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathFailureKind {
Timeout,
HardNat,
UdpBlocked,
AuthFailure,
PolicyDenied,
RelayUnavailable,
UnsupportedPlatform,
ProtocolError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathCancelReason {
LoserOfRace,
ParentCancelled,
BudgetExceeded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathOutcomeResult {
Success(PathSuccessKind),
Failure(PathFailureKind),
Cancelled(PathCancelReason),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathOutcome {
pub result: PathOutcomeResult,
pub completed_at_micros: u64,
pub observed_rtt_micros: Option<u64>,
pub bytes_sent: u64,
pub bytes_received: u64,
}
impl PathOutcome {
#[must_use]
pub const fn success(
kind: PathSuccessKind,
completed_at_micros: u64,
observed_rtt_micros: Option<u64>,
) -> Self {
Self {
result: PathOutcomeResult::Success(kind),
completed_at_micros,
observed_rtt_micros,
bytes_sent: 0,
bytes_received: 0,
}
}
#[must_use]
pub const fn failure(kind: PathFailureKind, completed_at_micros: u64) -> Self {
Self {
result: PathOutcomeResult::Failure(kind),
completed_at_micros,
observed_rtt_micros: None,
bytes_sent: 0,
bytes_received: 0,
}
}
#[must_use]
pub const fn cancelled(reason: PathCancelReason, completed_at_micros: u64) -> Self {
Self {
result: PathOutcomeResult::Cancelled(reason),
completed_at_micros,
observed_rtt_micros: None,
bytes_sent: 0,
bytes_received: 0,
}
}
#[must_use]
pub const fn with_bytes(mut self, bytes_sent: u64, bytes_received: u64) -> Self {
self.bytes_sent = bytes_sent;
self.bytes_received = bytes_received;
self
}
#[must_use]
pub const fn is_success(self) -> bool {
matches!(self.result, PathOutcomeResult::Success(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathAttemptState {
Pending,
Racing,
Succeeded(PathOutcome),
Failed(PathOutcome),
Cancelled(PathOutcome),
DrainedLoser {
winner: PathCandidateId,
outcome: PathOutcome,
},
}
impl PathAttemptState {
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(
self,
Self::Succeeded(_) | Self::Failed(_) | Self::Cancelled(_) | Self::DrainedLoser { .. }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathCandidate {
pub id: PathCandidateId,
pub kind: PathKind,
pub trace_id: PathTraceId,
pub budget: PathBudget,
pub security: PathSecurity,
pub state: PathAttemptState,
}
impl PathCandidate {
#[must_use]
pub fn new(id: PathCandidateId, kind: PathKind, trace_id: PathTraceId) -> Self {
Self {
id,
kind,
trace_id,
budget: PathBudget::default(),
security: PathSecurity::for_kind(kind),
state: PathAttemptState::Pending,
}
}
#[must_use]
pub const fn with_budget(mut self, budget: PathBudget) -> Self {
self.budget = budget;
self
}
#[must_use]
pub const fn with_security(mut self, security: PathSecurity) -> Self {
self.security = security;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathLoserCleanup {
pub candidate_id: PathCandidateId,
pub winner: PathCandidateId,
pub cancel_requested: bool,
pub drained: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathSelectionReason {
DirectCandidateValidated,
TailscaleCandidateValidated,
RelayFallbackValidated,
OfflineMailboxAccepted,
RaceStillPending,
NoSuccessfulCandidate,
MissingWinnerCandidate,
}
impl PathSelectionReason {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::DirectCandidateValidated => "direct_candidate_validated",
Self::TailscaleCandidateValidated => "tailscale_candidate_validated",
Self::RelayFallbackValidated => "relay_fallback_validated",
Self::OfflineMailboxAccepted => "offline_mailbox_accepted",
Self::RaceStillPending => "race_still_pending",
Self::NoSuccessfulCandidate => "no_successful_candidate",
Self::MissingWinnerCandidate => "missing_winner_candidate",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathDiagnosticSnapshot {
pub winner: Option<PathCandidateId>,
pub selected_kind: Option<PathKind>,
pub reason: PathSelectionReason,
pub candidate_count: usize,
pub racing_count: usize,
pub success_count: usize,
pub failure_count: usize,
pub cancelled_count: usize,
pub drained_loser_count: usize,
pub direct_count: usize,
pub tailscale_count: usize,
pub relay_count: usize,
pub mailbox_count: usize,
}
impl PathDiagnosticSnapshot {
#[must_use]
pub fn selected_family(self) -> Option<PathFamily> {
self.selected_kind.map(PathKind::family)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathRaceError {
DuplicateCandidate(PathCandidateId),
UnknownCandidate(PathCandidateId),
TerminalCandidate(PathCandidateId),
}
impl fmt::Display for PathRaceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateCandidate(id) => write!(f, "duplicate path candidate {}", id.get()),
Self::UnknownCandidate(id) => write!(f, "unknown path candidate {}", id.get()),
Self::TerminalCandidate(id) => {
write!(
f,
"terminal path candidate cannot be restarted {}",
id.get()
)
}
}
}
}
impl std::error::Error for PathRaceError {}
#[derive(Debug, Clone, Default)]
pub struct PathRace {
candidates: BTreeMap<PathCandidateId, PathCandidate>,
winner: Option<PathCandidateId>,
cleanup: Vec<PathLoserCleanup>,
}
impl PathRace {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_candidate(&mut self, candidate: PathCandidate) -> Result<(), PathRaceError> {
if self.candidates.contains_key(&candidate.id) {
return Err(PathRaceError::DuplicateCandidate(candidate.id));
}
self.candidates.insert(candidate.id, candidate);
Ok(())
}
pub fn start_candidate(&mut self, id: PathCandidateId) -> Result<(), PathRaceError> {
let candidate = self
.candidates
.get_mut(&id)
.ok_or(PathRaceError::UnknownCandidate(id))?;
if candidate.state.is_terminal() {
return Err(PathRaceError::TerminalCandidate(id));
}
candidate.state = PathAttemptState::Racing;
Ok(())
}
pub fn start_all(&mut self) -> Result<(), PathRaceError> {
let ids = self.candidates.keys().copied().collect::<Vec<_>>();
for id in ids {
self.start_candidate(id)?;
}
Ok(())
}
pub fn record_outcome(
&mut self,
id: PathCandidateId,
outcome: PathOutcome,
) -> Result<(), PathRaceError> {
let existing_state = self
.candidates
.get(&id)
.ok_or(PathRaceError::UnknownCandidate(id))?
.state;
if existing_state.is_terminal() {
return Ok(());
}
if let Some(winner) = self.winner {
if winner != id {
self.drain_loser(id, winner, outcome)?;
}
return Ok(());
}
let candidate = self
.candidates
.get_mut(&id)
.ok_or(PathRaceError::UnknownCandidate(id))?;
candidate.state = match outcome.result {
PathOutcomeResult::Success(_) => PathAttemptState::Succeeded(outcome),
PathOutcomeResult::Failure(_) => PathAttemptState::Failed(outcome),
PathOutcomeResult::Cancelled(_) => PathAttemptState::Cancelled(outcome),
};
if outcome.is_success() && self.winner.is_none() {
self.winner = Some(id);
self.drain_active_losers(id, outcome.completed_at_micros)?;
}
Ok(())
}
#[must_use]
pub const fn winner(&self) -> Option<PathCandidateId> {
self.winner
}
#[must_use]
pub fn candidate(&self, id: PathCandidateId) -> Option<&PathCandidate> {
self.candidates.get(&id)
}
#[must_use = "iterators are lazy; consume the returned iterator"]
pub fn candidates(&self) -> impl Iterator<Item = &PathCandidate> + '_ {
self.candidates.values()
}
#[must_use]
pub fn cleanup_records(&self) -> &[PathLoserCleanup] {
&self.cleanup
}
#[must_use]
pub fn diagnostic_snapshot(&self) -> PathDiagnosticSnapshot {
let mut snapshot = PathDiagnosticSnapshot {
winner: self.winner,
selected_kind: self
.winner
.and_then(|winner| self.candidates.get(&winner).map(|candidate| candidate.kind)),
reason: PathSelectionReason::RaceStillPending,
candidate_count: self.candidates.len(),
racing_count: 0,
success_count: 0,
failure_count: 0,
cancelled_count: 0,
drained_loser_count: 0,
direct_count: 0,
tailscale_count: 0,
relay_count: 0,
mailbox_count: 0,
};
for candidate in self.candidates.values() {
match candidate.kind.family() {
PathFamily::Direct => snapshot.direct_count += 1,
PathFamily::Tailscale => snapshot.tailscale_count += 1,
PathFamily::Relay => snapshot.relay_count += 1,
PathFamily::OfflineMailbox => snapshot.mailbox_count += 1,
}
match candidate.state {
PathAttemptState::Pending => {}
PathAttemptState::Racing => snapshot.racing_count += 1,
PathAttemptState::Succeeded(_) => snapshot.success_count += 1,
PathAttemptState::Failed(_) => snapshot.failure_count += 1,
PathAttemptState::Cancelled(_) => snapshot.cancelled_count += 1,
PathAttemptState::DrainedLoser { .. } => snapshot.drained_loser_count += 1,
}
}
snapshot.reason = match (snapshot.winner, snapshot.selected_kind) {
(Some(_), Some(kind)) => match kind.family() {
PathFamily::Direct => PathSelectionReason::DirectCandidateValidated,
PathFamily::Tailscale => PathSelectionReason::TailscaleCandidateValidated,
PathFamily::Relay => PathSelectionReason::RelayFallbackValidated,
PathFamily::OfflineMailbox => PathSelectionReason::OfflineMailboxAccepted,
},
(Some(_), None) => PathSelectionReason::MissingWinnerCandidate,
(None, None) if self.all_terminal() => PathSelectionReason::NoSuccessfulCandidate,
(None, None) => PathSelectionReason::RaceStillPending,
(None, Some(_)) => PathSelectionReason::MissingWinnerCandidate,
};
snapshot
}
#[must_use]
pub fn all_terminal(&self) -> bool {
self.candidates
.values()
.all(|candidate| candidate.state.is_terminal())
}
fn drain_active_losers(
&mut self,
winner: PathCandidateId,
completed_at_micros: u64,
) -> Result<(), PathRaceError> {
let loser_ids = self
.candidates
.keys()
.copied()
.filter(|id| *id != winner)
.collect::<Vec<_>>(); for loser_id in loser_ids {
let loser_state = self
.candidates
.get(&loser_id)
.ok_or(PathRaceError::UnknownCandidate(loser_id))?
.state;
if loser_state.is_terminal() {
continue;
}
let outcome =
PathOutcome::cancelled(PathCancelReason::LoserOfRace, completed_at_micros);
self.drain_loser(loser_id, winner, outcome)?;
}
Ok(())
}
fn drain_loser(
&mut self,
loser_id: PathCandidateId,
winner: PathCandidateId,
outcome: PathOutcome,
) -> Result<(), PathRaceError> {
let loser = self
.candidates
.get_mut(&loser_id)
.ok_or(PathRaceError::UnknownCandidate(loser_id))?;
loser.state = PathAttemptState::DrainedLoser { winner, outcome };
if !self
.cleanup
.iter()
.any(|record| record.candidate_id == loser_id)
{
self.cleanup.push(PathLoserCleanup {
candidate_id: loser_id,
winner,
cancel_requested: true,
drained: true,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn candidate(raw: u64, kind: PathKind) -> PathCandidate {
PathCandidate::new(PathCandidateId::new(raw), kind, PathTraceId::new(raw + 100))
}
#[test]
fn path_kind_all_contains_every_candidate_type() {
assert_eq!(PathKind::ALL.len(), 9);
assert!(PathKind::ALL.contains(&PathKind::LanMulticast));
assert!(PathKind::ALL.contains(&PathKind::ExplicitPublicUdp));
assert!(PathKind::ALL.contains(&PathKind::PublicIpv6));
assert!(PathKind::ALL.contains(&PathKind::NatPunchedUdp));
assert!(PathKind::ALL.contains(&PathKind::TailscaleIp));
assert!(PathKind::ALL.contains(&PathKind::AtpRelayUdp));
assert!(PathKind::ALL.contains(&PathKind::AtpRelayTcpTls443));
assert!(PathKind::ALL.contains(&PathKind::MasqueConnectUdp));
assert!(PathKind::ALL.contains(&PathKind::OfflineMailbox));
}
#[test]
fn security_defaults_distinguish_direct_relay_and_mailbox_paths() {
let direct = PathSecurity::for_kind(PathKind::NatPunchedUdp);
assert!(direct.exposes_local_ip_to_peer);
assert!(!direct.relay_metadata_visible);
assert!(!direct.store_and_forward);
let relay = PathSecurity::for_kind(PathKind::AtpRelayTcpTls443);
assert!(!relay.exposes_local_ip_to_peer);
assert!(relay.relay_metadata_visible);
assert!(!relay.store_and_forward);
let mailbox = PathSecurity::for_kind(PathKind::OfflineMailbox);
assert!(!mailbox.exposes_local_ip_to_peer);
assert!(mailbox.relay_metadata_visible);
assert!(mailbox.store_and_forward);
}
#[test]
fn path_family_classifies_direct_relay_tailscale_and_mailbox() {
assert_eq!(PathKind::LanMulticast.family(), PathFamily::Direct);
assert_eq!(PathKind::ExplicitPublicUdp.family(), PathFamily::Direct);
assert_eq!(PathKind::PublicIpv6.family(), PathFamily::Direct);
assert_eq!(PathKind::NatPunchedUdp.family(), PathFamily::Direct);
assert_eq!(PathKind::TailscaleIp.family(), PathFamily::Tailscale);
assert_eq!(PathKind::AtpRelayUdp.family(), PathFamily::Relay);
assert_eq!(PathKind::AtpRelayTcpTls443.family(), PathFamily::Relay);
assert_eq!(PathKind::MasqueConnectUdp.family(), PathFamily::Relay);
assert_eq!(
PathKind::OfflineMailbox.family(),
PathFamily::OfflineMailbox
);
assert!(PathKind::PublicIpv6.is_direct());
assert!(PathKind::AtpRelayTcpTls443.is_relay());
}
#[test]
fn masque_connect_udp_adapter_keeps_relay_family_and_proof_caveats() {
let kind = PathKind::MasqueConnectUdp;
let security = PathSecurity::for_kind(kind);
assert_eq!(kind.family(), PathFamily::Relay);
assert!(kind.is_relay());
assert!(kind.uses_connect_udp_proxy());
assert!(kind.has_nested_congestion_caveat());
assert!(kind.requires_intermediary_authority());
assert_eq!(kind.proof_summary_label(), "masque_connect_udp_adapter");
assert_eq!(
kind.adapter_failure_hint(),
Some("proxy_auth_policy_or_connect_udp_failure")
);
assert!(security.authenticated_peer);
assert!(security.end_to_end_encrypted);
assert!(!security.exposes_local_ip_to_peer);
assert!(security.relay_metadata_visible);
assert!(!security.store_and_forward);
}
#[test]
fn first_success_wins_and_active_losers_are_drained() {
let direct = PathCandidateId::new(1);
let relay = PathCandidateId::new(2);
let tailscale = PathCandidateId::new(3);
let mut race = PathRace::new();
race.add_candidate(candidate(direct.get(), PathKind::NatPunchedUdp))
.expect("direct candidate");
race.add_candidate(candidate(relay.get(), PathKind::AtpRelayUdp))
.expect("relay candidate");
race.add_candidate(candidate(tailscale.get(), PathKind::TailscaleIp))
.expect("tailscale candidate");
race.start_all().expect("start race");
race.record_outcome(
relay,
PathOutcome::success(PathSuccessKind::RelaySelected, 20_000, Some(8_000))
.with_bytes(240, 240),
)
.expect("record relay success");
assert_eq!(race.winner(), Some(relay));
assert!(race.all_terminal());
assert_eq!(race.cleanup_records().len(), 2);
assert!(
race.cleanup_records().iter().all(|record| {
record.winner == relay && record.cancel_requested && record.drained
})
);
assert!(matches!(
race.candidate(direct).expect("direct").state,
PathAttemptState::DrainedLoser { winner, .. } if winner == relay
));
assert!(matches!(
race.candidate(tailscale).expect("tailscale").state,
PathAttemptState::DrainedLoser { winner, .. } if winner == relay
));
let snapshot = race.diagnostic_snapshot();
assert_eq!(snapshot.winner, Some(relay));
assert_eq!(snapshot.selected_kind, Some(PathKind::AtpRelayUdp));
assert_eq!(snapshot.reason, PathSelectionReason::RelayFallbackValidated);
assert_eq!(snapshot.reason.code(), "relay_fallback_validated");
assert_eq!(snapshot.selected_family(), Some(PathFamily::Relay));
assert_eq!(snapshot.direct_count, 1);
assert_eq!(snapshot.tailscale_count, 1);
assert_eq!(snapshot.relay_count, 1);
assert_eq!(snapshot.drained_loser_count, 2);
}
#[test]
fn failures_do_not_select_winner() {
let direct = PathCandidateId::new(1);
let public_ipv6 = PathCandidateId::new(2);
let mut race = PathRace::new();
race.add_candidate(candidate(direct.get(), PathKind::NatPunchedUdp))
.expect("direct candidate");
race.add_candidate(candidate(public_ipv6.get(), PathKind::PublicIpv6))
.expect("ipv6 candidate");
race.start_all().expect("start race");
race.record_outcome(direct, PathOutcome::failure(PathFailureKind::HardNat, 10))
.expect("hard nat");
race.record_outcome(
public_ipv6,
PathOutcome::failure(PathFailureKind::UdpBlocked, 12),
)
.expect("udp blocked");
assert_eq!(race.winner(), None);
assert!(race.all_terminal());
assert!(race.cleanup_records().is_empty());
let snapshot = race.diagnostic_snapshot();
assert_eq!(snapshot.reason, PathSelectionReason::NoSuccessfulCandidate);
assert_eq!(snapshot.failure_count, 2);
assert_eq!(snapshot.selected_kind, None);
}
#[test]
fn late_loser_outcome_cannot_replace_winner() {
let direct = PathCandidateId::new(1);
let relay = PathCandidateId::new(2);
let mut race = PathRace::new();
race.add_candidate(candidate(direct.get(), PathKind::NatPunchedUdp))
.expect("direct candidate");
race.add_candidate(candidate(relay.get(), PathKind::AtpRelayUdp))
.expect("relay candidate");
race.start_all().expect("start race");
race.record_outcome(
direct,
PathOutcome::success(PathSuccessKind::DirectValidated, 10, Some(3)),
)
.expect("direct wins");
race.record_outcome(
relay,
PathOutcome::success(PathSuccessKind::RelaySelected, 11, Some(4)),
)
.expect("late loser is drained");
assert_eq!(race.winner(), Some(direct));
assert!(matches!(
race.candidate(relay).expect("relay").state,
PathAttemptState::DrainedLoser { winner, .. } if winner == direct
));
let snapshot = race.diagnostic_snapshot();
assert_eq!(
snapshot.reason,
PathSelectionReason::DirectCandidateValidated
);
assert_eq!(snapshot.selected_kind, Some(PathKind::NatPunchedUdp));
assert_eq!(snapshot.selected_family(), Some(PathFamily::Direct));
}
#[test]
fn late_winner_outcome_cannot_overwrite_selected_success() {
let relay = PathCandidateId::new(1);
let mut race = PathRace::new();
race.add_candidate(candidate(relay.get(), PathKind::AtpRelayTcpTls443))
.expect("relay candidate");
race.start_all().expect("start race");
race.record_outcome(
relay,
PathOutcome::success(PathSuccessKind::RelaySelected, 10, Some(4)).with_bytes(256, 128),
)
.expect("relay wins");
race.record_outcome(
relay,
PathOutcome::failure(PathFailureKind::RelayUnavailable, 11),
)
.expect("late duplicate outcome is idempotent");
assert_eq!(race.winner(), Some(relay));
assert!(matches!(
race.candidate(relay).expect("relay").state,
PathAttemptState::Succeeded(outcome)
if outcome.result == PathOutcomeResult::Success(PathSuccessKind::RelaySelected)
&& outcome.bytes_sent == 256
&& outcome.bytes_received == 128
));
let snapshot = race.diagnostic_snapshot();
assert_eq!(snapshot.success_count, 1);
assert_eq!(snapshot.failure_count, 0);
assert_eq!(snapshot.reason, PathSelectionReason::RelayFallbackValidated);
}
#[test]
fn terminal_failures_before_winner_are_not_reclassified_as_drained_losers() {
let direct = PathCandidateId::new(1);
let relay = PathCandidateId::new(2);
let mut race = PathRace::new();
race.add_candidate(candidate(direct.get(), PathKind::NatPunchedUdp))
.expect("direct candidate");
race.add_candidate(candidate(relay.get(), PathKind::AtpRelayUdp))
.expect("relay candidate");
race.start_all().expect("start race");
race.record_outcome(
direct,
PathOutcome::failure(PathFailureKind::UdpBlocked, 10),
)
.expect("direct failed before relay won");
race.record_outcome(
relay,
PathOutcome::success(PathSuccessKind::RelaySelected, 20, Some(6)),
)
.expect("relay wins");
race.record_outcome(direct, PathOutcome::failure(PathFailureKind::Timeout, 30))
.expect("late duplicate direct failure is idempotent");
assert!(matches!(
race.candidate(direct).expect("direct").state,
PathAttemptState::Failed(outcome)
if outcome.result == PathOutcomeResult::Failure(PathFailureKind::UdpBlocked)
));
assert!(race.cleanup_records().is_empty());
let snapshot = race.diagnostic_snapshot();
assert_eq!(snapshot.failure_count, 1);
assert_eq!(snapshot.drained_loser_count, 0);
assert_eq!(snapshot.reason, PathSelectionReason::RelayFallbackValidated);
}
#[test]
fn diagnostic_snapshot_reports_pending_race() {
let mut race = PathRace::new();
race.add_candidate(candidate(1, PathKind::PublicIpv6))
.expect("ipv6 candidate");
race.add_candidate(candidate(2, PathKind::TailscaleIp))
.expect("tailscale candidate");
race.start_candidate(PathCandidateId::new(1))
.expect("start ipv6");
let snapshot = race.diagnostic_snapshot();
assert_eq!(snapshot.reason, PathSelectionReason::RaceStillPending);
assert_eq!(snapshot.candidate_count, 2);
assert_eq!(snapshot.racing_count, 1);
assert_eq!(snapshot.direct_count, 1);
assert_eq!(snapshot.tailscale_count, 1);
assert_eq!(snapshot.selected_kind, None);
}
#[test]
fn policy_denied_is_a_failure_not_success() {
let outcome = PathOutcome::failure(PathFailureKind::PolicyDenied, 42);
assert!(!outcome.is_success());
assert!(matches!(
outcome.result,
PathOutcomeResult::Failure(PathFailureKind::PolicyDenied)
));
}
#[test]
fn duplicate_candidate_ids_are_rejected() {
let mut race = PathRace::new();
let first = candidate(1, PathKind::PublicIpv6);
let duplicate = candidate(1, PathKind::AtpRelayUdp);
race.add_candidate(first).expect("first candidate");
let err = race
.add_candidate(duplicate)
.expect_err("duplicate id must fail");
assert_eq!(
err,
PathRaceError::DuplicateCandidate(PathCandidateId::new(1))
);
}
}