use std::collections::{BTreeSet, HashSet};
use std::net::SocketAddr;
use crate::family::Family;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CandidateSource {
RelayIntroduction,
Pex,
Dht,
DnsAAAA,
DnsA,
StunReflexive,
ListenAddr,
PriorDial,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Candidate {
pub addr: SocketAddr,
pub family: Family,
pub source: CandidateSource,
}
impl Candidate {
pub fn new(addr: SocketAddr, source: CandidateSource) -> Candidate {
Candidate {
addr,
family: Family::of(&addr),
source,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PeerCandidates {
candidates: Vec<Candidate>,
seen: HashSet<SocketAddr>,
}
impl PeerCandidates {
pub fn new() -> PeerCandidates {
PeerCandidates::default()
}
pub fn add(&mut self, addr: SocketAddr, source: CandidateSource) -> bool {
if !self.seen.insert(addr) {
return false;
}
self.candidates.push(Candidate::new(addr, source));
true
}
pub fn extend<I>(&mut self, addrs: I, source: CandidateSource)
where
I: IntoIterator<Item = SocketAddr>,
{
for addr in addrs {
self.add(addr, source);
}
}
pub fn families(&self) -> BTreeSet<Family> {
self.candidates.iter().map(|c| c.family).collect()
}
pub fn of_family(&self, family: Family) -> impl Iterator<Item = SocketAddr> + '_ {
self.candidates
.iter()
.filter(move |c| c.family == family)
.map(|c| c.addr)
}
pub fn all(&self) -> &[Candidate] {
&self.candidates
}
pub fn is_empty(&self) -> bool {
self.candidates.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sa(s: &str) -> SocketAddr {
s.parse().unwrap()
}
#[test]
fn derives_family_on_add() {
let mut p = PeerCandidates::new();
p.add(sa("[2001:db8::1]:443"), CandidateSource::Dht);
p.add(sa("203.0.113.1:443"), CandidateSource::DnsA);
assert_eq!(p.families(), BTreeSet::from([Family::V6, Family::V4]));
}
#[test]
fn dedups_and_keeps_first_source() {
let mut p = PeerCandidates::new();
assert!(p.add(sa("203.0.113.1:443"), CandidateSource::Pex));
assert!(!p.add(sa("203.0.113.1:443"), CandidateSource::Dht));
assert_eq!(p.all().len(), 1);
assert_eq!(p.all()[0].source, CandidateSource::Pex);
}
#[test]
fn of_family_preserves_discovery_order() {
let mut p = PeerCandidates::new();
p.extend(
[sa("[2001:db8::2]:443"), sa("[2001:db8::1]:443")],
CandidateSource::ListenAddr,
);
let v6: Vec<_> = p.of_family(Family::V6).collect();
assert_eq!(v6, vec![sa("[2001:db8::2]:443"), sa("[2001:db8::1]:443")]);
assert!(p.of_family(Family::V4).next().is_none());
}
#[test]
fn v4_mapped_v6_aggregates_as_v4() {
let mut p = PeerCandidates::new();
p.add(
sa("[::ffff:203.0.113.9]:443"),
CandidateSource::StunReflexive,
);
assert_eq!(p.families(), BTreeSet::from([Family::V4]));
}
}