Skip to main content

rtc_ice/candidate/
mod.rs

1#[cfg(test)]
2mod candidate_pair_test;
3#[cfg(test)]
4mod candidate_test;
5
6//TODO: #[cfg(test)]
7//TODO: mod candidate_relay_test;
8/*TODO: #[cfg(test)]
9TODO: mod candidate_server_reflexive_test;
10*/
11
12pub mod candidate_host;
13pub mod candidate_pair;
14pub mod candidate_peer_reflexive;
15pub mod candidate_relay;
16pub mod candidate_server_reflexive;
17
18use crate::network_type::NetworkType;
19use crate::tcp_type::TcpType;
20use crc::{CRC_32_ISCSI, Crc};
21use serde::{Deserialize, Serialize};
22use shared::error::*;
23use std::fmt;
24use std::net::{IpAddr, SocketAddr};
25use std::time::Instant;
26
27use crate::candidate::candidate_host::CandidateHostConfig;
28use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
29use crate::candidate::candidate_relay::CandidateRelayConfig;
30use crate::candidate::candidate_server_reflexive::CandidateServerReflexiveConfig;
31use crate::network_type::determine_network_type;
32
33pub(crate) const RECEIVE_MTU: usize = 8192;
34pub(crate) const DEFAULT_LOCAL_PREFERENCE: u16 = 65535;
35
36/// Indicates that the candidate is used for RTP.
37pub(crate) const COMPONENT_RTP: u16 = 1;
38/// Indicates that the candidate is used for RTCP.
39pub(crate) const COMPONENT_RTCP: u16 = 0;
40
41/// Represents the type of candidate `CandidateType` enum.
42#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
43pub enum CandidateType {
44    #[default]
45    #[serde(rename = "unspecified")]
46    Unspecified,
47    #[serde(rename = "host")]
48    Host,
49    #[serde(rename = "srflx")]
50    ServerReflexive,
51    #[serde(rename = "prflx")]
52    PeerReflexive,
53    #[serde(rename = "relay")]
54    Relay,
55}
56
57// String makes CandidateType printable
58impl fmt::Display for CandidateType {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        let s = match *self {
61            CandidateType::Host => "host",
62            CandidateType::ServerReflexive => "srflx",
63            CandidateType::PeerReflexive => "prflx",
64            CandidateType::Relay => "relay",
65            CandidateType::Unspecified => "Unknown candidate type",
66        };
67        write!(f, "{s}")
68    }
69}
70
71impl CandidateType {
72    /// Returns the preference weight of a `CandidateType`.
73    ///
74    /// 4.1.2.2.  Guidelines for Choosing Type and Local Preferences
75    /// The RECOMMENDED values are 126 for host candidates, 100
76    /// for server reflexive candidates, 110 for peer reflexive candidates,
77    /// and 0 for relayed candidates.
78    #[must_use]
79    pub const fn preference(self) -> u16 {
80        match self {
81            Self::Host => 126,
82            Self::PeerReflexive => 110,
83            Self::ServerReflexive => 100,
84            Self::Relay | CandidateType::Unspecified => 0,
85        }
86    }
87}
88
89pub(crate) fn contains_candidate_type(
90    candidate_type: CandidateType,
91    candidate_type_list: &[CandidateType],
92) -> bool {
93    if candidate_type_list.is_empty() {
94        return false;
95    }
96    for ct in candidate_type_list {
97        if *ct == candidate_type {
98            return true;
99        }
100    }
101    false
102}
103
104/// Convey transport addresses related to the candidate, useful for diagnostics and other purposes.
105#[derive(PartialEq, Eq, Debug, Clone)]
106pub struct CandidateRelatedAddress {
107    pub address: String,
108    pub port: u16,
109}
110
111// String makes CandidateRelatedAddress printable
112impl fmt::Display for CandidateRelatedAddress {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        write!(f, " related {}:{}", self.address, self.port)
115    }
116}
117
118#[derive(Default)]
119pub struct CandidateConfig {
120    pub candidate_id: String,
121    pub network: String,
122    pub address: String,
123    pub port: u16,
124    pub component: u16,
125    pub priority: u32,
126    pub foundation: String,
127}
128
129#[derive(Clone, Debug)]
130pub struct Candidate {
131    pub(crate) id: String,
132    pub(crate) network_type: NetworkType,
133    pub(crate) candidate_type: CandidateType,
134
135    pub(crate) component: u16,
136    pub(crate) address: String,
137    pub(crate) port: u16,
138    pub(crate) related_address: Option<CandidateRelatedAddress>,
139    pub(crate) tcp_type: TcpType,
140
141    pub(crate) resolved_addr: SocketAddr,
142
143    pub(crate) last_sent: Instant,
144    pub(crate) last_received: Instant,
145
146    pub(crate) foundation_override: String,
147    pub(crate) priority_override: u32,
148
149    pub(crate) network: String,
150
151    pub(crate) url: Option<String>,
152}
153
154impl Default for Candidate {
155    fn default() -> Self {
156        Self {
157            id: String::new(),
158            network_type: NetworkType::Unspecified,
159            candidate_type: CandidateType::default(),
160
161            component: 0,
162            address: String::new(),
163            port: 0,
164            related_address: None,
165            tcp_type: TcpType::default(),
166
167            resolved_addr: SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 0),
168
169            last_sent: Instant::now(),
170            last_received: Instant::now(),
171
172            foundation_override: String::new(),
173            priority_override: 0,
174            network: String::new(),
175
176            url: None,
177        }
178    }
179}
180
181// String makes the candidateBase printable
182impl fmt::Display for Candidate {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        if let Some(related_address) = self.related_address() {
185            write!(
186                f,
187                "{} {} {}:{}{}",
188                self.network_type(),
189                self.candidate_type(),
190                self.address(),
191                self.port(),
192                related_address,
193            )
194        } else {
195            write!(
196                f,
197                "{} {} {}:{}",
198                self.network_type(),
199                self.candidate_type(),
200                self.address(),
201                self.port(),
202            )
203        }
204    }
205}
206
207impl Candidate {
208    pub fn foundation(&self) -> String {
209        if !self.foundation_override.is_empty() {
210            return self.foundation_override.clone();
211        }
212
213        let mut buf = vec![];
214        buf.extend_from_slice(self.candidate_type().to_string().as_bytes());
215        buf.extend_from_slice(self.address.as_bytes());
216        buf.extend_from_slice(self.network_type().to_string().as_bytes());
217
218        let checksum = Crc::<u32>::new(&CRC_32_ISCSI).checksum(&buf);
219
220        format!("{checksum}")
221    }
222
223    /// Returns Candidate ID.
224    pub fn id(&self) -> &str {
225        self.id.as_str()
226    }
227
228    /// Returns candidate component.
229    pub fn component(&self) -> u16 {
230        self.component
231    }
232
233    /// Sets candidate component.
234    pub fn set_component(&mut self, component: u16) {
235        self.component = component;
236    }
237
238    /// Returns a time indicating the last time this candidate was received.
239    pub fn last_received(&self) -> Instant {
240        self.last_received
241    }
242
243    /// Returns a time indicating the last time this candidate was sent.
244    pub fn last_sent(&self) -> Instant {
245        self.last_sent
246    }
247
248    /// Returns candidate NetworkType.
249    pub fn network_type(&self) -> NetworkType {
250        self.network_type
251    }
252
253    /// Returns Candidate Address.
254    pub fn address(&self) -> &str {
255        self.address.as_str()
256    }
257
258    /// Returns Candidate Port.
259    pub fn port(&self) -> u16 {
260        self.port
261    }
262
263    /// Computes the priority for this ICE Candidate.
264    pub fn priority(&self) -> u32 {
265        if self.priority_override != 0 {
266            return self.priority_override;
267        }
268
269        // The local preference MUST be an integer from 0 (lowest preference) to
270        // 65535 (highest preference) inclusive.  When there is only a single IP
271        // address, this value SHOULD be set to 65535.  If there are multiple
272        // candidates for a particular component for a particular data stream
273        // that have the same type, the local preference MUST be unique for each
274        // one.
275        (1 << 24) * u32::from(self.candidate_type().preference())
276            + (1 << 8) * u32::from(self.local_preference())
277            + (256 - u32::from(self.component()))
278    }
279
280    /// Returns `Option<CandidateRelatedAddress>`.
281    pub fn related_address(&self) -> Option<CandidateRelatedAddress> {
282        self.related_address.as_ref().cloned()
283    }
284
285    /// Returns candidate type.
286    pub fn candidate_type(&self) -> CandidateType {
287        self.candidate_type
288    }
289
290    pub fn tcp_type(&self) -> TcpType {
291        self.tcp_type
292    }
293
294    pub fn url(&self) -> Option<&str> {
295        self.url.as_deref()
296    }
297
298    /// Returns the string representation of the ICECandidate.
299    pub fn marshal(&self) -> String {
300        let mut val = format!(
301            "{} {} {} {} {} {} typ {}",
302            self.foundation(),
303            self.component(),
304            self.network_type().network_short(),
305            self.priority(),
306            self.address(),
307            self.port(),
308            self.candidate_type()
309        );
310
311        if self.tcp_type != TcpType::Unspecified {
312            val += format!(" tcptype {}", self.tcp_type()).as_str();
313        }
314
315        if let Some(related_address) = self.related_address() {
316            val += format!(
317                " raddr {} rport {}",
318                related_address.address, related_address.port,
319            )
320            .as_str();
321        }
322
323        val
324    }
325
326    pub fn addr(&self) -> SocketAddr {
327        self.resolved_addr
328    }
329
330    pub fn seen(&mut self, outbound: bool) {
331        let now = Instant::now();
332
333        if outbound {
334            self.set_last_sent(now);
335        } else {
336            self.set_last_received(now);
337        }
338    }
339
340    /// Used to compare two candidateBases.
341    pub fn equal(&self, other: &Candidate) -> bool {
342        self.network_type() == other.network_type()
343            && self.candidate_type() == other.candidate_type()
344            && self.address() == other.address()
345            && self.port() == other.port()
346            && self.tcp_type() == other.tcp_type()
347            && self.related_address() == other.related_address()
348    }
349
350    /// Returns true if this candidate can be paired with `other` according to
351    /// ICE candidate pairing rules.
352    ///
353    /// Candidates must share the same network type (protocol and address family)
354    /// and have compatible TCP types (RFC 6544). UDP candidates use
355    /// `TcpType::Unspecified`.
356    pub(crate) fn can_pair_with(&self, other: &Candidate) -> bool {
357        if self.network_type() != other.network_type() {
358            return false;
359        }
360
361        match (self.tcp_type(), other.tcp_type()) {
362            (TcpType::Active, TcpType::Passive) => true,
363            (TcpType::Passive, TcpType::Active) => true,
364            (TcpType::SimultaneousOpen, TcpType::SimultaneousOpen) => true,
365            (TcpType::Unspecified, TcpType::Unspecified) => true, // UDP candidates
366            _ => false,
367        }
368    }
369
370    pub fn set_ip(&mut self, ip: &IpAddr) -> Result<()> {
371        self.network_type = determine_network_type(&self.network, ip)?;
372        self.resolved_addr = SocketAddr::new(*ip, self.port); //TODO:  create_addr(network_type, *ip, self.port);
373        Ok(())
374    }
375}
376
377impl Candidate {
378    pub fn set_last_received(&mut self, now: Instant) {
379        self.last_received = now;
380    }
381
382    pub fn set_last_sent(&mut self, now: Instant) {
383        self.last_sent = now;
384    }
385
386    /// Returns the local preference for this candidate.
387    pub fn local_preference(&self) -> u16 {
388        if self.network_type().is_tcp() {
389            // RFC 6544, section 4.2
390            //
391            // In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE
392            // candidate prioritization is defined.  For TCP candidates, the same
393            // formula and candidate type preferences SHOULD be used, and the
394            // RECOMMENDED type preferences for the new candidate types defined in
395            // this document (see Section 5) are 105 for NAT-assisted candidates and
396            // 75 for UDP-tunneled candidates.
397            //
398            // (...)
399            //
400            // With TCP candidates, the local preference part of the recommended
401            // priority formula is updated to also include the directionality
402            // (active, passive, or simultaneous-open) of the TCP connection.  The
403            // RECOMMENDED local preference is then defined as:
404            //
405            //     local preference = (2^13) * direction-pref + other-pref
406            //
407            // The direction-pref MUST be between 0 and 7 (both inclusive), with 7
408            // being the most preferred.  The other-pref MUST be between 0 and 8191
409            // (both inclusive), with 8191 being the most preferred.  It is
410            // RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates
411            // have the direction-pref assigned as follows: 6 for active, 4 for
412            // passive, and 2 for S-O.  For the NAT-assisted and server reflexive
413            // candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and
414            // 2 for passive.
415            //
416            // (...)
417            //
418            // If any two candidates have the same type-preference and direction-
419            // pref, they MUST have a unique other-pref.  With this specification,
420            // this usually only happens with multi-homed hosts, in which case
421            // other-pref is the preference for the particular IP address from which
422            // the candidate was obtained.  When there is only a single IP address,
423            // this value SHOULD be set to the maximum allowed value (8191).
424            let other_pref: u16 = 8191;
425
426            let direction_pref: u16 = match self.candidate_type() {
427                CandidateType::Host | CandidateType::Relay => match self.tcp_type() {
428                    TcpType::Active => 6,
429                    TcpType::Passive => 4,
430                    TcpType::SimultaneousOpen => 2,
431                    TcpType::Unspecified => 0,
432                },
433                CandidateType::PeerReflexive | CandidateType::ServerReflexive => {
434                    match self.tcp_type() {
435                        TcpType::SimultaneousOpen => 6,
436                        TcpType::Active => 4,
437                        TcpType::Passive => 2,
438                        TcpType::Unspecified => 0,
439                    }
440                }
441                CandidateType::Unspecified => 0,
442            };
443
444            (1 << 13) * direction_pref + other_pref
445        } else {
446            DEFAULT_LOCAL_PREFERENCE
447        }
448    }
449}
450
451/// Creates a Candidate from its string representation.
452pub fn unmarshal_candidate(raw: &str) -> Result<Candidate> {
453    let split: Vec<&str> = raw.split_whitespace().collect();
454    if split.len() < 8 {
455        return Err(Error::Other(format!(
456            "{:?} ({})",
457            Error::ErrAttributeTooShortIceCandidate,
458            split.len()
459        )));
460    }
461
462    // Foundation
463    let foundation = split[0].to_owned();
464
465    // Component
466    let component: u16 = split[1].parse()?;
467
468    // Network
469    let network = split[2].to_owned();
470
471    // Priority
472    let priority: u32 = split[3].parse()?;
473
474    // Address
475    let address = split[4].to_owned();
476
477    // Port
478    let port: u16 = split[5].parse()?;
479
480    let typ = split[7];
481
482    let mut rel_addr = String::new();
483    let mut rel_port = 0;
484    let mut tcp_type = TcpType::Unspecified;
485
486    if split.len() > 8 {
487        let split2 = &split[8..];
488
489        if split2[0] == "raddr" {
490            if split2.len() < 4 {
491                return Err(Error::Other(format!(
492                    "{:?}: incorrect length",
493                    Error::ErrParseRelatedAddr
494                )));
495            }
496
497            // RelatedAddress
498            split2[1].clone_into(&mut rel_addr);
499
500            // RelatedPort
501            rel_port = split2[3].parse()?;
502        } else if split2[0] == "tcptype" {
503            if split2.len() < 2 {
504                return Err(Error::Other(format!(
505                    "{:?}: incorrect length",
506                    Error::ErrParseType
507                )));
508            }
509
510            tcp_type = TcpType::from(split2[1]);
511        }
512    }
513
514    match typ {
515        "host" => {
516            let config = CandidateHostConfig {
517                base_config: CandidateConfig {
518                    network,
519                    address,
520                    port,
521                    component,
522                    priority,
523                    foundation,
524                    ..CandidateConfig::default()
525                },
526                tcp_type,
527            };
528            config.new_candidate_host()
529        }
530        "srflx" => {
531            let config = CandidateServerReflexiveConfig {
532                base_config: CandidateConfig {
533                    network,
534                    address,
535                    port,
536                    component,
537                    priority,
538                    foundation,
539                    ..CandidateConfig::default()
540                },
541                rel_addr,
542                rel_port,
543                url: None,
544            };
545            config.new_candidate_server_reflexive()
546        }
547        "prflx" => {
548            let config = CandidatePeerReflexiveConfig {
549                base_config: CandidateConfig {
550                    network,
551                    address,
552                    port,
553                    component,
554                    priority,
555                    foundation,
556                    ..CandidateConfig::default()
557                },
558                rel_addr,
559                rel_port,
560            };
561
562            config.new_candidate_peer_reflexive()
563        }
564        "relay" => {
565            let config = CandidateRelayConfig {
566                base_config: CandidateConfig {
567                    network,
568                    address,
569                    port,
570                    component,
571                    priority,
572                    foundation,
573                    ..CandidateConfig::default()
574                },
575                rel_addr,
576                rel_port,
577                url: None,
578            };
579            config.new_candidate_relay()
580        }
581        _ => Err(Error::Other(format!(
582            "{:?} ({})",
583            Error::ErrUnknownCandidateType,
584            typ
585        ))),
586    }
587}