1use std::net::SocketAddr;
11
12use crate::candidate::PeerCandidates;
13use crate::family::Family;
14use crate::local::LocalStack;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct NoCommonFamily {
22 pub local: Vec<Family>,
24 pub peer: Vec<Family>,
26}
27
28impl std::fmt::Display for NoCommonFamily {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(
31 f,
32 "no common address family: local has {:?}, peer offers {:?}",
33 self.local, self.peer
34 )
35 }
36}
37
38impl std::error::Error for NoCommonFamily {}
39
40pub fn dial_order(
52 local: &LocalStack,
53 peer: &PeerCandidates,
54) -> Result<Vec<SocketAddr>, NoCommonFamily> {
55 let peer_families = peer.families();
56 let mut ordered = Vec::new();
57 for family in local.families() {
58 if peer_families.contains(&family) {
59 ordered.extend(peer.of_family(family));
60 }
61 }
62 if ordered.is_empty() {
63 return Err(NoCommonFamily {
64 local: local.families(),
65 peer: peer_families.into_iter().collect(),
66 });
67 }
68 Ok(ordered)
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::candidate::CandidateSource;
75
76 fn sa(s: &str) -> SocketAddr {
77 s.parse().unwrap()
78 }
79
80 fn peer_with(v6: bool, v4: bool) -> PeerCandidates {
82 let mut p = PeerCandidates::new();
83 if v6 {
84 p.add(sa("[2001:db8::1]:443"), CandidateSource::Dht);
85 }
86 if v4 {
87 p.add(sa("203.0.113.1:443"), CandidateSource::DnsA);
88 }
89 p
90 }
91
92 #[test]
94 fn disjoint_families_report_no_common_family() {
95 let local = LocalStack::from_flags(false, true);
96 let peer = peer_with(true, false);
97 let err = dial_order(&local, &peer).unwrap_err();
98 assert_eq!(err.local, vec![Family::V4]);
99 assert_eq!(err.peer, vec![Family::V6]);
100 }
101
102 #[test]
104 fn dual_stack_prefers_ipv6() {
105 let local = LocalStack::from_flags(true, true);
106 let peer = peer_with(true, true);
107 let order = dial_order(&local, &peer).unwrap();
108 assert_eq!(order, vec![sa("[2001:db8::1]:443"), sa("203.0.113.1:443")]);
109 }
110
111 #[test]
113 fn never_dials_a_family_the_peer_lacks() {
114 let local = LocalStack::from_flags(true, true);
115 let peer = peer_with(false, true);
116 let order = dial_order(&local, &peer).unwrap();
117 assert_eq!(order, vec![sa("203.0.113.1:443")]);
118 assert!(order.iter().all(|a| Family::of(a) == Family::V4));
119 }
120
121 #[test]
123 fn never_dials_a_family_the_local_host_lacks() {
124 let local = LocalStack::from_flags(false, true);
125 let peer = peer_with(true, true);
126 let order = dial_order(&local, &peer).unwrap();
127 assert_eq!(order, vec![sa("203.0.113.1:443")]);
128 assert!(order.iter().all(|a| Family::of(a) == Family::V4));
129 }
130
131 #[test]
132 fn v6_only_local_and_dual_peer_yields_only_v6() {
133 let local = LocalStack::from_flags(true, false);
134 let peer = peer_with(true, true);
135 let order = dial_order(&local, &peer).unwrap();
136 assert_eq!(order, vec![sa("[2001:db8::1]:443")]);
137 }
138
139 #[test]
140 fn empty_peer_is_no_common_family() {
141 let local = LocalStack::from_flags(true, true);
142 let err = dial_order(&local, &PeerCandidates::new()).unwrap_err();
143 assert!(err.peer.is_empty());
144 }
145
146 #[test]
147 fn multiple_addresses_per_family_keep_discovery_order() {
148 let local = LocalStack::from_flags(true, true);
149 let mut peer = PeerCandidates::new();
150 peer.add(sa("[2001:db8::2]:443"), CandidateSource::ListenAddr);
151 peer.add(sa("198.51.100.7:443"), CandidateSource::Pex);
152 peer.add(sa("[2001:db8::1]:443"), CandidateSource::Dht);
153 let order = dial_order(&local, &peer).unwrap();
154 assert_eq!(
155 order,
156 vec![
157 sa("[2001:db8::2]:443"),
158 sa("[2001:db8::1]:443"),
159 sa("198.51.100.7:443"),
160 ]
161 );
162 }
163}