use dig_dht::{CandidateAddr, ProviderRecord};
use std::net::{IpAddr, SocketAddr};
use thiserror::Error;
pub const MAX_DIAL_CANDIDATES: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum AddrError {
#[error("candidate host {host:?} is not an IPv4/IPv6 literal")]
NotAnIpLiteral {
host: String,
},
}
pub fn candidate_socket(addr: &CandidateAddr) -> Result<SocketAddr, AddrError> {
let ip: IpAddr = addr.host.parse().map_err(|_| AddrError::NotAnIpLiteral {
host: addr.host.clone(),
})?;
Ok(SocketAddr::new(ip, addr.port))
}
pub fn display(addr: &CandidateAddr) -> String {
match candidate_socket(addr) {
Ok(socket) => socket.to_string(),
Err(_) => format!("{}:{}", addr.host, addr.port),
}
}
pub fn dial_candidates(provider: &ProviderRecord) -> Vec<&CandidateAddr> {
let mut candidates: Vec<&CandidateAddr> = provider
.addresses
.iter()
.filter(|a| a.kind.is_dialable())
.collect();
candidates.sort_by_key(|a| match candidate_socket(a) {
Ok(SocketAddr::V6(_)) => 0,
Ok(SocketAddr::V4(_)) => 1,
Err(_) => 2,
});
candidates.truncate(MAX_DIAL_CANDIDATES);
candidates
}
#[cfg(test)]
mod tests {
use super::*;
use dig_dht::{AddressKind, Key};
use dig_nat::PeerId;
fn record(addresses: Vec<CandidateAddr>) -> ProviderRecord {
ProviderRecord::new(
&Key::from_bytes([0xAB; 32]),
&PeerId::from_bytes([1; 32]),
addresses,
u64::MAX,
)
}
#[test]
fn resolves_v4_v6_and_v4_mapped_hosts() {
for host in ["10.0.0.1", "2001:db8::1", "::ffff:10.0.0.1"] {
let addr = CandidateAddr::direct(host, 9444);
let socket = candidate_socket(&addr).expect("literal host must resolve");
assert_eq!(socket.ip(), host.parse::<IpAddr>().unwrap());
assert_eq!(socket.port(), 9444);
}
}
#[test]
fn rejects_a_non_literal_host_with_a_named_reason() {
let err = candidate_socket(&CandidateAddr::direct("peer.example", 9444)).unwrap_err();
assert_eq!(
err,
AddrError::NotAnIpLiteral {
host: "peer.example".into()
}
);
}
#[test]
fn display_brackets_v6_and_leaves_v4_bare() {
assert_eq!(
display(&CandidateAddr::direct("10.0.0.1", 9444)),
"10.0.0.1:9444"
);
assert_eq!(
display(&CandidateAddr::direct("::ffff:10.0.0.1", 9444)),
"[::ffff:10.0.0.1]:9444"
);
assert!(display(&CandidateAddr::direct("2001:db8::1", 9444))
.parse::<SocketAddr>()
.is_ok());
}
#[test]
fn dial_order_is_v6_then_v4_then_unresolvable() {
let p = record(vec![
CandidateAddr::direct("10.0.0.1", 1),
CandidateAddr::direct("peer.example", 2),
CandidateAddr::direct("2001:db8::1", 3),
]);
let hosts: Vec<&str> = dial_candidates(&p)
.iter()
.map(|a| a.host.as_str())
.collect();
assert_eq!(hosts, vec!["2001:db8::1", "10.0.0.1", "peer.example"]);
}
#[test]
fn dial_candidates_skip_relay_markers_and_stay_bounded() {
let mut addresses = vec![CandidateAddr::relay_marker()];
addresses.extend((0..10).map(|i| CandidateAddr::direct(format!("10.0.0.{i}"), 9444)));
let p = record(addresses);
let candidates = dial_candidates(&p);
assert_eq!(candidates.len(), MAX_DIAL_CANDIDATES);
assert!(candidates.iter().all(|a| a.kind == AddressKind::Direct));
}
}