use std::net::SocketAddr;
use crate::candidate::PeerCandidates;
use crate::family::Family;
use crate::local::LocalStack;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoCommonFamily {
pub local: Vec<Family>,
pub peer: Vec<Family>,
}
impl std::fmt::Display for NoCommonFamily {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"no common address family: local has {:?}, peer offers {:?}",
self.local, self.peer
)
}
}
impl std::error::Error for NoCommonFamily {}
pub fn dial_order(
local: &LocalStack,
peer: &PeerCandidates,
) -> Result<Vec<SocketAddr>, NoCommonFamily> {
let peer_families = peer.families();
let mut ordered = Vec::new();
for family in local.families() {
if peer_families.contains(&family) {
ordered.extend(peer.of_family(family));
}
}
if ordered.is_empty() {
return Err(NoCommonFamily {
local: local.families(),
peer: peer_families.into_iter().collect(),
});
}
Ok(ordered)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::candidate::CandidateSource;
fn sa(s: &str) -> SocketAddr {
s.parse().unwrap()
}
fn peer_with(v6: bool, v4: bool) -> PeerCandidates {
let mut p = PeerCandidates::new();
if v6 {
p.add(sa("[2001:db8::1]:443"), CandidateSource::Dht);
}
if v4 {
p.add(sa("203.0.113.1:443"), CandidateSource::DnsA);
}
p
}
#[test]
fn disjoint_families_report_no_common_family() {
let local = LocalStack::from_flags(false, true);
let peer = peer_with(true, false);
let err = dial_order(&local, &peer).unwrap_err();
assert_eq!(err.local, vec![Family::V4]);
assert_eq!(err.peer, vec![Family::V6]);
}
#[test]
fn dual_stack_prefers_ipv6() {
let local = LocalStack::from_flags(true, true);
let peer = peer_with(true, true);
let order = dial_order(&local, &peer).unwrap();
assert_eq!(order, vec![sa("[2001:db8::1]:443"), sa("203.0.113.1:443")]);
}
#[test]
fn never_dials_a_family_the_peer_lacks() {
let local = LocalStack::from_flags(true, true);
let peer = peer_with(false, true);
let order = dial_order(&local, &peer).unwrap();
assert_eq!(order, vec![sa("203.0.113.1:443")]);
assert!(order.iter().all(|a| Family::of(a) == Family::V4));
}
#[test]
fn never_dials_a_family_the_local_host_lacks() {
let local = LocalStack::from_flags(false, true);
let peer = peer_with(true, true);
let order = dial_order(&local, &peer).unwrap();
assert_eq!(order, vec![sa("203.0.113.1:443")]);
assert!(order.iter().all(|a| Family::of(a) == Family::V4));
}
#[test]
fn v6_only_local_and_dual_peer_yields_only_v6() {
let local = LocalStack::from_flags(true, false);
let peer = peer_with(true, true);
let order = dial_order(&local, &peer).unwrap();
assert_eq!(order, vec![sa("[2001:db8::1]:443")]);
}
#[test]
fn empty_peer_is_no_common_family() {
let local = LocalStack::from_flags(true, true);
let err = dial_order(&local, &PeerCandidates::new()).unwrap_err();
assert!(err.peer.is_empty());
}
#[test]
fn multiple_addresses_per_family_keep_discovery_order() {
let local = LocalStack::from_flags(true, true);
let mut peer = PeerCandidates::new();
peer.add(sa("[2001:db8::2]:443"), CandidateSource::ListenAddr);
peer.add(sa("198.51.100.7:443"), CandidateSource::Pex);
peer.add(sa("[2001:db8::1]:443"), CandidateSource::Dht);
let order = dial_order(&local, &peer).unwrap();
assert_eq!(
order,
vec![
sa("[2001:db8::2]:443"),
sa("[2001:db8::1]:443"),
sa("198.51.100.7:443"),
]
);
}
}