use std::collections::HashSet;
use std::net::SocketAddr;
use rand::seq::SliceRandom;
pub fn order_candidates(candidates: &[SocketAddr]) -> Vec<SocketAddr> {
let (v6, v4): (Vec<SocketAddr>, Vec<SocketAddr>) =
candidates.iter().partition(|addr| addr.is_ipv6());
let mut ordered = Vec::with_capacity(candidates.len());
ordered.extend(v6);
ordered.extend(v4);
ordered
}
pub fn candidate_order(discovered: &[SocketAddr]) -> Vec<SocketAddr> {
let mut seen = HashSet::with_capacity(discovered.len());
let mut distinct: Vec<SocketAddr> = discovered
.iter()
.copied()
.filter(|addr| seen.insert(*addr))
.collect();
distinct.shuffle(&mut rand::thread_rng());
order_candidates(&distinct)
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
fn v4(last: u8) -> SocketAddr {
SocketAddr::new(Ipv4Addr::new(203, 0, 113, last).into(), 8444)
}
fn v6(seg: u16) -> SocketAddr {
SocketAddr::new(
Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, seg).into(),
8444,
)
}
#[test]
fn every_ipv6_candidate_is_dialled_before_any_ipv4_one() {
let mixed = vec![v4(1), v6(1), v4(2), v6(2), v4(3)];
let ordered = candidate_order(&mixed);
let first_v4 = ordered
.iter()
.position(|a| a.is_ipv4())
.expect("the fixture holds IPv4 candidates");
let last_v6 = ordered
.iter()
.rposition(|a| a.is_ipv6())
.expect("the fixture holds IPv6 candidates");
assert!(
last_v6 < first_v4,
"IPv6 must be preferred over IPv4 (§5.2): {ordered:?}"
);
}
#[test]
fn a_duplicate_address_is_removed_even_when_the_duplicates_are_not_adjacent() {
const ROUNDS: usize = 32;
let with_repeats = vec![v6(1), v4(1), v6(2), v6(1), v4(2), v4(1), v6(3), v6(1)];
for round in 0..ROUNDS {
let ordered = candidate_order(&with_repeats);
let distinct: HashSet<SocketAddr> = ordered.iter().copied().collect();
assert_eq!(
ordered.len(),
distinct.len(),
"round {round}: a candidate must be offered at most once, however the duplicates were spread: {ordered:?}"
);
assert_eq!(
distinct.len(),
5,
"round {round}: the five distinct addresses must all survive: {ordered:?}"
);
}
}
#[test]
fn distinct_candidates_all_survive_ordering() {
let all_distinct = vec![v6(1), v6(2), v4(1), v4(2)];
let ordered = candidate_order(&all_distinct);
assert_eq!(ordered.len(), 4);
assert_eq!(
ordered.iter().copied().collect::<HashSet<_>>(),
all_distinct.iter().copied().collect::<HashSet<_>>()
);
}
#[test]
fn a_local_address_is_not_preferred_over_a_public_one_of_the_same_family() {
let v6_lo = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 8444);
let v4_lo = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 8444);
assert_eq!(
order_candidates(&[v6(1), v6_lo, v4(1), v4_lo]),
vec![v6(1), v6_lo, v4(1), v4_lo],
"family orders the candidates; locality must not reorder them"
);
}
#[test]
fn ipv6_precedes_ipv4_for_loopback_too() {
let v6_lo = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 8444);
let v4_lo = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 8444);
assert_eq!(order_candidates(&[v4_lo, v6_lo]), vec![v6_lo, v4_lo]);
}
}