use std::{collections::BTreeSet, fmt, sync::Arc};
use iroh_relay::{RelayConfig, RelayMap};
use n0_future::time::Duration;
use crate::net_report::Report;
const DEFAULT_INITIAL_RETRANSMIT: Duration = Duration::from_millis(100);
const HTTPS_OFFSET: Duration = Duration::from_millis(200);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)]
#[repr(u8)]
pub enum Probe {
Https,
#[cfg(not(wasm_browser))]
QadIpv4,
#[cfg(not(wasm_browser))]
QadIpv6,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct ProbeSet {
proto: Probe,
probes: Vec<(Duration, Arc<RelayConfig>)>,
}
impl ProbeSet {
fn new(proto: Probe) -> Self {
Self {
probes: Vec::new(),
proto,
}
}
pub(super) fn proto(&self) -> Probe {
self.proto
}
fn push(&mut self, delay: Duration, endpoint: Arc<RelayConfig>) {
self.probes.push((delay, endpoint));
}
fn is_empty(&self) -> bool {
self.probes.is_empty()
}
pub(super) fn params(&self) -> impl Iterator<Item = &(Duration, Arc<RelayConfig>)> {
self.probes.iter()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub(super) struct ProbePlan {
set: BTreeSet<ProbeSet>,
}
impl ProbePlan {
pub(super) fn initial(relay_map: &RelayMap, protocols: &BTreeSet<Probe>) -> Self {
let mut plan = Self::default();
for relay in relay_map.relays::<Vec<_>>() {
let mut https_probes = ProbeSet::new(Probe::Https);
for attempt in 0u32..3 {
let delay = HTTPS_OFFSET + DEFAULT_INITIAL_RETRANSMIT * attempt;
https_probes.push(delay, relay.clone());
}
plan.add_if_enabled(protocols, https_probes);
}
plan
}
pub(super) fn with_last_report(
relay_map: &RelayMap,
last_report: &Report,
protocols: &BTreeSet<Probe>,
) -> Self {
if last_report.relay_latency.is_empty() {
return Self::initial(relay_map, protocols);
}
Self::default()
}
pub(super) fn iter(&self) -> impl Iterator<Item = &ProbeSet> {
self.set.iter()
}
fn add_if_enabled(&mut self, protocols: &BTreeSet<Probe>, set: ProbeSet) {
if !set.is_empty() && protocols.contains(&set.proto) {
self.set.insert(set);
}
}
}
impl fmt::Display for ProbePlan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "ProbePlan {{")?;
for probe_set in self.set.iter() {
writeln!(f, r#" ProbeSet("{}") {{"#, probe_set.proto)?;
for (delay, endpoint) in probe_set.probes.iter() {
writeln!(f, " {delay:?} to {endpoint},")?;
}
writeln!(f, " }}")?;
}
writeln!(f, "}}")
}
}
impl FromIterator<ProbeSet> for ProbePlan {
fn from_iter<T: IntoIterator<Item = ProbeSet>>(iter: T) -> Self {
Self {
set: iter.into_iter().collect(),
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::net_report::test_utils;
macro_rules! probeset {
(proto: Probe::$kind:ident, relay: $endpoint:expr, delays: $delays:expr,) => {
ProbeSet {
proto: Probe::$kind,
probes: $delays.iter().map(|delay| (*delay, $endpoint)).collect(),
}
};
}
fn default_protocols() -> BTreeSet<Probe> {
BTreeSet::from([Probe::QadIpv4, Probe::QadIpv6, Probe::Https])
}
#[tokio::test]
async fn test_initial_probeplan() {
let (_servers, relay_map) = test_utils::relay_map(2).await;
let relay_1 = &relay_map.relays::<Vec<_>>()[0];
let relay_2 = &relay_map.relays::<Vec<_>>()[1];
let plan = ProbePlan::initial(&relay_map, &default_protocols());
let expected_plan: ProbePlan = [
probeset! {
proto: Probe::Https,
relay: relay_1.clone(),
delays: [
Duration::from_millis(200),
Duration::from_millis(300),
Duration::from_millis(400)
],
},
probeset! {
proto: Probe::Https,
relay: relay_2.clone(),
delays: [
Duration::from_millis(200),
Duration::from_millis(300),
Duration::from_millis(400)
],
},
]
.into_iter()
.collect();
println!("expected:");
println!("{expected_plan}");
println!("actual:");
println!("{plan}");
assert_eq!(plan.to_string(), expected_plan.to_string());
assert_eq!(plan, expected_plan);
}
#[tokio::test]
async fn test_initial_probeplan_some_protocols() {
let (_servers, relay_map) = test_utils::relay_map(2).await;
let relay_1 = &relay_map.relays::<Vec<_>>()[0];
let relay_2 = &relay_map.relays::<Vec<_>>()[1];
let plan = ProbePlan::initial(&relay_map, &BTreeSet::from([Probe::Https]));
let expected_plan: ProbePlan = [
probeset! {
proto: Probe::Https,
relay: relay_1.clone(),
delays: [Duration::from_millis(200),
Duration::from_millis(300),
Duration::from_millis(400)],
},
probeset! {
proto: Probe::Https,
relay: relay_2.clone(),
delays: [Duration::from_millis(200),
Duration::from_millis(300),
Duration::from_millis(400)],
},
]
.into_iter()
.collect();
println!("expected:");
println!("{expected_plan}");
println!("actual:");
println!("{plan}");
assert_eq!(plan.to_string(), expected_plan.to_string());
assert_eq!(plan, expected_plan);
}
}