Skip to main content

sipx_media/ice/
agent.rs

1//! The ICE agent: a state machine over events (RFC 8445 §6–§8, §11; [spec] §2, §6–§10).
2//!
3//! Sans-IO, and that is a constraint from the working agreement rather than a preference. The
4//! agent reads no clock, owns no socket and holds no `tokio` type. Time arrives as
5//! [`Input::TimerFired`] and leaves as [`Output::SetTimer`]; datagrams arrive as bytes with a
6//! source address and leave as bytes with a destination. Everything that makes ICE hard to get
7//! right — pacing, retransmission, the order two agents converge in — is therefore reachable from
8//! an ordinary unit test with no sleeping and no flakiness, which is the only way the seven rows
9//! of §7.3.1.1's role-conflict table can each be asserted.
10//!
11//! What is *not* here, deliberately:
12//!
13//! - **Aggressive nomination.** RFC 8445 §4 deprecated it and §8.1.1 explains why it is no longer
14//!   even useful — "in this specification, data can always be sent on any valid pair, without
15//!   nomination". There is no option to enable it, because an option to enable it is an option to
16//!   re-nominate mid-session, which is the behaviour `a=ice-options:ice2` exists to stop. The
17//!   controlled side still tolerates a peer that nominates more than once, by selecting the
18//!   highest-priority nominated pair: tolerating a legacy peer is not the same as being one.
19//! - **The lite role.** [spec] §12, with the reason. Interoperating with a lite *peer* is in
20//!   scope and is why [`Input::RemoteDescription`] carries `lite`.
21//! - **Trickle ICE, TURN, and the socket.** The first two are out of scope for the spec; the
22//!   third is the driver's, and the driver is a loop over [`Agent::handle`].
23//!
24//! [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
25
26use std::net::SocketAddr;
27use std::time::Duration;
28
29use sipx_sdp::ice::{Candidate, CandidateType, ComponentId, Credentials, Priority};
30
31use super::candidate::{
32    CandidateIds, Foundations, Gathered, LocalBase, LocalCandidate, LocalId, PairFoundation,
33    RemoteCandidate, RemoteId, assign_local_preferences, find_local, find_remote,
34};
35use super::checklist::{
36    CandidatePair, Checklist, ChecklistSet, ChecklistState, PairId, PairIds, PairState, Role,
37    ValidPair, form_pairs, ordered_pair_priority,
38};
39use super::stun::{self, Class, Message, Peering, RoleAttribute, TransactionId};
40use super::timing::Timers;
41
42/// RFC 8445 §6.1.2.5's default limit on the size of the checklist set.
43pub const DEFAULT_PAIR_LIMIT: usize = 100;
44
45/// Only UDP: [spec] §3, and the transport is part of §5.1.1.3's foundation.
46///
47/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
48const TRANSPORT: sipx_sdp::ice::Transport = sipx_sdp::ice::Transport::Udp;
49
50/// What the agent may be waiting for.
51///
52/// There is one retransmission timer per outstanding check rather than one for the agent, because
53/// §14.3's RTO is per transaction: two checks sent one Ta apart have different retransmission
54/// intervals, since the number of outstanding checks changed between them.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub enum Timer {
57    /// Ta: the pacing tick. One check leaves per tick, across the whole checklist set (§14.2).
58    Ta,
59    /// The retransmission timer for the check outstanding on this pair (RFC 5389 §7.2.1).
60    Retransmit(PairId),
61    /// Tn: how long the controlling agent keeps checking after the first valid pair appears
62    /// before it nominates ([spec] §8).
63    ///
64    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
65    Nomination,
66    /// Tr: the keepalive interval on the selected pairs (§11).
67    Keepalive,
68}
69
70/// Something that happened.
71#[derive(Debug, Clone)]
72pub enum Input {
73    /// The far end's ICE parameters, from an offer or an answer.
74    RemoteDescription {
75        /// Its `a=ice-ufrag` and `a=ice-pwd` (RFC 8839 §5.4).
76        credentials: Credentials,
77        /// Its `a=candidate` lines. Ones naming a transport sipx does not check over are
78        /// discarded here rather than failing the description.
79        candidates: Vec<Candidate>,
80        /// Whether it said `a=ice-lite` (RFC 8839 §5.3). A lite peer never sends a check, and
81        /// §6.1.1 makes a full agent facing one controlling unconditionally.
82        lite: bool,
83    },
84    /// The local half of the ICE session a restart is about to begin ([spec] §13.5).
85    ///
86    /// Applied **before** the [`Self::RemoteDescription`] that carries the restart, because the
87    /// answer to a restart has to name these rather than the credentials the old session keyed its
88    /// checks with. Setting them is not itself a restart: nothing is rebuilt until a description
89    /// arrives whose `ice-ufrag` *and* `ice-pwd` have both changed (RFC 8839 §4.4.1.1.1), so an
90    /// offerer can announce its new parameters and still be answered by a peer that declines.
91    ///
92    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
93    LocalCredentials {
94        /// Our `a=ice-ufrag` and `a=ice-pwd` for the new session (RFC 8839 §5.4).
95        credentials: Credentials,
96        /// §7.1.3's tiebreaker, drawn fresh — a restart is a new session, and reusing the old
97        /// value would resolve a role conflict the way the previous session resolved it.
98        tiebreaker: u64,
99    },
100    /// A local candidate the driver gathered.
101    LocalCandidate(Gathered),
102    /// Gathering will produce nothing further.
103    GatheringDone,
104    /// A datagram [`crate::dtls::classify`] called STUN, and where it came from.
105    Datagram {
106        /// Its source address.
107        from: SocketAddr,
108        /// The socket it arrived on.
109        on: LocalBase,
110        /// The bytes.
111        bytes: Vec<u8>,
112    },
113    /// Media went out on a selected pair; resets that pair's keepalive timer (§11).
114    DataSent {
115        /// Which component's selected pair carried it.
116        component: ComponentId,
117    },
118    /// A timer fired.
119    TimerFired(Timer),
120}
121
122/// Something the driver must do, in the order given.
123///
124/// A `Send` always precedes the `SetTimer` that will retransmit it — the same rule the transaction
125/// machines follow, so a retransmission timer can never start before the thing it retransmits has
126/// gone out.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub enum Output {
129    /// Put these bytes on the wire. The driver owns the socket.
130    Send {
131        /// Which socket to send from.
132        on: LocalBase,
133        /// Where to send them.
134        to: SocketAddr,
135        /// The datagram.
136        bytes: Vec<u8>,
137    },
138    /// Arrange for this timer to fire after this long.
139    SetTimer {
140        /// Which timer.
141        timer: Timer,
142        /// How long from now.
143        after: Duration,
144    },
145    /// Cancel a timer that has not fired.
146    ClearTimer(Timer),
147    /// A component has a selected pair: media goes here now, in both directions.
148    Selected {
149        /// Which component.
150        component: ComponentId,
151        /// The socket to send it from.
152        local: LocalBase,
153        /// How this side gathered the selected local candidate.
154        local_kind: CandidateType,
155        /// The address to send it to.
156        remote: SocketAddr,
157        /// How the peer described the selected remote candidate.
158        remote_kind: CandidateType,
159    },
160    /// ICE failed for a component. The call layer decides what that means.
161    Failed {
162        /// Which component.
163        component: ComponentId,
164    },
165}
166
167/// Everything about the agent a deployment may change.
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct Config {
170    /// The timers of §14 and [spec] §9.
171    ///
172    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
173    pub timers: Timers,
174    /// §6.1.2.5's limit on the size of the checklist set. "The default limit … is 100, but the
175    /// value MUST be configurable."
176    pub pair_limit: usize,
177}
178
179impl Default for Config {
180    fn default() -> Self {
181        Self {
182            timers: Timers::default(),
183            pair_limit: DEFAULT_PAIR_LIMIT,
184        }
185    }
186}
187
188/// One outstanding connectivity check (RFC 5389 §7.2.1, RFC 8445 §7.2).
189#[derive(Debug, Clone)]
190struct Transaction {
191    id: TransactionId,
192    pair: PairId,
193    on: LocalBase,
194    /// The local address the request went out from — half of §7.2.5.2.1's symmetry test.
195    from: SocketAddr,
196    /// The address it was sent to — the other half.
197    to: SocketAddr,
198    /// The exact bytes, kept so a retransmission is a retransmission and not a second message
199    /// with the same transaction ID.
200    bytes: Vec<u8>,
201    /// The `PRIORITY` this check claimed, which §7.2.5.3.1 makes the priority of any
202    /// peer-reflexive candidate the response teaches us.
203    priority: Priority,
204    /// Which role attribute went out, which is what §7.2.5.1 reads to decide which way to switch.
205    role: RoleAttribute,
206    /// Whether this check carried `USE-CANDIDATE`.
207    nominating: bool,
208    attempt: u32,
209    rto: Duration,
210    initial_rto: Duration,
211    final_wait: bool,
212    /// §7.3.1.4's cancellation: no more retransmissions and no failure on silence, but the
213    /// response is still accepted if it arrives.
214    cancelled: bool,
215}
216
217/// What §7.3.1.1 makes of an inbound check's role attribute.
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219enum Conflict {
220    /// No conflict: the peer named the other role, or named none at all.
221    None,
222    /// Our role changed. §7.3.1.1's remaining processing still runs.
223    Switched,
224    /// Answer 487 Role Conflict and keep our role. Nothing else in §7.3.1 runs.
225    Reject,
226}
227
228/// How far the agent has got towards having checklists to pace.
229///
230/// An ordered three-state and not a pair of flags: "gathering finished" and "the checklists are
231/// formed" are not independent, and the states they can be in together are exactly these three.
232#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
233enum Phase {
234    /// The driver is still gathering candidates.
235    Gathering,
236    /// Gathering is finished. The checklists form as soon as both halves of the exchange are in.
237    Gathered,
238    /// The checklists exist and Ta is pacing over them.
239    Checking,
240}
241
242/// How far [spec] §8's stopping criterion has got.
243///
244/// `Tn` is armed by the first valid pair and not by the first check, because the criterion is
245/// "how long the controlling agent keeps checking **after the first valid pair**": arming it
246/// earlier nominates before there is anything to nominate.
247///
248/// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250enum Stopping {
251    /// No valid pair yet, so nothing is being waited for.
252    Idle,
253    /// A valid pair appeared and `Tn` is running.
254    Armed,
255    /// `Tn` fired: nominate the best valid pair for each component now.
256    Elapsed,
257}
258
259/// A component that has been concluded.
260#[derive(Debug, Clone, PartialEq, Eq)]
261struct Selection {
262    component: ComponentId,
263    local: LocalBase,
264    remote: SocketAddr,
265    priority: u64,
266}
267
268/// The sans-IO ICE agent.
269///
270/// One agent per data stream, which is one checklist. The checklist *set* it holds is still a set
271/// — see [`super::checklist`] — because §6.1.2.6's unfreezing rule is stated over the set.
272#[derive(Debug)]
273pub struct Agent {
274    config: Config,
275    role: Role,
276    tiebreaker: u64,
277    offerer: bool,
278    credentials: Credentials,
279    peering: Option<Peering>,
280    local: Vec<LocalCandidate>,
281    remote: Vec<RemoteCandidate>,
282    foundations: Foundations,
283    ids: PairIds,
284    candidate_ids: CandidateIds,
285    /// Whether a Ta timer is outstanding, so that a path which creates work after the checklist
286    /// set went quiet can arm one and a path that did not cannot arm two.
287    ///
288    /// Without it, §7.3.1.4's triggered check for an address first seen *after* a checklist
289    /// completed is enqueued and never sent: `conclude` cleared Ta and only a Ta tick sends
290    /// anything. That is §8.1.1's tolerance clause dead on arrival, and it is not observable in a
291    /// test that fires Ta by hand.
292    ta_armed: bool,
293    set: ChecklistSet,
294    transactions: Vec<Transaction>,
295    phase: Phase,
296    /// Pairs the controlling agent has enqueued a nominating check for (§8.1.1). A component
297    /// appears here once and once only: "the agent MUST NOT nominate another pair for [the] same
298    /// component … within the ICE session".
299    nominating: Vec<(ComponentId, PairId)>,
300    /// Pairs whose triggered check was caused by a `USE-CANDIDATE` we accepted while controlled
301    /// (§7.3.1.5), and whose success therefore nominates.
302    nominate_on_success: Vec<PairId>,
303    /// Where [spec] §8's `Tn` has got to.
304    ///
305    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
306    stopping: Stopping,
307    selected: Vec<Selection>,
308    failed: Vec<ComponentId>,
309}
310
311impl Agent {
312    /// A new agent.
313    ///
314    /// `offerer` is whether sipx sent the initial offer, which is §6.1.1's role determination for
315    /// two full agents; `tiebreaker` is §7.1.3's 64-bit value, chosen at random per ICE session.
316    ///
317    /// The *initial* tiebreaker is the caller's so that a test can choose it — §7.3.1.1's `T = V`
318    /// row is not reachable otherwise. The redraw after a 487 is not, and cannot be: §7.2.5.1
319    /// requires the value to change, and in the symmetric conflict both ends apply the same rule
320    /// to the same old value, so anything derived from it leaves them equal and oscillating (see
321    /// this module's `fresh_tiebreaker`). That one path reaches for the process RNG, as
322    /// [`stun::new_transaction_id`] already does for every check's transaction ID. Randomness is
323    /// not I/O — no clock is read and no socket is touched — but it does mean the 487 path is the
324    /// one place a test can pin only that the value *changed*, not what it became.
325    #[must_use]
326    pub fn new(config: Config, offerer: bool, credentials: Credentials, tiebreaker: u64) -> Self {
327        Self {
328            config,
329            role: Role::determine(offerer, false),
330            tiebreaker,
331            offerer,
332            credentials,
333            peering: None,
334            local: Vec::new(),
335            remote: Vec::new(),
336            foundations: Foundations::default(),
337            ids: PairIds::default(),
338            candidate_ids: CandidateIds::default(),
339            ta_armed: false,
340            set: ChecklistSet::new(),
341            transactions: Vec::new(),
342            phase: Phase::Gathering,
343            nominating: Vec::new(),
344            nominate_on_success: Vec::new(),
345            stopping: Stopping::Idle,
346            selected: Vec::new(),
347            failed: Vec::new(),
348        }
349    }
350
351    /// Our role (§6.1.1), which §7.3.1.1 and §7.2.5.1 may change.
352    #[must_use]
353    pub const fn role(&self) -> Role {
354        self.role
355    }
356
357    /// Our tiebreaker (§7.1.3). It changes when a 487 does (§7.2.5.1).
358    #[must_use]
359    pub const fn tiebreaker(&self) -> u64 {
360        self.tiebreaker
361    }
362
363    /// Our short-term credentials — what a later offer or answer must put in `a=ice-ufrag` and
364    /// `a=ice-pwd` for this stream ([spec] §13.5).
365    ///
366    /// Read rather than remembered by the caller: these change under [`Input::LocalCredentials`],
367    /// and a signalling layer holding its own copy is a second place for them to be right.
368    ///
369    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
370    #[must_use]
371    pub const fn credentials(&self) -> &Credentials {
372        &self.credentials
373    }
374
375    /// The short-term credential directions after a peer description was applied.
376    #[cfg(feature = "dtls")]
377    pub(crate) const fn peering(&self) -> Option<&Peering> {
378        self.peering.as_ref()
379    }
380
381    /// The checklist set.
382    #[must_use]
383    pub const fn checklists(&self) -> &ChecklistSet {
384        &self.set
385    }
386
387    /// The local candidates, including any peer-reflexive one §7.2.5.3.1 learned.
388    #[must_use]
389    pub fn local_candidates(&self) -> &[LocalCandidate] {
390        &self.local
391    }
392
393    /// The remote candidates, including any peer-reflexive one §7.3.1.3 learned.
394    #[must_use]
395    pub fn remote_candidates(&self) -> &[RemoteCandidate] {
396        &self.remote
397    }
398
399    /// The selected pair for a component, once there is one (§8.1.1).
400    #[must_use]
401    pub fn selected(&self, component: ComponentId) -> Option<(LocalBase, SocketAddr)> {
402        self.selected
403            .iter()
404            .find(|selection| selection.component == component)
405            .map(|selection| (selection.local, selection.remote))
406    }
407
408    /// Feed the agent an event and get back what the driver must do.
409    pub fn handle(&mut self, input: Input) -> Vec<Output> {
410        let mut out = Vec::new();
411        match input {
412            Input::RemoteDescription {
413                credentials,
414                candidates,
415                lite,
416            } => self.remote_description(credentials, &candidates, lite, &mut out),
417            Input::LocalCredentials {
418                credentials,
419                tiebreaker,
420            } => {
421                self.credentials = credentials;
422                self.tiebreaker = tiebreaker;
423            }
424            Input::LocalCandidate(gathered) => self.local_candidate(gathered),
425            Input::GatheringDone => {
426                self.phase = self.phase.max(Phase::Gathered);
427                self.start(&mut out);
428            }
429            Input::Datagram { from, on, bytes } => self.datagram(from, on, &bytes, &mut out),
430            Input::DataSent { component } => {
431                if self.selected(component).is_some() {
432                    out.push(Output::SetTimer {
433                        timer: Timer::Keepalive,
434                        after: self.config.timers.tr,
435                    });
436                }
437            }
438            Input::TimerFired(timer) => self.timer(timer, &mut out),
439        }
440        out
441    }
442
443    // ---------------------------------------------------------------- gathering and description
444
445    /// A description from the peer — the first one, a later one for the same ICE session, or an
446    /// ICE restart.
447    ///
448    /// The candidate list is **merged**, never replaced. Every live [`CandidatePair`] holds a
449    /// [`RemoteId`], and RFC 8839 §4.2 lets a peer send more than one description for the same
450    /// session — a 183 with SDP and then a 200 with SDP, or any re-INVITE. Replacing the table
451    /// under the pairs would leave each of them naming a candidate it was never formed for, or
452    /// naming nothing at all, and an agent whose every pair dangles sends no checks, reports no
453    /// failure and is simply silent. The candidate list is the peer's to choose, so that is a
454    /// re-offer silencing ICE.
455    ///
456    /// Changing **both** `ice-ufrag` and `ice-pwd` is RFC 8839 §4.4.1.1.1's ICE restart, and only
457    /// that rebuilds: new checklists, new pair states, nothing carried over but the selected pair
458    /// media is still flowing on ([spec] §13.2).
459    ///
460    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
461    fn remote_description(
462        &mut self,
463        credentials: Credentials,
464        candidates: &[Candidate],
465        lite: bool,
466        out: &mut Vec<Output>,
467    ) {
468        // §6.1.1, and the peer's `a=ice-lite` is the whole reason this is not decided in the
469        // constructor: a full agent facing a lite one controls whoever offered.
470        self.role = Role::determine(self.offerer, lite);
471        let restart = self.peering.as_ref().is_some_and(|peering| {
472            let known = peering.remote();
473            known.ufrag() != credentials.ufrag() && known.pwd() != credentials.pwd()
474        });
475        self.peering = Some(Peering::new(self.credentials.clone(), credentials));
476        if restart {
477            self.restart(out);
478        }
479        let added = self.merge_remote_candidates(candidates);
480        if self.phase == Phase::Checking {
481            if added > 0 {
482                self.extend(out);
483            }
484        } else {
485            self.start(out);
486        }
487    }
488
489    /// Add the candidates this description brought that we do not already have, by address.
490    ///
491    /// Returns how many were new. A candidate we learned as peer-reflexive (§7.3.1.3) and the
492    /// peer has now signalled properly keeps its identity and its pairs; §7.3.1.3 says as much —
493    /// "if any subsequent candidate exchanges contain this peer-reflexive candidate, it will
494    /// signal the actual foundation for the candidate".
495    fn merge_remote_candidates(&mut self, candidates: &[Candidate]) -> usize {
496        let mut added = 0usize;
497        for candidate in candidates {
498            let Some(parsed) = RemoteCandidate::signalled(RemoteId(0), candidate) else {
499                continue;
500            };
501            if let Some(known) = self
502                .remote
503                .iter_mut()
504                .find(|known| known.address == parsed.address)
505            {
506                // Keep the identity — pairs hold it — and take the description's word for the
507                // rest.
508                known.foundation = parsed.foundation;
509                known.kind = parsed.kind;
510                known.priority = parsed.priority;
511                continue;
512            }
513            let id = self.candidate_ids.remote();
514            self.remote.push(RemoteCandidate { id, ..parsed });
515            added = added.saturating_add(1);
516        }
517        added
518    }
519
520    /// RFC 8839 §4.4.1.1.1: everything is rebuilt for the new ICE session.
521    fn restart(&mut self, out: &mut Vec<Output>) {
522        for transaction in std::mem::take(&mut self.transactions) {
523            out.push(Output::ClearTimer(Timer::Retransmit(transaction.pair)));
524        }
525        self.remote.clear();
526        self.set = ChecklistSet::new();
527        self.nominating.clear();
528        self.nominate_on_success.clear();
529        self.stopping = Stopping::Idle;
530        self.failed.clear();
531        self.phase = self.phase.min(Phase::Gathered);
532        if self.ta_armed {
533            self.ta_armed = false;
534            out.push(Output::ClearTimer(Timer::Ta));
535        }
536        // `selected` is deliberately kept: media keeps flowing on the old selected pair until the
537        // new session selects one ([spec] §13.2), and the keepalive timer with it.
538    }
539
540    /// §6.1.2: "If candidates are added to a checklist … the agent will re-perform these steps for
541    /// the updated checklist."
542    ///
543    /// The pairs already in the set keep their state and their identities; only the new ones are
544    /// formed, pruned against what is there, limited and then unfrozen.
545    fn extend(&mut self, out: &mut Vec<Output>) {
546        let paired: Vec<RemoteId> = self
547            .set
548            .checklists()
549            .iter()
550            .flat_map(|list| list.pairs().iter().map(|pair| pair.remote))
551            .collect();
552        let fresh: Vec<RemoteCandidate> = self
553            .remote
554            .iter()
555            .filter(|candidate| !paired.contains(&candidate.id))
556            .cloned()
557            .collect();
558        if fresh.is_empty() {
559            return;
560        }
561        let pairs = form_pairs(&mut self.ids, self.role, &self.local, &fresh);
562        for pair in pairs {
563            self.insert_pair(pair);
564        }
565        self.set.limit(self.config.pair_limit);
566        self.forget_unreferenced_remotes();
567        self.set.unfreeze_added();
568        self.arm_ta(out);
569    }
570
571    fn local_candidate(&mut self, gathered: Gathered) {
572        let foundation = self.foundations.assign(&gathered, TRANSPORT);
573        self.local.push(LocalCandidate {
574            id: self.candidate_ids.local(),
575            gathered,
576            foundation,
577            local_preference: 0,
578            priority: Priority::MIN,
579        });
580        // §5.1.2.1's local preference is a property of the set, so every candidate is repriced
581        // whenever the set grows.
582        assign_local_preferences(&mut self.local);
583    }
584
585    /// §6.1.2: form the checklists once both halves of the exchange are in.
586    fn start(&mut self, out: &mut Vec<Output>) {
587        if self.phase != Phase::Gathered || self.peering.is_none() {
588            return;
589        }
590        if self.local.is_empty() || self.remote.is_empty() {
591            return;
592        }
593        self.phase = Phase::Checking;
594        let pairs = form_pairs(&mut self.ids, self.role, &self.local, &self.remote);
595        self.set = ChecklistSet::new();
596        self.set.push(Checklist::new(pairs));
597        self.set.limit(self.config.pair_limit);
598        self.set.compute_initial_states();
599        self.arm_ta(out);
600    }
601
602    /// Arm Ta if it is not already armed.
603    ///
604    /// Every path that creates work goes through here, not only [`Agent::start`]: a triggered
605    /// check enqueued after a checklist completed has to be able to restart the pacing, or it is
606    /// queued for a tick that will never come.
607    fn arm_ta(&mut self, out: &mut Vec<Output>) {
608        if self.ta_armed {
609            return;
610        }
611        self.ta_armed = true;
612        out.push(Output::SetTimer {
613            timer: Timer::Ta,
614            after: self.config.timers.pacing(),
615        });
616    }
617
618    /// Insert a pair, unless the checklist already holds one that §6.1.2.4 would call redundant
619    /// with it — the same local base against the same remote address.
620    fn insert_pair(&mut self, pair: CandidatePair) {
621        let Some((base, remote)) = find_local(&self.local, pair.local)
622            .map(|local| local.gathered.base_address)
623            .zip(find_remote(&self.remote, pair.remote).map(|remote| remote.address))
624        else {
625            return;
626        };
627        let redundant = self.set.checklists().iter().any(|list| {
628            list.pairs().iter().any(|known| {
629                find_local(&self.local, known.local)
630                    .is_some_and(|local| local.gathered.base_address == base)
631                    && find_remote(&self.remote, known.remote)
632                        .is_some_and(|known| known.address == remote)
633            })
634        });
635        if redundant {
636            return;
637        }
638        if let Some(list) = self.set.checklists_mut().first_mut() {
639            list.insert(pair);
640        }
641    }
642
643    /// Drop peer-reflexive remote candidates that no pair and no valid pair refers to any more.
644    ///
645    /// §6.1.2.5's limit bounds the pairs; this bounds the table they index. Without it, §7.3.1.3
646    /// grows one remote candidate per distinct source address that can produce an authenticated
647    /// check, which is unbounded even when every pair it would have formed was discarded.
648    fn forget_unreferenced_remotes(&mut self) {
649        let mut live: Vec<RemoteId> = self
650            .set
651            .checklists()
652            .iter()
653            .flat_map(|list| list.pairs().iter().map(|pair| pair.remote))
654            .collect();
655        let addresses: Vec<SocketAddr> = self
656            .set
657            .checklists()
658            .iter()
659            .flat_map(|list| list.valid().iter().map(|valid| valid.remote))
660            .chain(self.selected.iter().map(|selection| selection.remote))
661            .collect();
662        for candidate in &self.remote {
663            if addresses.contains(&candidate.address) {
664                live.push(candidate.id);
665            }
666        }
667        self.remote.retain(|candidate| {
668            candidate.kind != CandidateType::PeerReflexive || live.contains(&candidate.id)
669        });
670    }
671
672    // ------------------------------------------------------------------------------- the timers
673
674    fn timer(&mut self, timer: Timer, out: &mut Vec<Output>) {
675        match timer {
676            Timer::Ta => self.pace(out),
677            Timer::Retransmit(pair) => self.retransmit(pair, out),
678            Timer::Nomination => {
679                self.stopping = Stopping::Elapsed;
680                self.nominate();
681            }
682            Timer::Keepalive => self.keepalive(out),
683        }
684    }
685
686    /// §6.1.4.2: one check per Ta tick, taken from the next checklist in the Running state.
687    fn pace(&mut self, out: &mut Vec<Output>) {
688        // The timer that brought us here has fired, so it is no longer outstanding.
689        self.ta_armed = false;
690        let count = self.set.checklists().len();
691        for _ in 0..count {
692            let Some(index) = self.set.next_active() else {
693                break;
694            };
695            // Step 1: the triggered-check queue first, whatever its pairs' priorities are. This
696            // is what makes ICE converge in the time it takes a peer's check to arrive.
697            let triggered = self
698                .set
699                .checklists_mut()
700                .get_mut(index)
701                .and_then(Checklist::take_triggered);
702            if let Some(id) = triggered {
703                self.send_check(id, out);
704                break;
705            }
706            if self
707                .set
708                .checklists()
709                .get(index)
710                .is_some_and(|list| list.state() != ChecklistState::Running)
711            {
712                // A concluded checklist answers what is still queued for it and starts nothing
713                // new (§8.1.2).
714                continue;
715            }
716            // Step 2: nothing Waiting here, so thaw what the set allows.
717            self.set.unfreeze_idle(index);
718            // Step 3: the highest-priority Waiting pair, ties broken by the lowest component.
719            let waiting = self.set.checklists().get(index).and_then(|list| {
720                list.pairs()
721                    .iter()
722                    .filter(|pair| pair.state == PairState::Waiting)
723                    .max_by_key(|pair| (pair.priority, std::cmp::Reverse(pair.component)))
724                    .map(|pair| pair.id)
725            });
726            if let Some(id) = waiting {
727                self.send_check(id, out);
728                break;
729            }
730            // Step 4: nothing to do for this checklist; try the next one without waiting for Ta.
731        }
732        if self.active() {
733            self.arm_ta(out);
734        }
735    }
736
737    /// Whether any checklist still has work: one in the Running state, or one whose
738    /// triggered-check queue is not empty.
739    ///
740    /// The second half is what keeps §8.1.1's tolerance clause meaningful. A peer that nominates
741    /// more than once has its later nominations answered by a checklist that is already
742    /// Completed, and a Ta tick that stops at the first Completed checklist would leave those
743    /// triggered checks queued forever — so the highest-priority nominated pair would never be
744    /// the selected one.
745    fn active(&self) -> bool {
746        self.set
747            .checklists()
748            .iter()
749            .any(|list| list.state() == ChecklistState::Running || list.has_triggered())
750    }
751
752    /// RFC 5389 §7.2.1: Rc transmissions, doubling the interval each time, then a final wait of
753    /// Rm times the RTO before the transaction has timed out.
754    fn retransmit(&mut self, pair: PairId, out: &mut Vec<Output>) {
755        let Some(position) = self
756            .transactions
757            .iter()
758            .position(|transaction| transaction.pair == pair)
759        else {
760            return;
761        };
762        let Some(transaction) = self.transactions.get_mut(position) else {
763            return;
764        };
765        if transaction.cancelled {
766            // §7.3.1.4: a cancelled transaction is not retransmitted and its silence is not a
767            // failure. The pair already has a triggered check of its own.
768            self.transactions.remove(position);
769            return;
770        }
771        if transaction.attempt < self.config.timers.rc {
772            transaction.attempt = transaction.attempt.saturating_add(1);
773            transaction.rto = self.config.timers.double(transaction.rto);
774            let (on, to, bytes, after) = (
775                transaction.on,
776                transaction.to,
777                transaction.bytes.clone(),
778                transaction.rto,
779            );
780            out.push(Output::Send { on, to, bytes });
781            out.push(Output::SetTimer {
782                timer: Timer::Retransmit(pair),
783                after,
784            });
785            return;
786        }
787        if !transaction.final_wait {
788            transaction.final_wait = true;
789            let after = self.config.timers.final_wait(transaction.initial_rto);
790            out.push(Output::SetTimer {
791                timer: Timer::Retransmit(pair),
792                after,
793            });
794            return;
795        }
796        // §7.2.5.2.3: the transaction timed out, so the pair failed.
797        let nominating = transaction.nominating;
798        self.transactions.remove(position);
799        self.fail_pair(pair, nominating, out);
800    }
801
802    /// §11: a Binding Indication on each selected pair, holding the NAT binding open.
803    fn keepalive(&mut self, out: &mut Vec<Output>) {
804        for selection in &self.selected {
805            if let Ok(bytes) = stun::keepalive(stun::new_transaction_id()) {
806                out.push(Output::Send {
807                    on: selection.local,
808                    to: selection.remote,
809                    bytes,
810                });
811            }
812        }
813        if !self.selected.is_empty() {
814            out.push(Output::SetTimer {
815                timer: Timer::Keepalive,
816                after: self.config.timers.tr,
817            });
818        }
819    }
820
821    // -------------------------------------------------------------------------- sending a check
822
823    fn send_check(&mut self, id: PairId, out: &mut Vec<Output>) {
824        let Some(peering) = self.peering.clone() else {
825            return;
826        };
827        let Some(pair) = self.set.pair(id) else {
828            return;
829        };
830        let (local_id, remote_id, component) = (pair.local, pair.remote, pair.component);
831        let (Some(local), Some(remote)) = (
832            find_local(&self.local, local_id),
833            find_remote(&self.remote, remote_id),
834        ) else {
835            return;
836        };
837        let (on, from, to) = (
838            local.gathered.base,
839            local.gathered.base_address,
840            remote.address,
841        );
842        // §7.1.1: the peer-reflexive preference, not the candidate's own.
843        let check_priority = local.check_priority();
844        let nominating = self.role.is_controlling()
845            && self
846                .nominating
847                .iter()
848                .any(|(nominated, pair)| *nominated == component && *pair == id);
849        let role = match self.role {
850            Role::Controlling => RoleAttribute::Controlling {
851                tiebreaker: self.tiebreaker,
852                nominate: nominating,
853            },
854            Role::Controlled => RoleAttribute::Controlled {
855                tiebreaker: self.tiebreaker,
856            },
857        };
858        let transaction_id = stun::new_transaction_id();
859        let Ok(bytes) = stun::connectivity_check(transaction_id, &peering, check_priority, role)
860        else {
861            return;
862        };
863
864        if let Some(pair) = self.set.pair_mut(id) {
865            pair.state = PairState::InProgress;
866        }
867        // §14.3, and the reason it is here and not in the constructor: the RTO counts the checks
868        // outstanding *now*, including this one.
869        let rto = self
870            .config
871            .timers
872            .rto(self.set.total_pairs(), self.set.outstanding());
873
874        out.push(Output::Send {
875            on,
876            to,
877            bytes: bytes.clone(),
878        });
879        out.push(Output::SetTimer {
880            timer: Timer::Retransmit(id),
881            after: rto,
882        });
883        self.transactions
884            .retain(|transaction| transaction.pair != id);
885        self.transactions.push(Transaction {
886            id: transaction_id,
887            pair: id,
888            on,
889            from,
890            to,
891            bytes,
892            priority: check_priority,
893            role,
894            nominating,
895            attempt: 1,
896            rto,
897            initial_rto: rto,
898            final_wait: false,
899            cancelled: false,
900        });
901    }
902
903    // ----------------------------------------------------------------------- inbound  datagrams
904
905    fn datagram(&mut self, from: SocketAddr, on: LocalBase, bytes: &[u8], out: &mut Vec<Output>) {
906        let Ok(message) = Message::decode(bytes) else {
907            // [spec] §11.3: a malformed datagram is a dropped datagram, never a state change.
908            return;
909        };
910        match message.class() {
911            Class::Request => self.inbound_check(from, on, &message, out),
912            Class::Success | Class::Error => self.inbound_response(from, on, &message, out),
913            // §11's keepalive draws no response and means nothing to the state machine.
914            Class::Indication => {}
915        }
916    }
917
918    /// §7.3: sipx is a STUN server on the media port as well as a client.
919    fn inbound_check(
920        &mut self,
921        from: SocketAddr,
922        on: LocalBase,
923        message: &Message,
924        out: &mut Vec<Output>,
925    ) {
926        let Some(peering) = self.peering.clone() else {
927            return;
928        };
929        // [spec] §11.2 and §11.3: the credential is checked before anything moves. An
930        // unauthenticated check is dropped rather than answered — answering one tells an off-path
931        // attacker which ufrag is live, and RFC 5389 §10.1.2's 401 is of no use to a peer that
932        // never had our password.
933        if message.username() != Some(peering.inbound_username().as_str())
934            || !message.verify_integrity(peering.inbound_key())
935        {
936            return;
937        }
938
939        match self.resolve_conflict(message.role()) {
940            Conflict::Reject => {
941                if let Ok(bytes) = stun::role_conflict(message.transaction(), &peering) {
942                    out.push(Output::Send {
943                        on,
944                        to: from,
945                        bytes,
946                    });
947                }
948                return;
949            }
950            Conflict::Switched | Conflict::None => {}
951        }
952
953        // §7.3.1: the rest runs whether or not the role changed, so long as a success response is
954        // generated — which it is, from here on.
955        if let Ok(bytes) = stun::check_success(message.transaction(), &peering, from) {
956            out.push(Output::Send {
957                on,
958                to: from,
959                bytes,
960            });
961        }
962        if self.phase != Phase::Checking {
963            // No checklist yet, so there is nothing to trigger. The response above still went, as
964            // §7.3 requires of an agent that has published a candidate on this base.
965            return;
966        }
967
968        let Some(local_id) = self.base_candidate(on) else {
969            return;
970        };
971        let Some(component) =
972            find_local(&self.local, local_id).map(|candidate| candidate.gathered.component)
973        else {
974            return;
975        };
976        let remote_id = self.learn_remote(from, component, message.priority());
977        self.triggered_check(local_id, remote_id, component, message.use_candidate(), out);
978    }
979
980    /// §7.3.1.3: a check from an address no remote candidate names is a peer-reflexive remote
981    /// candidate.
982    fn learn_remote(
983        &mut self,
984        from: SocketAddr,
985        component: ComponentId,
986        claimed: Option<Priority>,
987    ) -> RemoteId {
988        if let Some(known) = self
989            .remote
990            .iter()
991            .find(|candidate| candidate.address == from)
992        {
993            return known.id;
994        }
995        let id = self.candidate_ids.remote();
996        self.remote.push(RemoteCandidate {
997            id,
998            address: from,
999            kind: CandidateType::PeerReflexive,
1000            component,
1001            // "an arbitrary value, different from the foundations of all other remote candidates"
1002            foundation: self.foundations.learn_remote(),
1003            // "the priority is the value of the PRIORITY attribute in the Binding request" — and
1004            // a check that carries none is violating §7.1.1, so it gets the floor rather than a
1005            // priority it did not claim.
1006            priority: claimed.unwrap_or(Priority::MIN),
1007        });
1008        id
1009    }
1010
1011    /// §7.3.1.4, and §7.3.1.5's nomination when the check that arrived carried `USE-CANDIDATE`.
1012    fn triggered_check(
1013        &mut self,
1014        local: LocalId,
1015        remote: RemoteId,
1016        component: ComponentId,
1017        use_candidate: bool,
1018        out: &mut Vec<Output>,
1019    ) {
1020        let existing = self
1021            .set
1022            .checklists()
1023            .iter()
1024            .find_map(|list| list.find(local, remote));
1025        let id = if let Some(id) = existing {
1026            id
1027        } else {
1028            // §7.3.1.4: "the pair is inserted into the checklist based on its priority. Its state
1029            // is set to Waiting. The pair is enqueued into the triggered-check queue."
1030            let Some(pair) = self.build_pair(local, remote, component) else {
1031                return;
1032            };
1033            let id = pair.id;
1034            self.insert_pair(pair);
1035            if let Some(pair) = self.set.pair_mut(id) {
1036                pair.state = PairState::Waiting;
1037            }
1038            // §6.1.2.5's limit binds here and not only at formation. This is the growth path a
1039            // peer drives: one pair and one remote candidate per distinct source address that can
1040            // produce an authenticated check, each of which would otherwise become an 88-byte
1041            // check sent to an address the peer named and need not be able to receive at. The
1042            // limit is the bound §19.5.1 asks for, so it is applied every time the set grows.
1043            self.set.limit(self.config.pair_limit);
1044            self.forget_unreferenced_remotes();
1045            if self.set.pair(id).is_none() {
1046                // The new pair was the lowest-priority discardable one: the set is full of better
1047                // paths. The check that arrived is still answered, above; it just does not earn a
1048                // check of its own.
1049                return;
1050            }
1051            if let Some(index) = self.set.checklist_of(id)
1052                && let Some(list) = self.set.checklists_mut().get_mut(index)
1053            {
1054                list.trigger(id);
1055            }
1056            self.arm_ta(out);
1057            id
1058        };
1059        let before = self.set.pair(id).map(|pair| pair.state);
1060        match before {
1061            Some(PairState::Succeeded) => {
1062                // "If the state of that pair is Succeeded, nothing further is done."
1063            }
1064            Some(_) if self.set.pair(id).is_some_and(|pair| pair.nominated) => {
1065                // §8.1.2: "when the state of a pair is Succeeded, an agent will no longer
1066                // generate triggered checks when receiving a Binding request for the pair."
1067                //
1068                // It has to extend past Succeeded to a nominated pair in *any* state, or two
1069                // concluded agents re-trigger each other forever: a queued check of our own moves
1070                // the pair out of Succeeded, the peer's next request then finds it In-Progress
1071                // and §7.3.1.4's cancel-and-re-enqueue fires, and each end keeps the other's
1072                // queue full. Media flows on the selected pair the whole time, so the traffic is
1073                // pure waste — and the checklist never falls quiet, which is what a driver waits
1074                // for.
1075            }
1076            Some(state) => {
1077                if state == PairState::InProgress {
1078                    // "the agent cancels the In-Progress transaction" — no more retransmissions
1079                    // and no failure on silence, but the response is still accepted.
1080                    if let Some(transaction) = self
1081                        .transactions
1082                        .iter_mut()
1083                        .find(|transaction| transaction.pair == id)
1084                    {
1085                        transaction.cancelled = true;
1086                    }
1087                }
1088                if let Some(pair) = self.set.pair_mut(id) {
1089                    pair.state = PairState::Waiting;
1090                }
1091                if let Some(index) = self.set.checklist_of(id)
1092                    && let Some(list) = self.set.checklists_mut().get_mut(index)
1093                {
1094                    list.trigger(id);
1095                }
1096                // §7.3.1.4's check has to be able to leave even when the checklist that holds it
1097                // has already concluded — see [`Agent::ta_armed`].
1098                self.arm_ta(out);
1099            }
1100            None => return,
1101        }
1102
1103        if use_candidate && !self.role.is_controlling() {
1104            // §7.3.1.5. A Succeeded pair is nominated now; anything else is nominated when the
1105            // triggered check this just enqueued succeeds.
1106            if before == Some(PairState::Succeeded) {
1107                self.mark_nominated(id, out);
1108            } else if !self.nominate_on_success.contains(&id) {
1109                self.nominate_on_success.push(id);
1110            }
1111        }
1112    }
1113
1114    fn build_pair(
1115        &mut self,
1116        local: LocalId,
1117        remote: RemoteId,
1118        component: ComponentId,
1119    ) -> Option<CandidatePair> {
1120        let local_candidate = find_local(&self.local, local)?;
1121        let remote_candidate = find_remote(&self.remote, remote)?;
1122        Some(CandidatePair {
1123            id: self.ids.allocate(),
1124            local,
1125            remote,
1126            component,
1127            foundation: PairFoundation {
1128                local: local_candidate.foundation,
1129                remote: remote_candidate.foundation.clone(),
1130            },
1131            priority: ordered_pair_priority(
1132                self.role,
1133                local_candidate.priority,
1134                remote_candidate.priority,
1135            ),
1136            state: PairState::Frozen,
1137            nominated: false,
1138        })
1139    }
1140
1141    fn base_candidate(&self, on: LocalBase) -> Option<LocalId> {
1142        self.local
1143            .iter()
1144            .find(|candidate| {
1145                candidate.gathered.base == on && candidate.gathered.kind == CandidateType::Host
1146            })
1147            .map(|candidate| candidate.id)
1148    }
1149
1150    // ------------------------------------------------------------------------------- a response
1151
1152    fn inbound_response(
1153        &mut self,
1154        from: SocketAddr,
1155        on: LocalBase,
1156        message: &Message,
1157        out: &mut Vec<Output>,
1158    ) {
1159        let Some(peering) = self.peering.clone() else {
1160            return;
1161        };
1162        let Some(position) = self
1163            .transactions
1164            .iter()
1165            .position(|transaction| transaction.id == message.transaction())
1166        else {
1167            return;
1168        };
1169        let Some(transaction) = self.transactions.get(position).cloned() else {
1170            return;
1171        };
1172        // [spec] §11.3: an unauthenticated message moves nothing, including into Failed — or an
1173        // off-path attacker could fail every pair by answering the checks it can see.
1174        if !message.verify_integrity(peering.outbound_key()) {
1175            return;
1176        }
1177        // §7.2.5.2.1's symmetry test, before anything else is read: a response whose source is
1178        // not where the request went cannot be a response to it.
1179        if from != transaction.to || on != transaction.on {
1180            self.transactions.remove(position);
1181            out.push(Output::ClearTimer(Timer::Retransmit(transaction.pair)));
1182            self.fail_pair(transaction.pair, transaction.nominating, out);
1183            return;
1184        }
1185
1186        self.transactions.remove(position);
1187        out.push(Output::ClearTimer(Timer::Retransmit(transaction.pair)));
1188
1189        if message.class() == Class::Error {
1190            if message.error_code() == Some(stun::ROLE_CONFLICT) {
1191                self.role_conflict_response(&transaction);
1192            } else {
1193                // §7.2.5.2.4: an unrecoverable error response fails the pair.
1194                self.fail_pair(transaction.pair, transaction.nominating, out);
1195            }
1196            return;
1197        }
1198
1199        self.success(&transaction, message, out);
1200    }
1201
1202    /// §7.2.5.3: a check succeeded.
1203    fn success(&mut self, transaction: &Transaction, message: &Message, out: &mut Vec<Output>) {
1204        let Some(pair) = self.set.pair(transaction.pair).cloned() else {
1205            return;
1206        };
1207        // §7.2.5.3.1: the mapped address decides whether we just learned a candidate. A response
1208        // without one cannot have; treat it as the un-NATed case rather than as a failure, since
1209        // the pair demonstrably works either way.
1210        let mapped = message.mapped_address().unwrap_or(transaction.from);
1211        let local = self.learn_local(mapped, &pair, transaction.priority);
1212
1213        // §7.2.5.3.2: the valid pair is built from the mapped address and the address the request
1214        // was sent to, which is very often not a pair in any checklist.
1215        let priority = find_local(&self.local, local)
1216            .zip(find_remote(&self.remote, pair.remote))
1217            .map_or(pair.priority, |(local, remote)| {
1218                ordered_pair_priority(self.role, local.priority, remote.priority)
1219            });
1220        let first_valid = if let Some(index) = self.set.checklist_of(transaction.pair) {
1221            self.set
1222                .checklists_mut()
1223                .get_mut(index)
1224                .is_some_and(|list| {
1225                    list.add_valid(ValidPair {
1226                        component: pair.component,
1227                        local,
1228                        remote: transaction.to,
1229                        priority,
1230                        nominated: false,
1231                        generated_by: pair.id,
1232                    })
1233                })
1234        } else {
1235            false
1236        };
1237
1238        // §7.2.5.3.3.
1239        if let Some(pair) = self.set.pair_mut(transaction.pair) {
1240            pair.state = PairState::Succeeded;
1241        }
1242        self.set.unfreeze_foundation(&pair.foundation);
1243
1244        // §7.2.5.3.4, both directions: the check we nominated with, and the check a controlled
1245        // agent sent because the peer nominated.
1246        if transaction.nominating || self.nominate_on_success.contains(&transaction.pair) {
1247            self.nominate_on_success
1248                .retain(|id| *id != transaction.pair);
1249            self.mark_nominated(transaction.pair, out);
1250        }
1251
1252        if first_valid && self.stopping == Stopping::Idle {
1253            // [spec] §8: Tn counts from the first valid pair, not from the first check.
1254            self.stopping = Stopping::Armed;
1255            out.push(Output::SetTimer {
1256                timer: Timer::Nomination,
1257                after: self.config.timers.tn,
1258            });
1259        }
1260
1261        self.update_checklists(out);
1262        self.nominate();
1263    }
1264
1265    /// §7.2.5.3.1: a mapped address that is not a local candidate is a peer-reflexive one.
1266    fn learn_local(
1267        &mut self,
1268        mapped: SocketAddr,
1269        pair: &CandidatePair,
1270        claimed: Priority,
1271    ) -> LocalId {
1272        if let Some(known) = self
1273            .local
1274            .iter()
1275            .find(|candidate| candidate.gathered.address == mapped)
1276        {
1277            return known.id;
1278        }
1279        let Some(base) = find_local(&self.local, pair.local).copied() else {
1280            return pair.local;
1281        };
1282        let gathered = Gathered {
1283            base: base.gathered.base,
1284            base_address: base.gathered.base_address,
1285            address: mapped,
1286            kind: CandidateType::PeerReflexive,
1287            component: pair.component,
1288            server: None,
1289        };
1290        let foundation = self.foundations.assign(&gathered, TRANSPORT);
1291        let id = self.candidate_ids.local();
1292        self.local.push(LocalCandidate {
1293            id,
1294            gathered,
1295            foundation,
1296            local_preference: base.local_preference,
1297            // "The priority is the value of the PRIORITY attribute in the Binding request" — the
1298            // one this agent sent, which §7.1.1 already computed with the peer-reflexive
1299            // preference. That is what makes both ends price this candidate the same.
1300            priority: claimed,
1301        });
1302        id
1303    }
1304
1305    fn fail_pair(&mut self, id: PairId, nominating: bool, out: &mut Vec<Output>) {
1306        if let Some(pair) = self.set.pair_mut(id) {
1307            pair.state = PairState::Failed;
1308        }
1309        if nominating {
1310            // §7.2.5.3.4: a nominated check that fails takes its valid pair and its checklist
1311            // with it. There is no second nomination to fall back on.
1312            if let Some(index) = self.set.checklist_of(id)
1313                && let Some(list) = self.set.checklists_mut().get_mut(index)
1314            {
1315                list.remove_valid(id);
1316                list.set_state(ChecklistState::Failed);
1317            }
1318        }
1319        self.update_checklists(out);
1320    }
1321
1322    // ---------------------------------------------------------------------------- role conflict
1323
1324    /// §7.3.1.1's table, applied to the role attribute on an inbound check.
1325    fn resolve_conflict(&mut self, attribute: Option<RoleAttribute>) -> Conflict {
1326        let Some(attribute) = attribute else {
1327            // The last row: the peer is not doing role signalling, so there is no conflict.
1328            return Conflict::None;
1329        };
1330        let theirs = attribute.tiebreaker();
1331        match (self.role, attribute) {
1332            (Role::Controlling, RoleAttribute::Controlling { .. }) => {
1333                if self.tiebreaker >= theirs {
1334                    Conflict::Reject
1335                } else {
1336                    self.switch_role();
1337                    Conflict::Switched
1338                }
1339            }
1340            (Role::Controlled, RoleAttribute::Controlled { .. }) => {
1341                if self.tiebreaker >= theirs {
1342                    self.switch_role();
1343                    Conflict::Switched
1344                } else {
1345                    Conflict::Reject
1346                }
1347            }
1348            // Controlling against ICE-CONTROLLED, or controlled against ICE-CONTROLLING.
1349            _ => Conflict::None,
1350        }
1351    }
1352
1353    /// §7.2.5.1: our own check drew a 487.
1354    fn role_conflict_response(&mut self, transaction: &Transaction) {
1355        // "If the agent included an ICE-CONTROLLED attribute in the request, the agent MUST switch
1356        // to the controlling role. If the agent included an ICE-CONTROLLING attribute … switch to
1357        // the controlled role." The attribute that went out decides, not the role we hold now.
1358        self.role = match transaction.role {
1359            RoleAttribute::Controlled { .. } => Role::Controlling,
1360            RoleAttribute::Controlling { .. } => Role::Controlled,
1361        };
1362        // "The agent MUST change the tiebreaker value."
1363        self.tiebreaker = fresh_tiebreaker(self.tiebreaker);
1364        self.set
1365            .recompute_priorities(self.role, &self.local, &self.remote);
1366        if let Some(pair) = self.set.pair_mut(transaction.pair) {
1367            pair.state = PairState::Waiting;
1368        }
1369        if let Some(index) = self.set.checklist_of(transaction.pair)
1370            && let Some(list) = self.set.checklists_mut().get_mut(index)
1371        {
1372            list.trigger(transaction.pair);
1373        }
1374    }
1375
1376    fn switch_role(&mut self) {
1377        self.role = self.role.opposite();
1378        // §7.3.1.1's NOTE: "A change in roles will require an agent to recompute pair priorities
1379        // (Section 6.1.2.3), since those priorities are a function of role."
1380        self.set
1381            .recompute_priorities(self.role, &self.local, &self.remote);
1382    }
1383
1384    // ------------------------------------------------------------------------------- concluding
1385
1386    /// §8.1.1's nomination, under [spec] §8's stopping criterion.
1387    ///
1388    /// Regular nomination and nothing else: the controlling agent picks a valid pair and repeats
1389    /// the check that produced it with `USE-CANDIDATE`, by enqueueing that pair on the
1390    /// triggered-check queue. Once a component is nominated it is never nominated again.
1391    ///
1392    /// **On a large checklist set only `Tn` fires this, and that is by design.** §14.3 scales the
1393    /// RTO with `Ta × N × (Num-Waiting + Num-In-Progress)`, so a set at §6.1.2.5's default limit
1394    /// of 100 pairs starts every transaction at `50 ms × 100 × 100` = **500 s**, and Rc = 7
1395    /// transmissions with the doubling and the Rm final wait take over ten hours to exhaust. That
1396    /// is one transmission per pair per call: a higher-priority pair that simply gets no answer
1397    /// never reaches `Failed` inside any real session, so the "every pair of higher priority than
1398    /// the best valid pair has reached `Failed`" half of the criterion cannot become true and the
1399    /// whole decision rests on `Tn`. §19.5.1 treats that pacing as intended — it is the bound on
1400    /// what a candidate list can cost — so `Tn` is the criterion for any set of interesting size
1401    /// and the `Failed` half is the fast path for a small one, not the other way round.
1402    ///
1403    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
1404    fn nominate(&mut self) {
1405        if !self.role.is_controlling() {
1406            return;
1407        }
1408        let mut queue: Vec<(usize, ComponentId, PairId)> = Vec::new();
1409        for (index, list) in self.set.checklists().iter().enumerate() {
1410            if list.state() != ChecklistState::Running {
1411                continue;
1412            }
1413            let components = list.components();
1414            if components.is_empty() {
1415                continue;
1416            }
1417            // "every component has at least one valid pair"
1418            let best: Vec<(ComponentId, &ValidPair)> = components
1419                .iter()
1420                .filter_map(|component| {
1421                    list.valid()
1422                        .iter()
1423                        .filter(|valid| valid.component == *component)
1424                        .max_by_key(|valid| valid.priority)
1425                        .map(|valid| (*component, valid))
1426                })
1427                .collect();
1428            if best.len() != components.len() {
1429                continue;
1430            }
1431            // "…and either every pair of higher priority than the best valid pair has reached
1432            // Failed, or Tn has elapsed since the first valid pair appeared."
1433            let settled = best.iter().all(|(component, valid)| {
1434                list.pairs()
1435                    .iter()
1436                    .filter(|pair| pair.component == *component && pair.priority > valid.priority)
1437                    .all(|pair| pair.state == PairState::Failed)
1438            });
1439            if !settled && self.stopping != Stopping::Elapsed {
1440                continue;
1441            }
1442            for (component, valid) in best {
1443                if self
1444                    .nominating
1445                    .iter()
1446                    .any(|(nominated, _)| *nominated == component)
1447                {
1448                    continue;
1449                }
1450                queue.push((index, component, valid.generated_by));
1451            }
1452        }
1453        for (index, component, pair) in queue {
1454            self.nominating.push((component, pair));
1455            if let Some(list) = self.set.checklists_mut().get_mut(index) {
1456                list.trigger(pair);
1457            }
1458        }
1459    }
1460
1461    /// §7.2.5.3.4 and §7.3.1.5: this pair's valid pair is nominated, which concludes its
1462    /// component.
1463    fn mark_nominated(&mut self, id: PairId, out: &mut Vec<Output>) {
1464        let Some(index) = self.set.checklist_of(id) else {
1465            return;
1466        };
1467        if let Some(pair) = self.set.pair_mut(id) {
1468            pair.nominated = true;
1469        }
1470        if let Some(list) = self.set.checklists_mut().get_mut(index) {
1471            list.nominate_valid(id);
1472        }
1473        self.conclude(index, out);
1474    }
1475
1476    /// §8.1.2: a nominated pair concludes its component, and a nominated pair for every component
1477    /// completes the checklist.
1478    fn conclude(&mut self, index: usize, out: &mut Vec<Output>) {
1479        let Some(list) = self.set.checklists().get(index) else {
1480            return;
1481        };
1482        let components = list.components();
1483        let mut chosen: Vec<(ComponentId, PairId, LocalId, SocketAddr, u64)> = Vec::new();
1484        for component in &components {
1485            // §8.1.1's tolerance for a peer that nominates more than once: "the agents MUST
1486            // produce the selected pairs and use the pairs with the highest priority". sipx never
1487            // nominates twice itself; this is what stops a peer that does from being obeyed.
1488            if let Some(valid) = list
1489                .valid()
1490                .iter()
1491                .filter(|valid| valid.component == *component && valid.nominated)
1492                .max_by_key(|valid| valid.priority)
1493            {
1494                chosen.push((
1495                    *component,
1496                    valid.generated_by,
1497                    valid.local,
1498                    valid.remote,
1499                    valid.priority,
1500                ));
1501            }
1502        }
1503
1504        for (component, pair, local, remote, priority) in &chosen {
1505            let Some(local_candidate) = find_local(&self.local, *local) else {
1506                continue;
1507            };
1508            let base = local_candidate.gathered.base;
1509            let local_kind = local_candidate.gathered.kind;
1510            let Some(remote_kind) = self
1511                .set
1512                .pair(*pair)
1513                .and_then(|candidate_pair| find_remote(&self.remote, candidate_pair.remote))
1514                .map(|candidate| candidate.kind)
1515            else {
1516                continue;
1517            };
1518            let selection = Selection {
1519                component: *component,
1520                local: base,
1521                remote: *remote,
1522                priority: *priority,
1523            };
1524            let existing = self
1525                .selected
1526                .iter_mut()
1527                .find(|known| known.component == *component);
1528            match existing {
1529                Some(known) if *known == selection => continue,
1530                Some(known) if known.priority >= *priority => continue,
1531                Some(known) => *known = selection,
1532                None => self.selected.push(selection),
1533            }
1534            out.push(Output::Selected {
1535                component: *component,
1536                local: base,
1537                local_kind,
1538                remote: *remote,
1539                remote_kind,
1540            });
1541            // §8.1.2's pruning, for the agent that knows there will be no second nomination.
1542            // A controlled agent must not do it: §8.1.1 requires it to tolerate a peer that
1543            // nominates more than once and then "use the pairs with the highest priority", and a
1544            // checklist it has already emptied has nothing left to raise the selection to.
1545            if self.role.is_controlling() {
1546                if let Some(list) = self.set.checklists_mut().get_mut(index) {
1547                    list.keep_only_nominated(*component, *pair);
1548                }
1549                // §8.1.2: "if the state of a pair is In-Progress, the agent cancels the
1550                // In-Progress transaction". A removed pair leaves a transaction behind that
1551                // `retransmit` would happily keep servicing — invisible with one component,
1552                // because the last of them is cleared below, and a live retransmission loop for a
1553                // pair that no longer exists as soon as there are two.
1554                let live: Vec<PairId> = self
1555                    .set
1556                    .checklists()
1557                    .iter()
1558                    .flat_map(|list| list.pairs().iter().map(|pair| pair.id))
1559                    .collect();
1560                for transaction in &self.transactions {
1561                    if !live.contains(&transaction.pair) {
1562                        out.push(Output::ClearTimer(Timer::Retransmit(transaction.pair)));
1563                    }
1564                }
1565                self.transactions
1566                    .retain(|transaction| live.contains(&transaction.pair));
1567            }
1568        }
1569
1570        if chosen.len() == components.len() && !components.is_empty() {
1571            if let Some(list) = self.set.checklists_mut().get_mut(index) {
1572                list.set_state(ChecklistState::Completed);
1573            }
1574            if !self.active() {
1575                for transaction in std::mem::take(&mut self.transactions) {
1576                    out.push(Output::ClearTimer(Timer::Retransmit(transaction.pair)));
1577                }
1578                self.ta_armed = false;
1579                out.push(Output::ClearTimer(Timer::Ta));
1580                out.push(Output::SetTimer {
1581                    timer: Timer::Keepalive,
1582                    after: self.config.timers.tr,
1583                });
1584            }
1585        }
1586    }
1587
1588    /// §7.2.5.4: whether a checklist has finished, one way or the other.
1589    fn update_checklists(&mut self, out: &mut Vec<Output>) {
1590        let mut failed: Vec<ComponentId> = Vec::new();
1591        for list in self.set.checklists_mut() {
1592            if list.state() != ChecklistState::Running {
1593                continue;
1594            }
1595            let components = list.components();
1596            if components.is_empty() {
1597                continue;
1598            }
1599            let settled = list.pairs().iter().all(|pair| pair.state.is_final());
1600            let covered = components.iter().all(|component| {
1601                list.valid()
1602                    .iter()
1603                    .any(|valid| valid.component == *component)
1604            });
1605            if settled && !covered {
1606                list.set_state(ChecklistState::Failed);
1607                for component in components {
1608                    if !list
1609                        .valid()
1610                        .iter()
1611                        .any(|valid| valid.component == component)
1612                    {
1613                        failed.push(component);
1614                    }
1615                }
1616            }
1617        }
1618        for component in failed {
1619            if self.failed.contains(&component) {
1620                continue;
1621            }
1622            self.failed.push(component);
1623            out.push(Output::Failed { component });
1624        }
1625    }
1626}
1627
1628/// A new tiebreaker after a 487 (§7.2.5.1: "the agent MUST change the tiebreaker value").
1629///
1630/// Random, and not a bump of the old value, and that is the whole difficulty of the symmetric
1631/// case. Two agents that both start controlling with the *same* tiebreaker each 487 the other and
1632/// each switch to controlled — and if both derive their new value the same way from the same old
1633/// value, they land on the same new value, compare equal again on the next check, both switch back
1634/// to controlling under §7.3.1.1's `T ≥ V` row, and ping-pong roles until the checklist fails.
1635/// Only an independent draw at each end breaks that symmetry, which is why §7.1.3 makes the
1636/// tiebreaker random in the first place.
1637///
1638/// The redraw on collision is not superstition about the RNG: §7.2.5.1 says the value MUST
1639/// *change*, so a draw that returned the old one would not have satisfied it.
1640fn fresh_tiebreaker(previous: u64) -> u64 {
1641    let mut next: u64 = rand::random();
1642    while next == previous {
1643        next = rand::random();
1644    }
1645    next
1646}
1647
1648#[cfg(test)]
1649#[allow(
1650    clippy::unwrap_used,
1651    clippy::expect_used,
1652    clippy::panic,
1653    clippy::indexing_slicing
1654)]
1655mod tests {
1656    use super::*;
1657
1658    const ALICE: &str = "192.0.2.1:5000";
1659    const BOB: &str = "192.0.2.2:5000";
1660
1661    fn address(text: &str) -> SocketAddr {
1662        text.parse().unwrap()
1663    }
1664
1665    fn credentials(ufrag: &str) -> Credentials {
1666        Credentials::new(ufrag, format!("{ufrag}xxxxxxxxxxxxxxxxxxxxxxxx")).unwrap()
1667    }
1668
1669    fn host(address: SocketAddr) -> Gathered {
1670        Gathered {
1671            base: LocalBase(0),
1672            base_address: address,
1673            address,
1674            kind: CandidateType::Host,
1675            component: ComponentId::RTP,
1676            server: None,
1677        }
1678    }
1679
1680    fn host_line(address: SocketAddr, foundation: &str) -> Candidate {
1681        Candidate::parse(&format!(
1682            // A priority of its own, so that a pair's `G` and `D` differ and §6.1.2.3's
1683            // recomputation on a role change is visible.
1684            "{foundation} 1 UDP 1694498815 {} {} typ host",
1685            address.ip(),
1686            address.port()
1687        ))
1688        .unwrap()
1689    }
1690
1691    /// What a driver does with Ta: fire it when, and only when, the agent has armed one.
1692    ///
1693    /// A test that fires Ta by hand cannot tell an armed timer from an invented one, and that is
1694    /// exactly the gap this type closes — a triggered check enqueued after a checklist concluded
1695    /// is queued for a tick a real driver would never deliver.
1696    #[derive(Debug, Default)]
1697    struct Driver {
1698        armed: bool,
1699    }
1700
1701    impl Driver {
1702        /// Deliver the Ta tick the agent asked for. A one-shot timer that has fired is no longer
1703        /// armed, so only the agent's own `SetTimer` can arm the next one.
1704        fn tick(&mut self, agent: &mut Agent) -> Vec<Output> {
1705            self.armed = false;
1706            let outputs = agent.handle(Input::TimerFired(Timer::Ta));
1707            self.absorb(&outputs);
1708            outputs
1709        }
1710
1711        fn absorb(&mut self, outputs: &[Output]) {
1712            for output in outputs {
1713                match output {
1714                    Output::SetTimer {
1715                        timer: Timer::Ta, ..
1716                    } => self.armed = true,
1717                    Output::ClearTimer(Timer::Ta) => self.armed = false,
1718                    _ => {}
1719                }
1720            }
1721        }
1722    }
1723
1724    fn two_agents(
1725        offerer: (bool, bool),
1726        tiebreakers: (u64, u64),
1727    ) -> (Agent, Agent, Driver, Driver) {
1728        let (alice_address, bob_address) = (address(ALICE), address(BOB));
1729        let mut alice = Agent::new(
1730            Config::default(),
1731            offerer.0,
1732            credentials("aaaa"),
1733            tiebreakers.0,
1734        );
1735        let mut bob = Agent::new(
1736            Config::default(),
1737            offerer.1,
1738            credentials("bbbb"),
1739            tiebreakers.1,
1740        );
1741        let (mut left, mut right) = (Driver::default(), Driver::default());
1742        left.absorb(&alice.handle(Input::LocalCandidate(host(alice_address))));
1743        right.absorb(&bob.handle(Input::LocalCandidate(host(bob_address))));
1744        left.absorb(&alice.handle(Input::RemoteDescription {
1745            credentials: credentials("bbbb"),
1746            candidates: vec![host_line(bob_address, "1")],
1747            lite: false,
1748        }));
1749        right.absorb(&bob.handle(Input::RemoteDescription {
1750            credentials: credentials("aaaa"),
1751            candidates: vec![host_line(alice_address, "1")],
1752            lite: false,
1753        }));
1754        left.absorb(&alice.handle(Input::GatheringDone));
1755        right.absorb(&bob.handle(Input::GatheringDone));
1756        (alice, bob, left, right)
1757    }
1758
1759    /// Two agents wired to each other, each believing it sent the initial offer and each holding
1760    /// `tiebreaker` — which is §7.3.1.1's `T = V` row, the one that decides whether two copies of
1761    /// the same stack converge.
1762    fn both_controlling(tiebreaker: u64) -> (Agent, Agent, Driver, Driver) {
1763        two_agents((true, true), (tiebreaker, tiebreaker))
1764    }
1765
1766    /// Run `rounds` Ta ticks at both ends — but only at an end that has a Ta armed — carrying
1767    /// every datagram one produces to the other and following the exchange until it goes quiet.
1768    /// No clock, no socket: the "network" is this function, which is the point of the sans-IO
1769    /// shape.
1770    fn exchange(
1771        a: &mut Agent,
1772        b: &mut Agent,
1773        left: &mut Driver,
1774        right: &mut Driver,
1775        rounds: usize,
1776    ) {
1777        let (alice, bob) = (address(ALICE), address(BOB));
1778        for _ in 0..rounds {
1779            let mut pending: Vec<(bool, Vec<u8>)> = Vec::new();
1780            if left.armed {
1781                for output in left.tick(a) {
1782                    if let Output::Send { bytes, .. } = output {
1783                        pending.push((true, bytes));
1784                    }
1785                }
1786            }
1787            if right.armed {
1788                for output in right.tick(b) {
1789                    if let Output::Send { bytes, .. } = output {
1790                        pending.push((false, bytes));
1791                    }
1792                }
1793            }
1794            for _ in 0..8 {
1795                let mut next: Vec<(bool, Vec<u8>)> = Vec::new();
1796                for (to_bob, bytes) in pending {
1797                    let (target, driver, from) = if to_bob {
1798                        (&mut *b, &mut *right, alice)
1799                    } else {
1800                        (&mut *a, &mut *left, bob)
1801                    };
1802                    let outputs = target.handle(Input::Datagram {
1803                        from,
1804                        on: LocalBase(0),
1805                        bytes,
1806                    });
1807                    driver.absorb(&outputs);
1808                    for output in outputs {
1809                        if let Output::Send { bytes, .. } = output {
1810                            next.push((!to_bob, bytes));
1811                        }
1812                    }
1813                }
1814                if next.is_empty() {
1815                    break;
1816                }
1817                pending = next;
1818            }
1819        }
1820    }
1821
1822    fn agent(offerer: bool, tiebreaker: u64, remotes: &[SocketAddr]) -> Agent {
1823        let mut agent = Agent::new(Config::default(), offerer, credentials("aaaa"), tiebreaker);
1824        agent.handle(Input::LocalCandidate(host(address(ALICE))));
1825        agent.handle(Input::RemoteDescription {
1826            credentials: credentials("bbbb"),
1827            candidates: remotes
1828                .iter()
1829                .enumerate()
1830                .map(|(index, remote)| host_line(*remote, &(index + 1).to_string()))
1831                .collect(),
1832            lite: false,
1833        });
1834        agent.handle(Input::GatheringDone);
1835        agent
1836    }
1837
1838    /// The peer's view of the credential pair. Its `outbound_*` is what a check arriving at our
1839    /// agent must carry, which is the direction rule `Peering` exists to keep straight.
1840    fn peer() -> Peering {
1841        Peering::new(credentials("bbbb"), credentials("aaaa"))
1842    }
1843
1844    fn sent(outputs: &[Output]) -> Vec<Message> {
1845        outputs
1846            .iter()
1847            .filter_map(|output| match output {
1848                Output::Send { bytes, .. } => Message::decode(bytes).ok(),
1849                _ => None,
1850            })
1851            .collect()
1852    }
1853
1854    fn requests(outputs: &[Output]) -> Vec<Message> {
1855        sent(outputs)
1856            .into_iter()
1857            .filter(|message| message.class() == Class::Request)
1858            .collect()
1859    }
1860
1861    fn retransmit_after(outputs: &[Output]) -> Option<Duration> {
1862        outputs.iter().find_map(|output| match output {
1863            Output::SetTimer {
1864                timer: Timer::Retransmit(_),
1865                after,
1866            } => Some(*after),
1867            _ => None,
1868        })
1869    }
1870
1871    /// A check from the peer, with whatever role attribute the row under test needs.
1872    fn peer_check(role: RoleAttribute) -> Vec<u8> {
1873        stun::connectivity_check(
1874            stun::new_transaction_id(),
1875            &peer(),
1876            Priority::new(1_862_270_975).unwrap(),
1877            role,
1878        )
1879        .unwrap()
1880    }
1881
1882    fn deliver(agent: &mut Agent, from: SocketAddr, bytes: Vec<u8>) -> Vec<Output> {
1883        agent.handle(Input::Datagram {
1884            from,
1885            on: LocalBase(0),
1886            bytes,
1887        })
1888    }
1889
1890    // --------------------------------------------------------------------------- sans-IO
1891
1892    /// [spec] §2 and the working agreement: no runtime, no socket, no clock read. Asserted on the
1893    /// source rather than on behaviour, because the failure mode is a single `use` line that
1894    /// nothing else in this crate would notice — `sipx-media` legitimately depends on `tokio` for
1895    /// the driver and the session, so a compile-time barrier is not available here.
1896    ///
1897    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
1898    #[test]
1899    fn the_agent_reads_no_clock_and_owns_no_socket() {
1900        // Comments are stripped — these modules explain the constraint in the words the
1901        // constraint forbids — but everything else is scanned, including anything below the test
1902        // module. A scan that stopped at the first `#[cfg(test)]` would not have looked at
1903        // library code written after it.
1904        let code = |source: &str| -> String {
1905            source
1906                .lines()
1907                .filter(|line| !line.trim_start().starts_with("//"))
1908                .collect::<Vec<_>>()
1909                .join("\n")
1910        };
1911        // Spelled in fragments so that this list is not itself a match for the scan it drives.
1912        let forbidden = [
1913            ["tok", "io"].concat(),
1914            ["Udp", "Socket"].concat(),
1915            ["Ins", "tant"].concat(),
1916            ["System", "Time"].concat(),
1917            ["std::", "thread"].concat(),
1918        ];
1919        for source in [
1920            code(include_str!("agent.rs")),
1921            code(include_str!("checklist.rs")),
1922            code(include_str!("candidate.rs")),
1923            code(include_str!("timing.rs")),
1924        ] {
1925            for forbidden in &forbidden {
1926                assert!(
1927                    !source.contains(forbidden),
1928                    "the ICE agent must not reach for {forbidden}: time arrives as TimerFired \
1929                     and datagrams arrive as bytes"
1930                );
1931            }
1932        }
1933    }
1934
1935    // ------------------------------------------------------------------- §7.3.1.1, row by row
1936
1937    /// [spec] §7.3's table, all seven rows, each its own assertion.
1938    ///
1939    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
1940    #[test]
1941    fn the_role_conflict_table_is_walked_row_by_row() {
1942        let controlling = |tiebreaker: u64| RoleAttribute::Controlling {
1943            tiebreaker,
1944            nominate: false,
1945        };
1946        let controlled = |tiebreaker: u64| RoleAttribute::Controlled { tiebreaker };
1947
1948        // Row 1: controlling, ICE-CONTROLLING, T > V — 487 and keep controlling.
1949        let mut subject = agent(true, 100, &[address(BOB)]);
1950        assert_eq!(
1951            subject.resolve_conflict(Some(controlling(50))),
1952            Conflict::Reject
1953        );
1954        assert_eq!(subject.role(), Role::Controlling);
1955
1956        // Row 1 again at T = V. `>=` and not `>` is the whole point: with equal tiebreakers
1957        // neither side may switch on the request, or they simply swap roles.
1958        let mut subject = agent(true, 100, &[address(BOB)]);
1959        assert_eq!(
1960            subject.resolve_conflict(Some(controlling(100))),
1961            Conflict::Reject
1962        );
1963        assert_eq!(subject.role(), Role::Controlling);
1964
1965        // Row 2: controlling, ICE-CONTROLLING, T < V — switch to controlled, answer normally.
1966        let mut subject = agent(true, 100, &[address(BOB)]);
1967        assert_eq!(
1968            subject.resolve_conflict(Some(controlling(200))),
1969            Conflict::Switched
1970        );
1971        assert_eq!(subject.role(), Role::Controlled);
1972
1973        // Row 3: controlled, ICE-CONTROLLED, T >= V — switch to controlling, answer normally.
1974        let mut subject = agent(false, 100, &[address(BOB)]);
1975        assert_eq!(
1976            subject.resolve_conflict(Some(controlled(100))),
1977            Conflict::Switched
1978        );
1979        assert_eq!(subject.role(), Role::Controlling);
1980
1981        // Row 4: controlled, ICE-CONTROLLED, T < V — 487 and keep controlled.
1982        let mut subject = agent(false, 100, &[address(BOB)]);
1983        assert_eq!(
1984            subject.resolve_conflict(Some(controlled(200))),
1985            Conflict::Reject
1986        );
1987        assert_eq!(subject.role(), Role::Controlled);
1988
1989        // Row 5: controlling, ICE-CONTROLLED — no conflict.
1990        let mut subject = agent(true, 100, &[address(BOB)]);
1991        assert_eq!(
1992            subject.resolve_conflict(Some(controlled(200))),
1993            Conflict::None
1994        );
1995        assert_eq!(subject.role(), Role::Controlling);
1996
1997        // Row 6: controlled, ICE-CONTROLLING — no conflict.
1998        let mut subject = agent(false, 100, &[address(BOB)]);
1999        assert_eq!(
2000            subject.resolve_conflict(Some(controlling(200))),
2001            Conflict::None
2002        );
2003        assert_eq!(subject.role(), Role::Controlled);
2004
2005        // Row 7: neither attribute — no conflict; the peer is not doing role signalling.
2006        let mut subject = agent(true, 100, &[address(BOB)]);
2007        assert_eq!(subject.resolve_conflict(None), Conflict::None);
2008        assert_eq!(subject.role(), Role::Controlling);
2009    }
2010
2011    /// The rejecting rows put a 487 on the wire and answer nothing else.
2012    #[test]
2013    fn a_rejected_role_conflict_answers_487_and_not_a_success() {
2014        let mut subject = agent(true, 100, &[address(BOB)]);
2015        let outputs = deliver(
2016            &mut subject,
2017            address(BOB),
2018            peer_check(RoleAttribute::Controlling {
2019                tiebreaker: 100,
2020                nominate: false,
2021            }),
2022        );
2023        let answers = sent(&outputs);
2024        assert_eq!(answers.len(), 1);
2025        assert_eq!(answers[0].class(), Class::Error);
2026        assert_eq!(answers[0].error_code(), Some(stun::ROLE_CONFLICT));
2027        assert_eq!(subject.role(), Role::Controlling);
2028    }
2029
2030    /// …and the switching rows answer normally, because §7.3.1's remaining processing "[is]
2031    /// followed if the agent generated a successful response, even if the agent changed roles".
2032    #[test]
2033    fn a_switched_role_still_answers_the_check_that_caused_it() {
2034        let mut subject = agent(true, 100, &[address(BOB)]);
2035        let outputs = deliver(
2036            &mut subject,
2037            address(BOB),
2038            peer_check(RoleAttribute::Controlling {
2039                tiebreaker: 200,
2040                nominate: false,
2041            }),
2042        );
2043        let answers = sent(&outputs);
2044        assert_eq!(answers.len(), 1);
2045        assert_eq!(answers[0].class(), Class::Success);
2046        assert_eq!(subject.role(), Role::Controlled);
2047    }
2048
2049    /// §7.2.5.1, every clause of it: switch to the role opposite the attribute that went out,
2050    /// change the tiebreaker, recompute every pair priority, and re-run the check as a triggered
2051    /// one so the new role goes out immediately.
2052    #[test]
2053    fn a_487_switches_the_role_changes_the_tiebreaker_and_re_runs_the_check() {
2054        let mut subject = agent(true, 100, &[address(BOB)]);
2055        let check = subject.handle(Input::TimerFired(Timer::Ta));
2056        let outgoing = requests(&check);
2057        assert_eq!(outgoing.len(), 1);
2058        let transaction = outgoing[0].transaction();
2059        let before = subject.checklists().checklists()[0].pairs()[0].priority;
2060        let pair = subject.checklists().checklists()[0].pairs()[0].id;
2061        assert_eq!(
2062            subject.checklists().pair(pair).unwrap().state,
2063            PairState::InProgress
2064        );
2065
2066        let rejection = stun::role_conflict(transaction, &peer()).unwrap();
2067        subject.handle(Input::Datagram {
2068            from: address(BOB),
2069            on: LocalBase(0),
2070            bytes: rejection,
2071        });
2072
2073        assert_eq!(subject.role(), Role::Controlled);
2074        assert_ne!(subject.tiebreaker(), 100);
2075        assert_ne!(
2076            subject.checklists().checklists()[0].pairs()[0].priority,
2077            before,
2078            "a role switch swaps G and D, so every pair priority moves"
2079        );
2080        assert_eq!(
2081            subject.checklists().pair(pair).unwrap().state,
2082            PairState::Waiting
2083        );
2084        assert!(subject.checklists().checklists()[0].is_triggered(pair));
2085
2086        // And the re-run carries the new role.
2087        let rerun = subject.handle(Input::TimerFired(Timer::Ta));
2088        let rerun = requests(&rerun);
2089        assert_eq!(rerun.len(), 1);
2090        assert!(matches!(
2091            rerun[0].role(),
2092            Some(RoleAttribute::Controlled { .. })
2093        ));
2094    }
2095
2096    // ------------------------------------------------------------------------------- §7.1.1
2097
2098    /// §7.1.1: the `PRIORITY` in a check is the candidate's priority recomputed with the
2099    /// peer-reflexive type preference. Get this wrong and the peer prices the peer-reflexive
2100    /// candidate it learns from this very check differently from us.
2101    #[test]
2102    fn a_check_carries_the_peer_reflexive_priority_not_the_candidates_own() {
2103        let mut subject = agent(true, 100, &[address(BOB)]);
2104        let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2105        let check = &requests(&outputs)[0];
2106        let candidate = subject.local_candidates()[0];
2107        assert_eq!(candidate.priority.get(), 2_130_706_431);
2108        assert_eq!(check.priority(), Some(candidate.check_priority()));
2109        assert_eq!(check.priority().unwrap().get(), 1_862_270_975);
2110        assert_ne!(check.priority(), Some(candidate.priority));
2111    }
2112
2113    // -------------------------------------------------------------- peer-reflexive candidates
2114
2115    /// §7.3.1.3: a check from an address no `a=candidate` named is a peer-reflexive *remote*
2116    /// candidate, priced from the `PRIORITY` the check carried.
2117    #[test]
2118    fn a_check_from_an_unknown_address_teaches_a_remote_candidate() {
2119        let mut subject = agent(true, 100, &[address(BOB)]);
2120        assert_eq!(subject.remote_candidates().len(), 1);
2121        let behind_a_nat = address("198.51.100.7:41234");
2122        deliver(
2123            &mut subject,
2124            behind_a_nat,
2125            peer_check(RoleAttribute::Controlled { tiebreaker: 1 }),
2126        );
2127        let learned = subject
2128            .remote_candidates()
2129            .iter()
2130            .find(|candidate| candidate.address == behind_a_nat)
2131            .expect("§7.3.1.3 learns the source of an unmatched check");
2132        assert_eq!(learned.kind, CandidateType::PeerReflexive);
2133        assert_eq!(learned.priority.get(), 1_862_270_975);
2134        assert_eq!(learned.component, ComponentId::RTP);
2135    }
2136
2137    /// §7.2.5.3.1: a mapped address that is not one of our local candidates is a peer-reflexive
2138    /// *local* candidate, and its priority is the `PRIORITY` we put in the request — not
2139    /// something recomputed, or the two ends disagree.
2140    #[test]
2141    fn a_mapped_address_we_do_not_have_teaches_a_local_candidate() {
2142        let mut subject = agent(true, 100, &[address(BOB)]);
2143        let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2144        let transaction = requests(&outputs)[0].transaction();
2145        let reflexive = address("198.51.100.4:33445");
2146        let response = stun::check_success(transaction, &peer(), reflexive).unwrap();
2147        deliver(&mut subject, address(BOB), response);
2148
2149        let learned = subject
2150            .local_candidates()
2151            .iter()
2152            .find(|candidate| candidate.gathered.address == reflexive)
2153            .expect("§7.2.5.3.1 learns the mapped address");
2154        assert_eq!(learned.gathered.kind, CandidateType::PeerReflexive);
2155        assert_eq!(learned.priority.get(), 1_862_270_975);
2156        assert_eq!(learned.gathered.base_address, address(ALICE));
2157    }
2158
2159    // ---------------------------------------------------------------------- triggered checks
2160
2161    /// §7.3.1.4: a triggered check jumps the queue, whatever the priorities say. The peer's
2162    /// low-priority path is checked before our own highest-priority `Waiting` pair.
2163    #[test]
2164    fn a_triggered_check_preempts_the_highest_priority_waiting_pair() {
2165        let low = address("198.51.100.8:40000");
2166        let mut subject = agent(true, 100, &[address(BOB), low]);
2167        // The peer checks us from an address that is not even in the checklist yet.
2168        let surprise = address("198.51.100.9:41000");
2169        deliver(
2170            &mut subject,
2171            surprise,
2172            peer_check(RoleAttribute::Controlled { tiebreaker: 1 }),
2173        );
2174
2175        let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2176        let addressed: Vec<SocketAddr> = outputs
2177            .iter()
2178            .filter_map(|output| match output {
2179                Output::Send { to, bytes, .. } if Message::decode(bytes).is_ok() => Some(*to),
2180                _ => None,
2181            })
2182            .collect();
2183        assert_eq!(
2184            addressed,
2185            vec![surprise],
2186            "the triggered check goes first, ahead of every Waiting pair"
2187        );
2188    }
2189
2190    /// Nothing in the machine is a literal: a deployment that halves Ta gets checks at half the
2191    /// interval, and one that lowers §6.1.2.5's limit gets a smaller checklist set.
2192    #[test]
2193    fn the_timers_and_the_pair_limit_are_the_configured_ones() {
2194        let config = Config {
2195            timers: Timers {
2196                ta: Duration::from_millis(20),
2197                ..Timers::default()
2198            },
2199            pair_limit: 3,
2200        };
2201        let remotes: Vec<SocketAddr> = (1..=8)
2202            .map(|n| address(&format!("198.51.100.{n}:5000")))
2203            .collect();
2204        let mut subject = Agent::new(config, true, credentials("aaaa"), 100);
2205        subject.handle(Input::LocalCandidate(host(address(ALICE))));
2206        subject.handle(Input::RemoteDescription {
2207            credentials: credentials("bbbb"),
2208            candidates: remotes
2209                .iter()
2210                .enumerate()
2211                .map(|(index, remote)| host_line(*remote, &(index + 1).to_string()))
2212                .collect(),
2213            lite: false,
2214        });
2215        let started = subject.handle(Input::GatheringDone);
2216
2217        assert_eq!(subject.checklists().total_pairs(), 3);
2218        assert!(started.contains(&Output::SetTimer {
2219            timer: Timer::Ta,
2220            after: Duration::from_millis(20),
2221        }));
2222
2223        let tick = subject.handle(Input::TimerFired(Timer::Ta));
2224        assert!(tick.contains(&Output::SetTimer {
2225            timer: Timer::Ta,
2226            after: Duration::from_millis(20),
2227        }));
2228    }
2229
2230    // ------------------------------------------------------------------------- §7.2.5.2.1
2231
2232    /// §7.2.5.2.1: "the source IP address and port of the response MUST be equal to the
2233    /// destination … to which the Binding request was sent". A response from anywhere else fails
2234    /// the pair, however well formed and however well authenticated it is.
2235    #[test]
2236    fn a_response_from_the_wrong_address_fails_the_pair() {
2237        let mut subject = agent(true, 100, &[address(BOB)]);
2238        let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2239        let transaction = requests(&outputs)[0].transaction();
2240        let pair = subject.checklists().checklists()[0].pairs()[0].id;
2241
2242        let response = stun::check_success(transaction, &peer(), address(ALICE)).unwrap();
2243        deliver(&mut subject, address("198.51.100.66:5000"), response);
2244
2245        assert_eq!(
2246            subject.checklists().pair(pair).unwrap().state,
2247            PairState::Failed
2248        );
2249        assert!(subject.checklists().checklists()[0].valid().is_empty());
2250    }
2251
2252    /// …and an unauthenticated response moves nothing at all, not even into Failed — otherwise
2253    /// anyone who can see a check can fail every pair by answering it ([spec] §11.3).
2254    ///
2255    /// [spec]: https://github.com/codewandler/sipx/blob/main/docs/specs/ice.md
2256    #[test]
2257    fn a_response_with_the_wrong_credential_moves_no_state() {
2258        let mut subject = agent(true, 100, &[address(BOB)]);
2259        let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2260        let transaction = requests(&outputs)[0].transaction();
2261        let pair = subject.checklists().checklists()[0].pairs()[0].id;
2262
2263        // Keyed with a password that is not ours: `check_success` keys a response with the
2264        // responder's own credential, which is what our `outbound_key` has to match.
2265        let forged = Peering::new(credentials("zzzz"), credentials("aaaa"));
2266        let response = stun::check_success(transaction, &forged, address(ALICE)).unwrap();
2267        let answered = deliver(&mut subject, address(BOB), response);
2268
2269        assert!(answered.is_empty());
2270        assert_eq!(
2271            subject.checklists().pair(pair).unwrap().state,
2272            PairState::InProgress
2273        );
2274    }
2275
2276    // ------------------------------------------------------------------------------- §14.3
2277
2278    /// §14.3: "the RTO will be different for each transaction as the number of checks in the
2279    /// Waiting and In-Progress states change", so it is computed when a check is sent.
2280    #[test]
2281    fn the_rto_is_recomputed_for_every_transaction() {
2282        let remotes: Vec<SocketAddr> = (1..=5)
2283            .map(|n| address(&format!("198.51.100.{n}:5000")))
2284            .collect();
2285        // Controlled, so that a success does not immediately start nominating and change what is
2286        // outstanding for a second reason.
2287        let mut subject = agent(false, 100, &remotes);
2288
2289        let first = subject.handle(Input::TimerFired(Timer::Ta));
2290        let first_rto = retransmit_after(&first).expect("a check arms its retransmission timer");
2291        let transaction = requests(&first)[0].transaction();
2292        let destination = match first.first() {
2293            Some(Output::Send { to, .. }) => *to,
2294            other => panic!("expected a check, got {other:?}"),
2295        };
2296
2297        let response = stun::check_success(transaction, &peer(), address(ALICE)).unwrap();
2298        deliver(&mut subject, destination, response);
2299
2300        let second = subject.handle(Input::TimerFired(Timer::Ta));
2301        let second_rto = retransmit_after(&second).expect("the next check arms its own");
2302        assert!(
2303            second_rto < first_rto,
2304            "one fewer outstanding check must shorten the RTO: {second_rto:?} vs {first_rto:?}"
2305        );
2306    }
2307
2308    /// RFC 5389 §7.2.1: Rc transmissions, doubling, then a final wait of Rm times the RTO, and
2309    /// only then is the pair Failed.
2310    #[test]
2311    fn a_check_is_retransmitted_rc_times_before_the_pair_fails() {
2312        let mut subject = agent(true, 100, &[address(BOB)]);
2313        let first = subject.handle(Input::TimerFired(Timer::Ta));
2314        let pair = subject.checklists().checklists()[0].pairs()[0].id;
2315        let mut interval = retransmit_after(&first).unwrap();
2316
2317        // Transmissions 2..=Rc, each after twice the last interval.
2318        for _ in 1..Config::default().timers.rc {
2319            let outputs = subject.handle(Input::TimerFired(Timer::Retransmit(pair)));
2320            assert_eq!(requests(&outputs).len(), 1, "a retransmission is a resend");
2321            let next = retransmit_after(&outputs).unwrap();
2322            assert_eq!(next, interval * 2);
2323            interval = next;
2324            assert_eq!(
2325                subject.checklists().pair(pair).unwrap().state,
2326                PairState::InProgress
2327            );
2328        }
2329
2330        // The final wait: Rm times the transaction's first RTO, and no further request.
2331        let last = subject.handle(Input::TimerFired(Timer::Retransmit(pair)));
2332        assert!(requests(&last).is_empty());
2333        let timers = Config::default().timers;
2334        assert_eq!(
2335            retransmit_after(&last),
2336            Some(timers.final_wait(retransmit_after(&first).unwrap()))
2337        );
2338        assert_eq!(
2339            subject.checklists().pair(pair).unwrap().state,
2340            PairState::InProgress
2341        );
2342
2343        // And now it has timed out (§7.2.5.2.3).
2344        let done = subject.handle(Input::TimerFired(Timer::Retransmit(pair)));
2345        assert_eq!(
2346            subject.checklists().pair(pair).unwrap().state,
2347            PairState::Failed
2348        );
2349        assert!(
2350            done.iter()
2351                .any(|output| matches!(output, Output::Failed { .. })),
2352            "the only pair failed, so the component failed"
2353        );
2354    }
2355
2356    // -------------------------------------------------------------------------- nomination
2357
2358    /// Two agents that agree on their roles converge on a selected pair for the component, at
2359    /// both ends, by §8.1.1's regular nomination.
2360    #[test]
2361    fn two_agents_converge_on_a_selected_pair() {
2362        let (alice_address, bob_address) = (address(ALICE), address(BOB));
2363        let (mut alice, mut bob, mut left, mut right) = two_agents((true, false), (900, 100));
2364        assert_eq!(alice.role(), Role::Controlling);
2365        assert_eq!(bob.role(), Role::Controlled);
2366
2367        exchange(&mut alice, &mut bob, &mut left, &mut right, 6);
2368
2369        assert_eq!(
2370            alice.selected(ComponentId::RTP),
2371            Some((LocalBase(0), bob_address))
2372        );
2373        assert_eq!(
2374            bob.selected(ComponentId::RTP),
2375            Some((LocalBase(0), alice_address))
2376        );
2377        assert_eq!(
2378            alice.checklists().checklists()[0].state(),
2379            ChecklistState::Completed
2380        );
2381        // And both ends fall quiet. §8.1.2 stops an agent generating triggered checks for a
2382        // concluded pair, without which each end's redundant check finds the other's pair
2383        // In-Progress, §7.3.1.4 re-enqueues it, and two agreeing agents check each other for the
2384        // life of the call.
2385        assert!(!left.armed && !right.armed);
2386    }
2387
2388    /// A §7.3.1.4 check for an address first seen *after* the checklist concluded has to be able
2389    /// to leave. `conclude` clears Ta and `pace` re-arms only while there is work, so without an
2390    /// arming of its own the triggered check is enqueued for a tick a driver would never deliver
2391    /// — and the same silence swallows §8.1.1's tolerance clause below.
2392    #[test]
2393    fn a_check_arriving_after_the_checklist_concluded_still_arms_ta() {
2394        let (mut alice, mut bob, mut left, mut right) = two_agents((true, false), (900, 100));
2395        // Long enough for both ends to conclude and for the pacing to fall quiet.
2396        exchange(&mut alice, &mut bob, &mut left, &mut right, 12);
2397        assert_eq!(
2398            bob.checklists().checklists()[0].state(),
2399            ChecklistState::Completed
2400        );
2401        assert!(!right.armed, "a concluded checklist stops pacing");
2402
2403        // The peer checks from an address ICE has never seen — a NAT rebinding, say.
2404        let surprise = address("198.51.100.77:41000");
2405        let check = stun::connectivity_check(
2406            stun::new_transaction_id(),
2407            &Peering::new(credentials("aaaa"), credentials("bbbb")),
2408            Priority::new(1_862_270_975).unwrap(),
2409            RoleAttribute::Controlling {
2410                tiebreaker: 900,
2411                nominate: false,
2412            },
2413        )
2414        .unwrap();
2415        let outputs = bob.handle(Input::Datagram {
2416            from: surprise,
2417            on: LocalBase(0),
2418            bytes: check,
2419        });
2420        right.absorb(&outputs);
2421        assert!(
2422            right.armed,
2423            "the triggered check §7.3.1.4 just enqueued needs a Ta tick to leave"
2424        );
2425
2426        let tick = right.tick(&mut bob);
2427        let addressed: Vec<SocketAddr> = tick
2428            .iter()
2429            .filter_map(|output| match output {
2430                Output::Send { to, bytes, .. } if Message::decode(bytes).is_ok() => Some(*to),
2431                _ => None,
2432            })
2433            .collect();
2434        assert_eq!(addressed, vec![surprise]);
2435    }
2436
2437    /// §8.1.1: "the agent MUST NOT nominate another pair for [the] same component … within the
2438    /// ICE session". One `USE-CANDIDATE` leaves this agent, ever.
2439    #[test]
2440    fn the_controlling_agent_nominates_a_component_exactly_once() {
2441        let (alice_address, bob_address) = (address(ALICE), address(BOB));
2442        let mut alice = Agent::new(Config::default(), true, credentials("aaaa"), 900);
2443        let mut bob = Agent::new(Config::default(), false, credentials("bbbb"), 100);
2444        alice.handle(Input::LocalCandidate(host(alice_address)));
2445        bob.handle(Input::LocalCandidate(host(bob_address)));
2446        alice.handle(Input::RemoteDescription {
2447            credentials: credentials("bbbb"),
2448            candidates: vec![host_line(bob_address, "1")],
2449            lite: false,
2450        });
2451        bob.handle(Input::RemoteDescription {
2452            credentials: credentials("aaaa"),
2453            candidates: vec![host_line(alice_address, "1")],
2454            lite: false,
2455        });
2456        alice.handle(Input::GatheringDone);
2457        bob.handle(Input::GatheringDone);
2458
2459        let mut nominations = 0usize;
2460        for _ in 0..10 {
2461            let outputs = alice.handle(Input::TimerFired(Timer::Ta));
2462            for message in requests(&outputs) {
2463                if message.use_candidate() {
2464                    nominations += 1;
2465                }
2466            }
2467            for output in outputs {
2468                if let Output::Send { bytes, .. } = output {
2469                    for answer in bob.handle(Input::Datagram {
2470                        from: alice_address,
2471                        on: LocalBase(0),
2472                        bytes,
2473                    }) {
2474                        if let Output::Send { bytes, .. } = answer {
2475                            alice.handle(Input::Datagram {
2476                                from: bob_address,
2477                                on: LocalBase(0),
2478                                bytes,
2479                            });
2480                        }
2481                    }
2482                }
2483            }
2484        }
2485        assert_eq!(nominations, 1, "regular nomination nominates once");
2486    }
2487
2488    /// §7.1.2 makes `USE-CANDIDATE` the controlling agent's alone, and the type system makes it
2489    /// unsendable by a controlled one — [`RoleAttribute::Controlled`] has no `nominate`. This
2490    /// walks a whole controlled session to show that nothing routes round that.
2491    #[test]
2492    fn a_controlled_agent_never_sends_use_candidate() {
2493        let mut subject = agent(false, 100, &[address(BOB)]);
2494        let mut seen = 0usize;
2495        for _ in 0..6 {
2496            let outputs = subject.handle(Input::TimerFired(Timer::Ta));
2497            for message in requests(&outputs) {
2498                assert!(!message.use_candidate());
2499                assert!(matches!(
2500                    message.role(),
2501                    Some(RoleAttribute::Controlled { .. })
2502                ));
2503                seen += 1;
2504            }
2505            // Answer everything, so the session actually gets somewhere.
2506            for message in requests(&outputs) {
2507                let response =
2508                    stun::check_success(message.transaction(), &peer(), address(ALICE)).unwrap();
2509                deliver(&mut subject, address(BOB), response);
2510            }
2511        }
2512        assert!(seen > 0, "the controlled agent still sends checks");
2513    }
2514
2515    /// §8.1.1's tolerance clause: a peer implemented against RFC 5245 may nominate more than
2516    /// once, and "the agents MUST produce the selected pairs and use the pairs with the highest
2517    /// priority". Tolerating a legacy peer is not the same as being one.
2518    ///
2519    /// Ta is fired only when the agent has armed one, because the interesting half of this is
2520    /// that the second nomination arrives *after* the checklist concluded and its triggered check
2521    /// therefore has to arm a tick of its own. A test that fires Ta by hand passes without that.
2522    #[test]
2523    fn a_peer_that_nominates_twice_selects_the_highest_priority_nominated_pair() {
2524        let low = address("198.51.100.3:6000");
2525        let high = address("198.51.100.2:6000");
2526        let mut subject = Agent::new(Config::default(), false, credentials("aaaa"), 100);
2527        let mut driver = Driver::default();
2528        driver.absorb(&subject.handle(Input::LocalCandidate(host(address(ALICE)))));
2529        driver.absorb(&subject.handle(Input::RemoteDescription {
2530            credentials: credentials("bbbb"),
2531            candidates: vec![
2532                Candidate::parse(&format!(
2533                    "1 1 UDP 1000 {} {} typ host",
2534                    low.ip(),
2535                    low.port()
2536                ))
2537                .unwrap(),
2538                Candidate::parse(&format!(
2539                    "2 1 UDP 2130706431 {} {} typ host",
2540                    high.ip(),
2541                    high.port()
2542                ))
2543                .unwrap(),
2544            ],
2545            lite: false,
2546        }));
2547        driver.absorb(&subject.handle(Input::GatheringDone));
2548
2549        // The peer nominates the low-priority path first, then the high-priority one.
2550        for remote in [low, high] {
2551            let nominating = stun::connectivity_check(
2552                stun::new_transaction_id(),
2553                &peer(),
2554                Priority::new(1_862_270_975).unwrap(),
2555                RoleAttribute::Controlling {
2556                    tiebreaker: 999,
2557                    nominate: true,
2558                },
2559            )
2560            .unwrap();
2561            driver.absorb(&deliver(&mut subject, remote, nominating));
2562
2563            let mut rounds = 0;
2564            while driver.armed && rounds < 10 {
2565                rounds += 1;
2566                let outputs = driver.tick(&mut subject);
2567                let destinations: Vec<(SocketAddr, TransactionId)> = outputs
2568                    .iter()
2569                    .filter_map(|output| match output {
2570                        Output::Send { to, bytes, .. } => Message::decode(bytes)
2571                            .ok()
2572                            .filter(|message| message.class() == Class::Request)
2573                            .map(|message| (*to, message.transaction())),
2574                        _ => None,
2575                    })
2576                    .collect();
2577                for (to, transaction) in destinations {
2578                    let response =
2579                        stun::check_success(transaction, &peer(), address(ALICE)).unwrap();
2580                    driver.absorb(&deliver(&mut subject, to, response));
2581                }
2582            }
2583        }
2584
2585        assert_eq!(
2586            subject.selected(ComponentId::RTP),
2587            Some((LocalBase(0), high)),
2588            "§8.1.1: use the pair with the highest priority among the nominated ones"
2589        );
2590    }
2591
2592    // ----------------------------------------------------------- §6.1.2.5 on the learning path
2593
2594    /// §6.1.2.5's limit is a MUST and §19.5.1 is the attack it names. It binds at formation *and*
2595    /// on §7.3.1.4's insertion path, which is the one a peer drives: without it, each
2596    /// authenticated check from a fresh source address buys the sender a remote candidate, a
2597    /// pair, and eventually an 88-byte connectivity check sent to an address it named and need
2598    /// not be able to receive at.
2599    #[test]
2600    fn a_flood_of_checks_from_new_addresses_cannot_grow_the_set_past_the_limit() {
2601        let config = Config {
2602            pair_limit: 4,
2603            ..Config::default()
2604        };
2605        let mut subject = Agent::new(config, false, credentials("aaaa"), 100);
2606        let mut driver = Driver::default();
2607        driver.absorb(&subject.handle(Input::LocalCandidate(host(address(ALICE)))));
2608        driver.absorb(&subject.handle(Input::RemoteDescription {
2609            credentials: credentials("bbbb"),
2610            candidates: vec![host_line(address(BOB), "1")],
2611            lite: false,
2612        }));
2613        driver.absorb(&subject.handle(Input::GatheringDone));
2614
2615        let mut answered = 0usize;
2616        for n in 0..200u32 {
2617            let source = address(&format!("198.51.100.{}:{}", n % 200 + 1, 40000 + n));
2618            let outputs = deliver(
2619                &mut subject,
2620                source,
2621                peer_check(RoleAttribute::Controlling {
2622                    tiebreaker: 999,
2623                    nominate: false,
2624                }),
2625            );
2626            driver.absorb(&outputs);
2627            answered += sent(&outputs).len();
2628        }
2629
2630        assert_eq!(
2631            answered, 200,
2632            "§7.3 still answers every authenticated check — a 64-byte response to an 88-byte \
2633             request is not an amplifier"
2634        );
2635        assert!(
2636            subject.checklists().total_pairs() <= 4,
2637            "the checklist set grew to {} against a configured limit of 4",
2638            subject.checklists().total_pairs()
2639        );
2640        assert!(
2641            subject.remote_candidates().len() <= 5,
2642            "the remote candidate table grew to {}",
2643            subject.remote_candidates().len()
2644        );
2645
2646        // And what the agent goes on to *send* is bounded by the limit, not by the flood.
2647        let mut destinations: Vec<SocketAddr> = Vec::new();
2648        let mut rounds = 0;
2649        while driver.armed && rounds < 200 {
2650            rounds += 1;
2651            for output in driver.tick(&mut subject) {
2652                if let Output::Send { to, bytes, .. } = output
2653                    && Message::decode(&bytes).is_ok_and(|m| m.class() == Class::Request)
2654                    && !destinations.contains(&to)
2655                {
2656                    destinations.push(to);
2657                }
2658            }
2659        }
2660        assert!(
2661            destinations.len() <= 4,
2662            "checks went to {} distinct addresses against a limit of 4",
2663            destinations.len()
2664        );
2665    }
2666
2667    // ------------------------------------------------------------------- a second description
2668
2669    fn remote_address_of(subject: &Agent, pair: PairId) -> Option<SocketAddr> {
2670        let remote = subject.checklists().pair(pair)?.remote;
2671        find_remote(subject.remote_candidates(), remote).map(|candidate| candidate.address)
2672    }
2673
2674    fn three_remote_agent() -> (Agent, Driver, Vec<SocketAddr>) {
2675        let remotes: Vec<SocketAddr> = (1..=3)
2676            .map(|n| address(&format!("198.51.100.{n}:6000")))
2677            .collect();
2678        let mut subject = Agent::new(Config::default(), true, credentials("aaaa"), 100);
2679        let mut driver = Driver::default();
2680        driver.absorb(&subject.handle(Input::LocalCandidate(host(address(ALICE)))));
2681        driver.absorb(
2682            &subject.handle(Input::RemoteDescription {
2683                credentials: credentials("bbbb"),
2684                candidates: remotes
2685                    .iter()
2686                    .enumerate()
2687                    .map(|(index, remote)| host_line(*remote, &(index + 1).to_string()))
2688                    .collect(),
2689                lite: false,
2690            }),
2691        );
2692        driver.absorb(&subject.handle(Input::GatheringDone));
2693        (subject, driver, remotes)
2694    }
2695
2696    /// RFC 8839 §4.2 lets a peer send more than one description for the same ICE session — a 183
2697    /// with SDP and then a 200 with SDP, or any re-INVITE — and the candidate list is the peer's
2698    /// to choose. Replacing the remote table under the live pairs leaves each of them naming a
2699    /// candidate it was never formed for, or nothing at all, and an agent whose pairs all dangle
2700    /// sends no checks, reports no failure and is simply silent.
2701    #[test]
2702    fn a_second_description_adds_candidates_without_re_pointing_the_live_pairs() {
2703        let (mut subject, mut driver, remotes) = three_remote_agent();
2704        let before: Vec<(PairId, SocketAddr)> = subject.checklists().checklists()[0]
2705            .pairs()
2706            .iter()
2707            .map(|pair| (pair.id, remote_address_of(&subject, pair.id).unwrap()))
2708            .collect();
2709        assert_eq!(before.len(), 3);
2710
2711        let fresh = address("203.0.113.99:6000");
2712        driver.absorb(&subject.handle(Input::RemoteDescription {
2713            credentials: credentials("bbbb"),
2714            candidates: vec![host_line(fresh, "9")],
2715            lite: false,
2716        }));
2717
2718        for (pair, was) in &before {
2719            assert_eq!(
2720                remote_address_of(&subject, *pair),
2721                Some(*was),
2722                "a re-offer must not re-point a pair that is already being checked"
2723            );
2724        }
2725        assert!(
2726            subject
2727                .remote_candidates()
2728                .iter()
2729                .any(|candidate| candidate.address == fresh),
2730            "the candidate the second description brought is added"
2731        );
2732        for remote in &remotes {
2733            assert!(
2734                subject
2735                    .remote_candidates()
2736                    .iter()
2737                    .any(|candidate| candidate.address == *remote),
2738                "a candidate the second description omitted is not dropped underneath its pair"
2739            );
2740        }
2741
2742        // And the agent is still checking: it has a Ta armed and checks still leave.
2743        assert!(driver.armed);
2744        let destinations: Vec<SocketAddr> = driver
2745            .tick(&mut subject)
2746            .iter()
2747            .filter_map(|output| match output {
2748                Output::Send { to, bytes, .. } if Message::decode(bytes).is_ok() => Some(*to),
2749                _ => None,
2750            })
2751            .collect();
2752        assert_eq!(
2753            destinations.len(),
2754            1,
2755            "a re-offer must not silence the agent"
2756        );
2757    }
2758
2759    /// RFC 8839 §4.4.1.1.1: **both** `ice-ufrag` and `ice-pwd` changing is an ICE restart, and
2760    /// everything is rebuilt for the new session.
2761    #[test]
2762    fn an_ice_restart_rebuilds_the_checklists_and_keeps_checking() {
2763        let (mut subject, mut driver, _) = three_remote_agent();
2764        let before: Vec<PairId> = subject.checklists().checklists()[0]
2765            .pairs()
2766            .iter()
2767            .map(|pair| pair.id)
2768            .collect();
2769
2770        let fresh = address("203.0.113.99:6000");
2771        driver.absorb(&subject.handle(Input::RemoteDescription {
2772            credentials: credentials("cccc"),
2773            candidates: vec![host_line(fresh, "1")],
2774            lite: false,
2775        }));
2776
2777        assert_eq!(subject.remote_candidates().len(), 1);
2778        assert_eq!(subject.checklists().total_pairs(), 1);
2779        for pair in before {
2780            assert!(
2781                subject.checklists().pair(pair).is_none(),
2782                "a restart is a new ICE session, so none of the old pairs survive it"
2783            );
2784        }
2785        assert!(driver.armed);
2786        let destinations: Vec<SocketAddr> = driver
2787            .tick(&mut subject)
2788            .iter()
2789            .filter_map(|output| match output {
2790                Output::Send { to, bytes, .. } if Message::decode(bytes).is_ok() => Some(*to),
2791                _ => None,
2792            })
2793            .collect();
2794        assert_eq!(destinations, vec![fresh]);
2795    }
2796
2797    /// The failing-first test of this story, and the one §7.1's note exists for: two agents can
2798    /// both believe they offered — third-party call control, glare, a re-INVITE crossing — and two
2799    /// controlling agents never converge, because neither will accept the other's nomination.
2800    ///
2801    /// They are given the *same* tiebreaker, so this is §7.3.1.1's `T = V` row at both ends
2802    /// simultaneously: each 487s the other, each switches under §7.2.5.1, and only the fresh
2803    /// tiebreakers §7.2.5.1 mandates break the symmetry on the second round.
2804    #[test]
2805    fn two_agents_that_both_start_controlling_converge_on_one_role() {
2806        let (mut alice, mut bob, mut left, mut right) = both_controlling(0x1234_5678_9abc_def0);
2807        assert_eq!(alice.role(), Role::Controlling);
2808        assert_eq!(bob.role(), Role::Controlling);
2809
2810        exchange(&mut alice, &mut bob, &mut left, &mut right, 6);
2811
2812        assert_ne!(
2813            alice.role(),
2814            bob.role(),
2815            "two controlling agents never converge: neither accepts the other's nomination"
2816        );
2817        assert!(alice.role().is_controlling() || bob.role().is_controlling());
2818    }
2819}