sipx_media/ice/candidate.rs
1//! Candidates as the agent holds them: priority, foundation and base
2//! (RFC 8445 §5.1.1.3, §5.1.2.1, §7.1.1; [spec] §4, §5).
3//!
4//! [`sipx_sdp::ice::Candidate`] is the `a=candidate` line. It is what crosses the wire and it
5//! knows nothing about which socket a check would leave from, because `sipx-sdp` owns no sockets.
6//! The two types here add exactly that: a [`LocalCandidate`] carries the [`LocalBase`] the driver
7//! bound, and a [`RemoteCandidate`] carries a foundation that a peer-reflexive candidate can also
8//! have — §7.3.1.3 gives one "an arbitrary value, different from the foundations of all other
9//! remote candidates", which is not a value any `a=candidate` line ever supplied.
10//!
11//! [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
12
13use std::net::{IpAddr, SocketAddr};
14
15use sipx_sdp::ice::{Candidate, CandidateType, ComponentId, Foundation, Priority, Transport};
16
17/// Type preference for a host candidate (§5.1.2.2's recommended value; [spec] §4).
18///
19/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
20pub const HOST_PREFERENCE: u8 = 126;
21/// Type preference for a peer-reflexive candidate. §5.1.2.1 makes it a MUST that this is higher
22/// than the server-reflexive one, and it is the preference every `PRIORITY` attribute uses
23/// whatever the candidate actually is (§7.1.1, [`check_priority`]).
24///
25/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
26pub const PEER_REFLEXIVE_PREFERENCE: u8 = 110;
27/// Type preference for a server-reflexive candidate (§5.1.2.2's recommended value).
28pub const SERVER_REFLEXIVE_PREFERENCE: u8 = 100;
29/// Type preference for a relayed candidate: last resort, because it costs a relay's bandwidth.
30pub const RELAYED_PREFERENCE: u8 = 0;
31
32/// The largest type preference §5.1.2.1 admits: "an integer from 0 … to 126 … inclusive".
33pub const MAX_TYPE_PREFERENCE: u8 = 126;
34
35/// The local preference for a candidate that is the only one of its type for its component.
36/// §5.1.2.1: "When there is only a single IP address, this value SHOULD be set to 65535."
37pub const SINGLE_ADDRESS_PREFERENCE: u16 = 65535;
38
39/// Which socket the driver bound, named so the agent never has to hold one.
40///
41/// [spec] §2: "`LocalBase` is an index into the sockets the driver bound, not a socket. The agent
42/// never learns what a socket is; it says 'the one you called base 0' and the driver knows which."
43///
44/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct LocalBase(pub u16);
47
48/// A local candidate's identity.
49///
50/// A handle and not a copy of the candidate: §7.2.5.3.1 adds peer-reflexive candidates while
51/// pairs are live, and a pair that had copied its candidate would still be holding the priority
52/// it had before the role switched.
53///
54/// An allocated identity and not a position, for the same reason [`PairId`](crate::ice::checklist::PairId)
55/// is: the tables these name are not append-only. A second offer replaces the remote candidates,
56/// §6.1.2.5's limit discards pairs, and §7.3.1.3 learns candidates that may later be forgotten —
57/// after any of which a stored position names a candidate the pair was never formed for, which is
58/// a check sent to an address the peer never offered.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub struct LocalId(pub usize);
61
62/// A remote candidate's identity, allocated and stable — see [`LocalId`].
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct RemoteId(pub usize);
65
66/// Allocates the identities of [`LocalCandidate`]s and [`RemoteCandidate`]s.
67///
68/// One counter each, never reused, so a stale handle resolves to nothing rather than to whatever
69/// took the position.
70#[derive(Debug, Default)]
71pub struct CandidateIds {
72 local: usize,
73 remote: usize,
74}
75
76impl CandidateIds {
77 /// The next local identity.
78 pub fn local(&mut self) -> LocalId {
79 let id = LocalId(self.local);
80 self.local = self.local.saturating_add(1);
81 id
82 }
83
84 /// The next remote identity.
85 pub fn remote(&mut self) -> RemoteId {
86 let id = RemoteId(self.remote);
87 self.remote = self.remote.saturating_add(1);
88 id
89 }
90}
91
92/// The local candidate with this identity, if it is still known.
93#[must_use]
94pub fn find_local(candidates: &[LocalCandidate], id: LocalId) -> Option<&LocalCandidate> {
95 candidates.iter().find(|candidate| candidate.id == id)
96}
97
98/// The remote candidate with this identity, if it is still known.
99#[must_use]
100pub fn find_remote(candidates: &[RemoteCandidate], id: RemoteId) -> Option<&RemoteCandidate> {
101 candidates.iter().find(|candidate| candidate.id == id)
102}
103
104/// A local candidate's foundation (§5.1.1.3).
105///
106/// A decimal counter over the distinct tuples §5.1.1.3 defines, allocated in the order candidates
107/// are gathered, because the value itself is arbitrary — RFC 8839 §5.1 wants `1*32ice-char` and
108/// RFC 8445 gives the value meaning only by equality. A hash would satisfy the same grammar and
109/// be longer on the wire for no gain.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct LocalFoundation(pub u32);
112
113/// A remote candidate's foundation.
114///
115/// Two variants because a remote candidate has two provenances, and §7.3.1.3's is not expressible
116/// as an `a=candidate` foundation: a peer-reflexive remote candidate is learned from a check, not
117/// signalled, and its foundation is required to be "an arbitrary value, different from the
118/// foundations of all other remote candidates". Making that a separate variant is what stops a
119/// learned foundation from ever comparing equal to a signalled one by accident.
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
121pub enum RemoteFoundation {
122 /// The foundation the peer put on its `a=candidate` line (RFC 8839 §5.1).
123 Signalled(Foundation),
124 /// A counter allocated for a peer-reflexive remote candidate (§7.3.1.3).
125 Learned(u32),
126}
127
128/// A candidate pair's foundation: §6.1.2.6's "combination of the foundations of the local and
129/// remote candidates in the pair".
130///
131/// It exists only to be compared. §6.1.2.6 unfreezes exactly one pair per foundation and
132/// §7.2.5.3.3 unfreezes every pair sharing the foundation of one that just succeeded, so a wrong
133/// answer here makes ICE either check far too much or check nothing at all.
134#[derive(Debug, Clone, PartialEq, Eq, Hash)]
135pub struct PairFoundation {
136 /// The local candidate's foundation.
137 pub local: LocalFoundation,
138 /// The remote candidate's.
139 pub remote: RemoteFoundation,
140}
141
142/// A candidate the driver gathered, before the agent has priced it.
143///
144/// The agent assigns the foundation and the priority rather than taking them, because both are
145/// properties of the *set* of candidates: §5.1.1.3's foundation is a counter over distinct
146/// tuples, and §5.1.2.1's local preference "MUST be unique for each" candidate of a type, which
147/// is not a fact any single candidate knows about itself.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct Gathered {
150 /// The socket this candidate was gathered on, and which a check would leave from.
151 pub base: LocalBase,
152 /// That socket's own address — the candidate's base in §5.1.1.1's sense.
153 pub base_address: SocketAddr,
154 /// The candidate's transport address. Equal to `base_address` for a host candidate; the
155 /// address a STUN or TURN server reported otherwise.
156 pub address: SocketAddr,
157 /// How it was obtained.
158 pub kind: CandidateType,
159 /// Which component of the stream it is for.
160 pub component: ComponentId,
161 /// The IP address of the STUN or TURN server it was obtained from, for reflexive and relayed
162 /// candidates. Part of the foundation (§5.1.1.3) and `None` for a host candidate.
163 pub server: Option<IpAddr>,
164}
165
166/// A local candidate: what the driver gathered, plus what §5.1.1.3 and §5.1.2.1 make of it.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct LocalCandidate {
169 /// Its identity, stable for the life of the agent.
170 pub id: LocalId,
171 /// The gathered address and its base.
172 pub gathered: Gathered,
173 /// Its foundation (§5.1.1.3).
174 pub foundation: LocalFoundation,
175 /// Its local preference (§5.1.2.1), unique among candidates of the same type and component.
176 pub local_preference: u16,
177 /// Its priority (§5.1.2.1).
178 pub priority: Priority,
179}
180
181impl LocalCandidate {
182 /// The `PRIORITY` a connectivity check from this candidate carries (§7.1.1).
183 ///
184 /// Not [`LocalCandidate::priority`], and that is the whole point — see [`check_priority`].
185 #[must_use]
186 pub fn check_priority(&self) -> Priority {
187 check_priority(self.local_preference, self.gathered.component)
188 }
189}
190
191/// A remote candidate: one the peer signalled, or one §7.3.1.3 learned from a check.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct RemoteCandidate {
194 /// Its identity, stable for the life of the agent.
195 pub id: RemoteId,
196 /// Where to send a check.
197 pub address: SocketAddr,
198 /// How the peer obtained it, or [`CandidateType::PeerReflexive`] when it was learned here.
199 pub kind: CandidateType,
200 /// Which component it is for.
201 pub component: ComponentId,
202 /// Its foundation.
203 pub foundation: RemoteFoundation,
204 /// Its priority, as the peer computed it. Range-checked to RFC 8839 §5.1's `1..=2^31−1` by
205 /// [`Priority`] itself — which is what keeps §6.1.2.3's pair-priority arithmetic inside a
206 /// `u64`, so nothing here re-checks or widens it.
207 pub priority: Priority,
208}
209
210impl RemoteCandidate {
211 /// The remote candidate an `a=candidate` line describes.
212 ///
213 /// `None` when the line names a transport sipx does not check over: RFC 8839 §5.1's grammar
214 /// admits a `transport-extension`, and [spec] §3 says such a line is accepted and discarded
215 /// rather than failing the description a usable candidate arrived in.
216 ///
217 /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
218 #[must_use]
219 pub fn signalled(id: RemoteId, candidate: &Candidate) -> Option<Self> {
220 if candidate.transport != Transport::Udp {
221 return None;
222 }
223 Some(Self {
224 id,
225 address: SocketAddr::new(candidate.address, candidate.port),
226 kind: candidate.kind,
227 component: candidate.component,
228 foundation: RemoteFoundation::Signalled(candidate.foundation.clone()),
229 priority: candidate.priority,
230 })
231 }
232}
233
234/// §5.1.2.2's recommended type preference for a candidate of this type ([spec] §4's table).
235///
236/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
237#[must_use]
238pub const fn type_preference(kind: CandidateType) -> u8 {
239 match kind {
240 CandidateType::Host => HOST_PREFERENCE,
241 CandidateType::PeerReflexive => PEER_REFLEXIVE_PREFERENCE,
242 CandidateType::ServerReflexive => SERVER_REFLEXIVE_PREFERENCE,
243 CandidateType::Relayed => RELAYED_PREFERENCE,
244 }
245}
246
247/// §5.1.2.1's formula, exactly as it is printed:
248///
249/// ```text
250/// priority = (2^24)*(type preference) +
251/// (2^8)*(local preference) +
252/// (2^0)*(256 - component ID)
253/// ```
254///
255/// The ordering this produces is the only thing that makes two independent implementations agree
256/// on which pair wins, so it is written once and every caller goes through it.
257///
258/// `type_preference` is held to §5.1.2.1's `0..=126`; a larger value would put the result past
259/// 2^31 − 1, which [`Priority`] does not hold. The result is clamped up to [`Priority::MIN`] for
260/// the one input that yields zero — a relayed candidate (preference 0) that is also the 65536th
261/// of its type (local preference 0) for component 256 — because §5.1.2 requires a priority to be
262/// "a positive integer".
263#[must_use]
264pub fn priority(type_preference: u8, local_preference: u16, component: ComponentId) -> Priority {
265 let type_preference = u32::from(type_preference.min(MAX_TYPE_PREFERENCE));
266 let raw = (type_preference << 24)
267 + (u32::from(local_preference) << 8)
268 + (256u32.saturating_sub(u32::from(component.get())));
269 Priority::new(raw).unwrap_or(Priority::MIN)
270}
271
272/// The `PRIORITY` a connectivity check carries (§7.1.1).
273///
274/// The same formula, "but with the candidate type preference of peer-reflexive candidates" —
275/// 110, whatever the candidate sending the check actually is.
276///
277/// It has to be. That is the priority the *peer* will assign to the peer-reflexive candidate it
278/// may learn from this very check (§7.3.1.3 takes it straight out of the attribute), and the two
279/// ends have to agree on it. Send the candidate's own priority here and the far end prioritises
280/// the candidate it learned from us differently from the way we prioritise it, and the two
281/// checklists diverge — which shows up as ICE picking different pairs at the two ends, not as
282/// anything that looks like a bug in this function.
283#[must_use]
284pub fn check_priority(local_preference: u16, component: ComponentId) -> Priority {
285 priority(PEER_REFLEXIVE_PREFERENCE, local_preference, component)
286}
287
288/// §6.1.2.3's pair priority, with `controlling` the priority of the controlling agent's candidate
289/// and `controlled` the controlled agent's:
290///
291/// ```text
292/// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
293/// ```
294///
295/// It fits in a `u64` because [`Priority`] is bounded at 2^31 − 1: the largest value two in-range
296/// priorities produce is 2^63 − 2, at `G = D = 2^31 − 1`, where the `G>D` term is zero. That bound
297/// is [spec] §6.2's, and the range check that supplies it lives in `sipx-sdp`, on parse — an
298/// unchecked ten-digit priority from a peer overflows this expression and silently reorders the
299/// checklist that computing it exists to order.
300///
301/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
302#[must_use]
303pub fn pair_priority(controlling: Priority, controlled: Priority) -> u64 {
304 let g = u64::from(controlling.get());
305 let d = u64::from(controlled.get());
306 (1u64 << 32) * g.min(d) + 2 * g.max(d) + u64::from(g > d)
307}
308
309/// The distinct tuple §5.1.1.3 makes a foundation out of.
310#[derive(Debug, Clone, PartialEq, Eq)]
311struct FoundationKey {
312 kind: CandidateType,
313 /// "Their bases have the same IP address (the ports can be different)" — so the port is not
314 /// in the key, and leaving it in would give every socket its own foundation and unfreeze
315 /// every pair at once.
316 base_ip: IpAddr,
317 /// "For reflexive and relayed candidates, the STUN or TURN servers used to obtain them have
318 /// the same IP address."
319 server: Option<IpAddr>,
320 /// "They were obtained using the same transport protocol." One value today; in the key
321 /// because §5.1.1.3 puts it there and a second transport would otherwise share foundations
322 /// with the first.
323 transport: Transport,
324}
325
326/// Allocates foundations over the candidates as they are gathered (§5.1.1.3).
327#[derive(Debug, Default)]
328pub struct Foundations {
329 keys: Vec<FoundationKey>,
330 learned: u32,
331}
332
333impl Foundations {
334 /// The foundation for a gathered candidate: the counter already allocated to its tuple, or
335 /// the next one.
336 pub fn assign(&mut self, candidate: &Gathered, transport: Transport) -> LocalFoundation {
337 let key = FoundationKey {
338 kind: candidate.kind,
339 base_ip: candidate.base_address.ip(),
340 server: candidate.server,
341 transport,
342 };
343 let existing = self
344 .keys
345 .iter()
346 .position(|known| *known == key)
347 .unwrap_or_else(|| {
348 self.keys.push(key);
349 self.keys.len().saturating_sub(1)
350 });
351 LocalFoundation(
352 u32::try_from(existing)
353 .unwrap_or(u32::MAX)
354 .saturating_add(1),
355 )
356 }
357
358 /// The next foundation for a peer-reflexive *remote* candidate (§7.3.1.3): "an arbitrary
359 /// value, different from the foundations of all other remote candidates".
360 pub fn learn_remote(&mut self) -> RemoteFoundation {
361 self.learned = self.learned.saturating_add(1);
362 RemoteFoundation::Learned(self.learned)
363 }
364}
365
366/// Assign §5.1.2.1's local preferences across a gathered set, and price every candidate.
367///
368/// 65535 when a candidate is the only one of its type for its component; otherwise 65535, 65534,
369/// … descending over the candidates **sorted by address bytes**, which is the whole reason this
370/// is a function over the set rather than a field on a candidate. §5.1.2.1 requires the value to
371/// be unique per type and component; ordering by whatever the OS enumerated first would make the
372/// same host produce different priorities on different runs, and the priorities are what the far
373/// end reasons about.
374pub fn assign_local_preferences(candidates: &mut [LocalCandidate]) {
375 let mut ordered: Vec<usize> = (0..candidates.len()).collect();
376 ordered.sort_by_key(|index| {
377 candidates.get(*index).map(|candidate| {
378 (
379 type_preference(candidate.gathered.kind),
380 candidate.gathered.component.get(),
381 candidate.gathered.address.ip(),
382 candidate.gathered.address.port(),
383 )
384 })
385 });
386
387 let mut previous: Option<(u8, u16)> = None;
388 let mut preference = SINGLE_ADDRESS_PREFERENCE;
389 for index in ordered {
390 let Some(candidate) = candidates.get_mut(index) else {
391 continue;
392 };
393 let group = (
394 type_preference(candidate.gathered.kind),
395 candidate.gathered.component.get(),
396 );
397 if previous == Some(group) {
398 preference = preference.saturating_sub(1);
399 } else {
400 preference = SINGLE_ADDRESS_PREFERENCE;
401 previous = Some(group);
402 }
403 candidate.local_preference = preference;
404 candidate.priority = priority(
405 type_preference(candidate.gathered.kind),
406 preference,
407 candidate.gathered.component,
408 );
409 }
410}
411
412#[cfg(test)]
413#[allow(
414 clippy::unwrap_used,
415 clippy::expect_used,
416 clippy::panic,
417 clippy::indexing_slicing
418)]
419mod tests {
420 use super::*;
421
422 fn component(id: u16) -> ComponentId {
423 ComponentId::new(id).unwrap()
424 }
425
426 /// [spec] §4's worked vector, three candidates and three stated integers — asserted against
427 /// the numbers, not against the formula re-typed into the test. The third is the one RFC 8839
428 /// §5.1 prints in its own example line.
429 ///
430 /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
431 #[test]
432 fn the_priority_formula_reproduces_the_specs_three_row_table() {
433 assert_eq!(
434 priority(HOST_PREFERENCE, SINGLE_ADDRESS_PREFERENCE, component(1)).get(),
435 2_130_706_431
436 );
437 assert_eq!(
438 priority(HOST_PREFERENCE, SINGLE_ADDRESS_PREFERENCE, component(2)).get(),
439 2_130_706_430
440 );
441 assert_eq!(
442 priority(
443 SERVER_REFLEXIVE_PREFERENCE,
444 SINGLE_ADDRESS_PREFERENCE,
445 component(1)
446 )
447 .get(),
448 1_694_498_815
449 );
450 }
451
452 /// RFC 8839 §5.1's own example line carries the third row's number, and the candidate that
453 /// line describes is a server-reflexive RTP candidate with a single address.
454 #[test]
455 fn rfc8839s_example_line_carries_the_priority_this_formula_computes() {
456 let line = "2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr 203.0.113.141 rport 8998";
457 let candidate = Candidate::parse(line).unwrap();
458 assert_eq!(
459 candidate.priority,
460 priority(
461 SERVER_REFLEXIVE_PREFERENCE,
462 SINGLE_ADDRESS_PREFERENCE,
463 candidate.component
464 )
465 );
466 }
467
468 /// §5.1.2.1: the peer-reflexive preference MUST be higher than the server-reflexive one,
469 /// which is the only ordering constraint the RFC puts on the four values.
470 #[test]
471 fn the_type_preferences_are_ordered_the_way_the_rfc_requires() {
472 let preferences: Vec<u8> = [
473 CandidateType::Host,
474 CandidateType::PeerReflexive,
475 CandidateType::ServerReflexive,
476 CandidateType::Relayed,
477 ]
478 .into_iter()
479 .map(type_preference)
480 .collect();
481 let mut descending = preferences.clone();
482 descending.sort_unstable_by(|left, right| right.cmp(left));
483 descending.dedup();
484 assert_eq!(preferences, descending);
485 assert_eq!(type_preference(CandidateType::Host), HOST_PREFERENCE);
486 assert_eq!(
487 type_preference(CandidateType::PeerReflexive),
488 PEER_REFLEXIVE_PREFERENCE
489 );
490 }
491
492 /// §7.1.1: a check's `PRIORITY` is computed with the peer-reflexive type preference whatever
493 /// the candidate is. A host candidate's own priority is 2130706431; the check it sends says
494 /// 1862270975.
495 #[test]
496 fn a_check_carries_the_peer_reflexive_priority_and_not_the_candidates_own() {
497 let host = priority(HOST_PREFERENCE, SINGLE_ADDRESS_PREFERENCE, component(1));
498 let check = check_priority(SINGLE_ADDRESS_PREFERENCE, component(1));
499 assert_eq!(host.get(), 2_130_706_431);
500 assert_eq!(check.get(), 1_862_270_975);
501 assert_ne!(host, check);
502 assert_eq!(
503 check,
504 priority(
505 PEER_REFLEXIVE_PREFERENCE,
506 SINGLE_ADDRESS_PREFERENCE,
507 component(1)
508 )
509 );
510 }
511
512 /// A relayed candidate sends a check that claims 110, not 0 — the case where getting §7.1.1
513 /// wrong is most visible, because the two priorities are furthest apart.
514 #[test]
515 fn even_a_relayed_candidate_claims_the_peer_reflexive_preference_in_a_check() {
516 let relayed = priority(RELAYED_PREFERENCE, SINGLE_ADDRESS_PREFERENCE, component(1));
517 assert_eq!(relayed.get(), 16_777_215);
518 assert_eq!(
519 check_priority(SINGLE_ADDRESS_PREFERENCE, component(1)).get(),
520 1_862_270_975
521 );
522 }
523
524 /// [spec] §6.2's arithmetic: the largest pair priority two in-range priorities produce is
525 /// 2^63 − 2, at `G = D`, where the `G>D` term is zero.
526 ///
527 /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
528 #[test]
529 fn the_pair_priority_is_the_rfcs_expression_and_stays_inside_a_u64() {
530 let max = Priority::MAX;
531 assert_eq!(pair_priority(max, max), (1u64 << 63) - 2);
532
533 let g = Priority::new(2_130_706_431).unwrap();
534 let d = Priority::new(1_694_498_815).unwrap();
535 let expected = (1u64 << 32) * u64::from(d.get()) + 2 * u64::from(g.get()) + 1;
536 assert_eq!(pair_priority(g, d), expected);
537 // The last term is the tie-break, and it is asymmetric on purpose: the same two
538 // candidates in the same roles must give both ends the same number.
539 assert_eq!(pair_priority(d, g), expected - 1);
540 }
541
542 #[test]
543 fn foundations_are_equal_exactly_when_section_5_1_1_3_says_they_are() {
544 let mut foundations = Foundations::default();
545 let host = |ip: &str, port: u16| Gathered {
546 base: LocalBase(0),
547 base_address: SocketAddr::new(ip.parse().unwrap(), port),
548 address: SocketAddr::new(ip.parse().unwrap(), port),
549 kind: CandidateType::Host,
550 component: component(1),
551 server: None,
552 };
553
554 let media = foundations.assign(&host("192.0.2.1", 5000), Transport::Udp);
555 // Same base IP, different port: "the ports can be different".
556 let control = foundations.assign(&host("192.0.2.1", 5001), Transport::Udp);
557 assert_eq!(media, control);
558
559 // A different base IP is a different foundation.
560 let elsewhere = foundations.assign(&host("192.0.2.2", 5000), Transport::Udp);
561 assert_ne!(media, elsewhere);
562
563 // Same base, different type: different foundation.
564 let mut reflexive = host("192.0.2.1", 5000);
565 reflexive.kind = CandidateType::ServerReflexive;
566 reflexive.server = Some("198.51.100.1".parse().unwrap());
567 let srflx = foundations.assign(&reflexive, Transport::Udp);
568 assert_ne!(media, srflx);
569
570 // Same type and base, a different STUN server: different foundation.
571 let mut second_server = reflexive;
572 second_server.server = Some("198.51.100.2".parse().unwrap());
573 assert_ne!(srflx, foundations.assign(&second_server, Transport::Udp));
574
575 // And the same tuple twice is the same foundation, which is the property §6.1.2.6 uses.
576 assert_eq!(srflx, foundations.assign(&reflexive, Transport::Udp));
577 }
578
579 /// §7.3.1.3's foundation for a learned remote candidate is "different from the foundations of
580 /// all other remote candidates" — including every foundation a peer could have signalled.
581 #[test]
582 fn a_learned_remote_foundation_collides_with_nothing() {
583 let mut foundations = Foundations::default();
584 let first = foundations.learn_remote();
585 let second = foundations.learn_remote();
586 assert_ne!(first, second);
587 assert_ne!(
588 first,
589 RemoteFoundation::Signalled(Foundation::new("1").unwrap())
590 );
591 }
592
593 #[test]
594 fn local_preferences_descend_over_addresses_sorted_by_bytes() {
595 let gathered = |ip: &str| Gathered {
596 base: LocalBase(0),
597 base_address: SocketAddr::new(ip.parse().unwrap(), 5000),
598 address: SocketAddr::new(ip.parse().unwrap(), 5000),
599 kind: CandidateType::Host,
600 component: component(1),
601 server: None,
602 };
603 let candidate = |ip: &str| LocalCandidate {
604 id: LocalId(0),
605 gathered: gathered(ip),
606 foundation: LocalFoundation(1),
607 local_preference: 0,
608 priority: Priority::MIN,
609 };
610
611 // Handed over in an order no interface enumeration would guarantee.
612 let mut candidates = vec![
613 candidate("192.0.2.9"),
614 candidate("192.0.2.1"),
615 candidate("192.0.2.5"),
616 ];
617 assign_local_preferences(&mut candidates);
618
619 let preference = |ip: &str| {
620 candidates
621 .iter()
622 .find(|candidate| candidate.gathered.address.ip().to_string() == ip)
623 .unwrap()
624 .local_preference
625 };
626 assert_eq!(preference("192.0.2.1"), 65535);
627 assert_eq!(preference("192.0.2.5"), 65534);
628 assert_eq!(preference("192.0.2.9"), 65533);
629 }
630
631 /// One candidate of a type for a component gets §5.1.2.1's SHOULD value, and a second
632 /// component starts again from it — the uniqueness rule is per type *and* component.
633 #[test]
634 fn a_single_address_gets_65535_for_every_component() {
635 let gathered = |component_id: u16| Gathered {
636 base: LocalBase(0),
637 base_address: SocketAddr::new("192.0.2.1".parse().unwrap(), 5000 + component_id),
638 address: SocketAddr::new("192.0.2.1".parse().unwrap(), 5000 + component_id),
639 kind: CandidateType::Host,
640 component: component(component_id),
641 server: None,
642 };
643 let mut candidates = vec![
644 LocalCandidate {
645 id: LocalId(1),
646 gathered: gathered(1),
647 foundation: LocalFoundation(1),
648 local_preference: 0,
649 priority: Priority::MIN,
650 },
651 LocalCandidate {
652 id: LocalId(2),
653 gathered: gathered(2),
654 foundation: LocalFoundation(1),
655 local_preference: 0,
656 priority: Priority::MIN,
657 },
658 ];
659 assign_local_preferences(&mut candidates);
660 assert_eq!(candidates[0].local_preference, SINGLE_ADDRESS_PREFERENCE);
661 assert_eq!(candidates[1].local_preference, SINGLE_ADDRESS_PREFERENCE);
662 assert_eq!(candidates[0].priority.get(), 2_130_706_431);
663 assert_eq!(candidates[1].priority.get(), 2_130_706_430);
664 }
665}