Skip to main content

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}