Skip to main content

rtc_ice/candidate/
candidate_pair.rs

1//! Candidate pairs and their check state.
2//!
3//! ICE forms a pair from each compatible local/remote candidate combination and works through
4//! them in priority order. A pair's combined priority is computed from both sides' priorities with
5//! the controlling agent's dominating, so both agents derive the same ordering.
6//!
7//! [`CandidatePairState`](crate::candidate::candidate_pair::CandidatePairState) tracks how far a pair has got: waiting, in progress, succeeded or
8//! failed. The controlling agent nominates one of the succeeded pairs, and that pair carries the
9//! media.
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use std::time::Duration;
13
14/// Represent the ICE candidate pair state.
15#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum CandidatePairState {
17    #[default]
18    #[serde(rename = "unspecified")]
19    /// No state was set.
20    Unspecified = 0,
21
22    /// Means a check has not been performed for this pair.
23    #[serde(rename = "waiting")]
24    Waiting = 1,
25
26    /// Means a check has been sent for this pair, but the transaction is in progress.
27    #[serde(rename = "in-progress")]
28    InProgress = 2,
29
30    /// Means a check for this pair was already done and failed, either never producing any response
31    /// or producing an unrecoverable failure response.
32    #[serde(rename = "failed")]
33    Failed = 3,
34
35    /// Means a check for this pair was already done and produced a successful result.
36    #[serde(rename = "succeeded")]
37    Succeeded = 4,
38}
39
40impl From<u8> for CandidatePairState {
41    fn from(v: u8) -> Self {
42        match v {
43            1 => Self::Waiting,
44            2 => Self::InProgress,
45            3 => Self::Failed,
46            4 => Self::Succeeded,
47            _ => Self::Unspecified,
48        }
49    }
50}
51
52impl fmt::Display for CandidatePairState {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        let s = match *self {
55            Self::Waiting => "waiting",
56            Self::InProgress => "in-progress",
57            Self::Failed => "failed",
58            Self::Succeeded => "succeeded",
59            Self::Unspecified => "unspecified",
60        };
61
62        write!(f, "{s}")
63    }
64}
65
66/// Represents a combination of a local and remote candidate.
67#[derive(Clone, Copy)]
68pub struct CandidatePair {
69    /// Index of the local candidate in the agent's list.
70    pub local_index: usize,
71    /// Index of the remote candidate in the agent's list.
72    pub remote_index: usize,
73    /// The local candidate's priority.
74    pub local_priority: u32,
75    /// The remote candidate's priority.
76    pub remote_priority: u32,
77    pub(crate) ice_role_controlling: bool,
78    pub(crate) binding_request_count: u16,
79    pub(crate) state: CandidatePairState,
80    pub(crate) nominated: bool,
81
82    // STUN transaction stats
83    /// Total number of STUN connectivity check requests sent (not including retransmissions).
84    pub(crate) requests_sent: u64,
85    /// Total number of STUN connectivity check requests received.
86    pub(crate) requests_received: u64,
87    /// Total number of STUN connectivity check responses sent.
88    pub(crate) responses_sent: u64,
89    /// Total number of STUN connectivity check responses received.
90    pub(crate) responses_received: u64,
91    /// Total number of consent freshness requests sent.
92    pub(crate) consent_requests_sent: u64,
93
94    // RTT tracking
95    /// Sum of all round trip time measurements.
96    pub(crate) total_round_trip_time: Duration,
97    /// Latest round trip time measured.
98    pub(crate) current_round_trip_time: Duration,
99}
100
101impl fmt::Debug for CandidatePair {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(
104            f,
105            "prio {} (local, prio {}) {} <-> {} (remote, prio {})",
106            self.priority(),
107            self.local_priority,
108            self.local_index,
109            self.remote_index,
110            self.remote_priority,
111        )
112    }
113}
114
115impl fmt::Display for CandidatePair {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        write!(
118            f,
119            "prio {} (local, prio {}) {} <-> {} (remote, prio {})",
120            self.priority(),
121            self.local_priority,
122            self.local_index,
123            self.remote_index,
124            self.remote_priority,
125        )
126    }
127}
128
129impl PartialEq for CandidatePair {
130    fn eq(&self, other: &Self) -> bool {
131        self.local_index == other.local_index && self.remote_index == other.remote_index
132    }
133}
134
135impl CandidatePair {
136    #[must_use]
137    /// Forms a pair from a local and a remote candidate.
138    ///
139    /// `controlling` selects which priority dominates in the pair's combined priority.
140    pub fn new(
141        local_index: usize,
142        remote_index: usize,
143        local_priority: u32,
144        remote_priority: u32,
145        ice_role_controlling: bool,
146    ) -> Self {
147        Self {
148            local_index,
149            remote_index,
150            local_priority,
151            remote_priority,
152            ice_role_controlling,
153            state: CandidatePairState::Waiting,
154            binding_request_count: 0,
155            nominated: false,
156            // STUN transaction stats
157            requests_sent: 0,
158            requests_received: 0,
159            responses_sent: 0,
160            responses_received: 0,
161            consent_requests_sent: 0,
162            // RTT tracking
163            total_round_trip_time: Duration::ZERO,
164            current_round_trip_time: Duration::ZERO,
165        }
166    }
167
168    /// RFC 5245 - 5.7.2.  Computing Pair Priority and Ordering Pairs
169    /// Let G be the priority for the candidate provided by the controlling
170    /// agent.  Let D be the priority for the candidate provided by the
171    /// controlled agent.
172    /// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
173    pub fn priority(&self) -> u64 {
174        let (g, d) = if self.ice_role_controlling {
175            (self.local_priority, self.remote_priority)
176        } else {
177            (self.remote_priority, self.local_priority)
178        };
179
180        // 1<<32 overflows uint32; and if both g && d are
181        // maxUint32, this result would overflow uint64
182        ((1 << 32_u64) - 1) * u64::from(std::cmp::min(g, d))
183            + 2 * u64::from(std::cmp::max(g, d))
184            + u64::from(g > d)
185    }
186
187    /// Called when a STUN binding request is sent.
188    pub fn on_request_sent(&mut self) {
189        self.requests_sent += 1;
190    }
191
192    /// Called when a STUN binding request is received.
193    pub fn on_request_received(&mut self) {
194        self.requests_received += 1;
195    }
196
197    /// Called when a STUN binding success response is sent.
198    pub fn on_response_sent(&mut self) {
199        self.responses_sent += 1;
200    }
201
202    /// Called when a STUN binding success response is received.
203    /// Also updates RTT measurements.
204    pub fn on_response_received(&mut self, rtt: Duration) {
205        self.responses_received += 1;
206        self.current_round_trip_time = rtt;
207        self.total_round_trip_time += rtt;
208    }
209
210    /// Called when a consent freshness request is sent (keepalive).
211    pub fn on_consent_request_sent(&mut self) {
212        self.consent_requests_sent += 1;
213    }
214}