Skip to main content

rice_c/
candidate.rs

1// Copyright (C) 2025 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8//
9// SPDX-License-Identifier: MIT OR Apache-2.0
10
11//! ICE Candidates
12
13use std::ffi::{CStr, CString};
14
15/// ICE candidate.
16pub trait CandidateApi: sealed::CandidateAsC {
17    /// The component
18    fn component_id(&self) -> usize {
19        unsafe { (*self.as_c()).component_id }
20    }
21    /// The type of the Candidate
22    fn candidate_type(&self) -> CandidateType {
23        unsafe { (*self.as_c()).candidate_type.into() }
24    }
25    /// The network transport
26    fn transport(&self) -> TransportType {
27        unsafe { (*self.as_c()).transport_type.into() }
28    }
29
30    /// The (unique) foundation
31    fn foundation(&self) -> String {
32        unsafe {
33            CStr::from_ptr((*self.as_c()).foundation)
34                .to_str()
35                .unwrap()
36                .to_owned()
37        }
38    }
39    /// The priority
40    fn priority(&self) -> u32 {
41        unsafe { (*self.as_c()).priority }
42    }
43    /// The address to send to
44    fn address(&self) -> crate::Address {
45        unsafe { crate::Address::from_c_none((*self.as_c()).address) }
46    }
47    /// The address to send from
48    fn base_address(&self) -> crate::Address {
49        unsafe { crate::Address::from_c_none((*self.as_c()).base_address) }
50    }
51    /// Any related address that generated this candidate, e.g. STUN/TURN server
52    fn related_address(&self) -> Option<crate::Address> {
53        unsafe {
54            let related = (*self.as_c()).related_address;
55            if related.is_null() {
56                None
57            } else {
58                Some(crate::Address::from_c_none(related))
59            }
60        }
61    }
62    /// The type of TCP candidate
63    fn tcp_type(&self) -> TcpType {
64        unsafe { (*self.as_c()).tcp_type.into() }
65    }
66    // TODO: extensions
67}
68
69/// Errors produced when parsing a candidate
70#[derive(Debug, Copy, Clone, PartialEq, Eq)]
71#[repr(i32)]
72pub enum ParseCandidateError {
73    /// Not a candidate message.
74    NotCandidate = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE,
75    /// Invalid foundation value.
76    BadFoundation = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION,
77    /// Invalid component id.
78    BadComponentId = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID,
79    /// Invalid transport type.
80    BadTransportType = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE,
81    /// Invalid priority value.
82    BadPriority = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY,
83    /// Invalid network address.
84    BadAddress = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS,
85    /// Invalid candidate type.
86    BadCandidateType = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE,
87    /// Invalid extension format.
88    BadExtension = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION,
89    /// Data is not well formed.
90    Malformed = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED,
91}
92
93impl ParseCandidateError {
94    pub(crate) fn from_c(
95        value: crate::ffi::RiceParseCandidateError,
96    ) -> Result<(), ParseCandidateError> {
97        match value {
98            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_SUCCESS => Ok(()),
99            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE => {
100                Err(ParseCandidateError::NotCandidate)
101            }
102            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION => {
103                Err(ParseCandidateError::BadFoundation)
104            }
105            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID => {
106                Err(ParseCandidateError::BadComponentId)
107            }
108            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE => {
109                Err(ParseCandidateError::BadTransportType)
110            }
111            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY => {
112                Err(ParseCandidateError::BadPriority)
113            }
114            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS => {
115                Err(ParseCandidateError::BadAddress)
116            }
117            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE => {
118                Err(ParseCandidateError::BadCandidateType)
119            }
120            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION => {
121                Err(ParseCandidateError::BadExtension)
122            }
123            crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED => Err(ParseCandidateError::Malformed),
124            val => panic!("Unknown RiceParseCandidateError value {val:x?}"),
125        }
126    }
127}
128
129mod sealed {
130    pub trait CandidateAsC {
131        fn as_c(&self) -> *const crate::ffi::RiceCandidate;
132    }
133}
134
135/// An ICE candidate.
136#[derive(Clone)]
137pub struct Candidate {
138    ffi: crate::ffi::RiceCandidate,
139}
140
141unsafe impl Send for Candidate {}
142unsafe impl Sync for Candidate {}
143
144impl core::fmt::Debug for Candidate {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        let mut dbg = f.debug_struct("Candidate");
147        dbg.field("component_id", &self.component_id());
148        dbg.field("candidate_type", &self.candidate_type());
149        dbg.field("transport_type", &self.transport());
150        dbg.field("foundation", &self.foundation());
151        dbg.field("priority", &self.priority());
152        dbg.field("address", &self.address());
153        dbg.field("base_address", &self.base_address());
154        dbg.field("related_address", &self.related_address());
155        dbg.field("tcp_type", &self.tcp_type());
156        // TODO: extensions
157        dbg.finish()
158    }
159}
160
161impl PartialEq<Candidate> for Candidate {
162    fn eq(&self, other: &Candidate) -> bool {
163        unsafe { crate::ffi::rice_candidate_eq(&self.ffi, &other.ffi) }
164    }
165}
166
167impl PartialEq<CandidateOwned> for Candidate {
168    fn eq(&self, other: &CandidateOwned) -> bool {
169        unsafe { crate::ffi::rice_candidate_eq(&self.ffi, other.ffi) }
170    }
171}
172
173impl Eq for Candidate {}
174
175impl CandidateApi for Candidate {}
176impl sealed::CandidateAsC for Candidate {
177    fn as_c(&self) -> *const crate::ffi::RiceCandidate {
178        &self.ffi
179    }
180}
181
182impl Drop for Candidate {
183    fn drop(&mut self) {
184        unsafe { crate::ffi::rice_candidate_clear(&mut self.ffi) }
185    }
186}
187
188impl Candidate {
189    /// Builds the candidate
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// # use rice_c::candidate::*;
195    /// # use rice_c::Address;
196    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
197    /// let candidate = Candidate::builder(
198    ///     1,
199    ///     CandidateType::Host,
200    ///     TransportType::Udp,
201    ///     "foundation",
202    ///     addr,
203    /// )
204    /// .priority(1234)
205    /// .build();
206    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
207    /// ```
208    pub fn builder(
209        component_id: usize,
210        ctype: CandidateType,
211        ttype: TransportType,
212        foundation: &str,
213        address: crate::Address,
214    ) -> CandidateBuilder {
215        unsafe {
216            let foundation = CString::new(foundation).unwrap();
217            let mut ret = CandidateBuilder {
218                ffi: crate::ffi::RiceCandidate::zeroed(),
219            };
220            let address = address.into_c_full();
221            let res = crate::ffi::rice_candidate_init(
222                &mut ret.ffi,
223                component_id,
224                ctype.into(),
225                ttype.into(),
226                foundation.as_ptr(),
227                address,
228            );
229            if res != crate::ffi::RICE_ERROR_SUCCESS {
230                let _address = crate::Address::from_c_full(address);
231                panic!("Failed to crate ICE candidate!");
232            }
233            ret
234        }
235    }
236
237    pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
238        &self.ffi
239    }
240
241    pub(crate) unsafe fn from_c_none(candidate: *const crate::ffi::RiceCandidate) -> Self {
242        unsafe {
243            let mut ret = Self {
244                ffi: crate::ffi::RiceCandidate::zeroed(),
245            };
246            crate::ffi::rice_candidate_copy_into(candidate, &mut ret.ffi);
247            ret
248        }
249    }
250
251    pub(crate) fn from_c_full(candidate: crate::ffi::RiceCandidate) -> Self {
252        Self { ffi: candidate }
253    }
254
255    /// Copy this candidate into a heap allocated [`CandidateOwned`].
256    pub fn to_owned(&self) -> CandidateOwned {
257        unsafe {
258            CandidateOwned {
259                ffi: crate::ffi::rice_candidate_copy(&self.ffi),
260            }
261        }
262    }
263
264    /// Serialize this candidate to a string for use in SDP
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// # use rice_c::candidate::*;
270    /// # use rice_c::Address;
271    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
272    /// let candidate = Candidate::builder(
273    ///     1,
274    ///     CandidateType::Host,
275    ///     TransportType::Udp,
276    ///     "foundation",
277    ///     addr,
278    /// )
279    /// .priority(1234)
280    /// .build();
281    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
282    /// ```
283    pub fn to_sdp_string(&self) -> String {
284        unsafe {
285            let res = crate::ffi::rice_candidate_to_sdp_string(&self.ffi);
286            let s = CStr::from_ptr(res);
287            let ret = s.to_str().unwrap().to_owned();
288            crate::ffi::rice_string_free(res);
289            ret
290        }
291    }
292
293    /// Parse an SDP candidate string into a candidate.
294    pub fn from_sdp_string(s: &str) -> Result<Candidate, ParseCandidateError> {
295        let cand_str = std::ffi::CString::new(s).unwrap();
296        unsafe {
297            let mut ret = Candidate {
298                ffi: crate::ffi::RiceCandidate::zeroed(),
299            };
300            let res =
301                crate::ffi::rice_candidate_init_from_sdp_string(&mut ret.ffi, cand_str.as_ptr());
302            ParseCandidateError::from_c(res)?;
303            Ok(ret)
304        }
305    }
306}
307
308/// An ICE candidate.
309///
310/// Backed inside a heap allocation.
311#[derive(Eq)]
312pub struct CandidateOwned {
313    ffi: *mut crate::ffi::RiceCandidate,
314}
315
316unsafe impl Send for CandidateOwned {}
317unsafe impl Sync for CandidateOwned {}
318
319impl core::fmt::Debug for CandidateOwned {
320    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321        let mut dbg = f.debug_struct("CandidateOwned");
322        dbg.field("component_id", &self.component_id());
323        dbg.field("candidate_type", &self.candidate_type());
324        dbg.field("transport_type", &self.transport());
325        dbg.field("foundation", &self.foundation());
326        dbg.field("priority", &self.priority());
327        dbg.field("address", &self.address());
328        dbg.field("base_address", &self.base_address());
329        dbg.field("related_address", &self.related_address());
330        dbg.field("tcp_type", &self.tcp_type());
331        // TODO: extensions
332        dbg.finish()
333    }
334}
335
336impl PartialEq<CandidateOwned> for CandidateOwned {
337    fn eq(&self, other: &CandidateOwned) -> bool {
338        unsafe { crate::ffi::rice_candidate_eq(self.ffi, other.ffi) }
339    }
340}
341
342impl PartialEq<Candidate> for CandidateOwned {
343    fn eq(&self, other: &Candidate) -> bool {
344        unsafe { crate::ffi::rice_candidate_eq(self.ffi, &other.ffi) }
345    }
346}
347
348impl Clone for CandidateOwned {
349    fn clone(&self) -> Self {
350        unsafe {
351            Self {
352                ffi: crate::ffi::rice_candidate_copy(self.ffi),
353            }
354        }
355    }
356}
357
358impl Drop for CandidateOwned {
359    fn drop(&mut self) {
360        unsafe { crate::ffi::rice_candidate_free(self.ffi) }
361    }
362}
363
364impl CandidateApi for CandidateOwned {}
365impl sealed::CandidateAsC for CandidateOwned {
366    fn as_c(&self) -> *const crate::ffi::RiceCandidate {
367        self.ffi
368    }
369}
370
371impl CandidateOwned {
372    pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
373        self.ffi
374    }
375
376    /// Serialize this candidate to a string for use in SDP
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// # use rice_c::candidate::*;
382    /// # use rice_c::Address;
383    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
384    /// let candidate = Candidate::builder(
385    ///     1,
386    ///     CandidateType::Host,
387    ///     TransportType::Udp,
388    ///     "foundation",
389    ///     addr,
390    /// )
391    /// .priority(1234)
392    /// .build();
393    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
394    /// ```
395    pub fn to_sdp_string(&self) -> String {
396        unsafe {
397            let res = crate::ffi::rice_candidate_to_sdp_string(self.ffi);
398            let s = CStr::from_ptr(res);
399            let ret = s.to_str().unwrap().to_owned();
400            crate::ffi::rice_string_free(res);
401            ret
402        }
403    }
404}
405
406/// A builder for a [`Candidate`]
407#[derive(Debug)]
408pub struct CandidateBuilder {
409    ffi: crate::ffi::RiceCandidate,
410}
411
412impl CandidateBuilder {
413    /// Consume this builder a construct a new [`Candidate`].
414    pub fn build(self) -> Candidate {
415        Candidate { ffi: self.ffi }
416    }
417
418    /// Specify the priority of the to be built candidate
419    pub fn priority(mut self, priority: u32) -> Self {
420        unsafe {
421            crate::ffi::rice_candidate_set_priority(&mut self.ffi, priority);
422            self
423        }
424    }
425
426    /// Specify the base address of the to be built candidate
427    pub fn base_address(mut self, base: crate::Address) -> Self {
428        unsafe {
429            crate::ffi::rice_candidate_set_base_address(&mut self.ffi, base.into_c_full());
430            self
431        }
432    }
433
434    /// Specify the related address of the to be built candidate
435    pub fn related_address(mut self, related: crate::Address) -> Self {
436        unsafe {
437            crate::ffi::rice_candidate_set_related_address(&mut self.ffi, related.into_c_full());
438            self
439        }
440    }
441
442    /// Specify the type of TCP connection of the to be built candidate
443    ///
444    /// - This will panic at build() time if the transport type is not [`TransportType::Tcp`].
445    /// - This will panic at build() time if this function is not called but the
446    ///   transport type is [`TransportType::Tcp`]
447    pub fn tcp_type(mut self, typ: TcpType) -> Self {
448        unsafe {
449            if self.ffi.transport_type != TransportType::Tcp.into() && typ != TcpType::None {
450                panic!("Attempt made to set the TcpType of a non-TCP candidate");
451            }
452            crate::ffi::rice_candidate_set_tcp_type(&mut self.ffi, typ.into());
453            self
454        }
455    }
456
457    // TODO: extensions
458}
459
460/// The type of the candidate
461#[derive(Debug, Clone, Copy, PartialEq, Eq)]
462#[repr(u32)]
463pub enum CandidateType {
464    /// The candidate is a local network interface
465    Host = crate::ffi::RICE_CANDIDATE_TYPE_HOST,
466    /// The candidate was discovered from incoming data
467    PeerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
468    /// The candidate was discovered by asking an external server (STUN/TURN)
469    ServerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
470    /// The candidate will relay all data through an external server (TURN).
471    Relayed = crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
472}
473
474impl From<crate::ffi::RiceCandidateType> for CandidateType {
475    fn from(value: crate::ffi::RiceCandidateType) -> Self {
476        match value {
477            crate::ffi::RICE_CANDIDATE_TYPE_HOST => Self::Host,
478            crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE => Self::PeerReflexive,
479            crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE => Self::ServerReflexive,
480            crate::ffi::RICE_CANDIDATE_TYPE_RELAYED => Self::Relayed,
481            val => panic!("Unknown candidate type {val:x?}"),
482        }
483    }
484}
485
486impl From<CandidateType> for crate::ffi::RiceCandidateType {
487    fn from(value: CandidateType) -> Self {
488        match value {
489            CandidateType::Host => crate::ffi::RICE_CANDIDATE_TYPE_HOST,
490            CandidateType::PeerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
491            CandidateType::ServerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
492            CandidateType::Relayed => crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
493        }
494    }
495}
496
497/// The type of TCP candidate
498#[derive(Debug, Copy, Clone, PartialEq, Eq)]
499#[repr(u32)]
500pub enum TcpType {
501    /// Not a TCP candidate.
502    None = crate::ffi::RICE_TCP_TYPE_NONE,
503    /// The candidate address will connect to a remote address.
504    Active = crate::ffi::RICE_TCP_TYPE_ACTIVE,
505    /// The candidate will listen for incominng TCP connections.
506    Passive = crate::ffi::RICE_TCP_TYPE_PASSIVE,
507    /// Simultaneous open.  The candidate will both listen for incoming connections, and connect to
508    /// remote addresses.
509    So = crate::ffi::RICE_TCP_TYPE_SO,
510}
511
512impl From<crate::ffi::RiceTcpType> for TcpType {
513    fn from(value: crate::ffi::RiceTcpType) -> Self {
514        match value {
515            crate::ffi::RICE_TCP_TYPE_NONE => Self::None,
516            crate::ffi::RICE_TCP_TYPE_ACTIVE => Self::Active,
517            crate::ffi::RICE_TCP_TYPE_PASSIVE => Self::Passive,
518            crate::ffi::RICE_TCP_TYPE_SO => Self::So,
519            val => panic!("Unknown tcp type value {val:x?}"),
520        }
521    }
522}
523
524impl From<TcpType> for crate::ffi::RiceTcpType {
525    fn from(value: TcpType) -> Self {
526        match value {
527            TcpType::None => crate::ffi::RICE_TCP_TYPE_NONE,
528            TcpType::Active => crate::ffi::RICE_TCP_TYPE_ACTIVE,
529            TcpType::Passive => crate::ffi::RICE_TCP_TYPE_PASSIVE,
530            TcpType::So => crate::ffi::RICE_TCP_TYPE_SO,
531        }
532    }
533}
534
535/// The transport type.
536#[derive(Debug, Copy, Clone, PartialEq, Eq)]
537pub enum TransportType {
538    /// UDP transport.
539    Udp,
540    /// TCP transport.
541    Tcp,
542}
543
544impl core::fmt::Display for TransportType {
545    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546        write!(f, "{self:?}")
547    }
548}
549
550impl From<crate::ffi::RiceTransportType> for TransportType {
551    fn from(value: crate::ffi::RiceTransportType) -> Self {
552        match value {
553            crate::ffi::RICE_TRANSPORT_TYPE_UDP => Self::Udp,
554            crate::ffi::RICE_TRANSPORT_TYPE_TCP => Self::Tcp,
555            _ => panic!("Unknown transport type value"),
556        }
557    }
558}
559
560impl From<TransportType> for crate::ffi::RiceTransportType {
561    fn from(value: TransportType) -> Self {
562        match value {
563            TransportType::Udp => crate::ffi::RICE_TRANSPORT_TYPE_UDP,
564            TransportType::Tcp => crate::ffi::RICE_TRANSPORT_TYPE_TCP,
565        }
566    }
567}
568
569impl core::str::FromStr for TransportType {
570    type Err = ParseTransportTypeError;
571
572    fn from_str(s: &str) -> Result<Self, Self::Err> {
573        if s.eq_ignore_ascii_case("udp") {
574            Ok(Self::Udp)
575        } else if s.eq_ignore_ascii_case("tcp") {
576            Ok(Self::Tcp)
577        } else {
578            Err(ParseTransportTypeError::UnknownTransport)
579        }
580    }
581}
582
583/// Errors when parsing a [`TransportType`]
584#[derive(Copy, Clone, Debug, thiserror::Error, PartialEq, Eq)]
585pub enum ParseTransportTypeError {
586    /// An unknown transport value was provided
587    #[error("Unknown transport value was provided")]
588    UnknownTransport,
589}
590
591/// Paired local and remote candidate
592#[derive(Debug, Clone, PartialEq, Eq)]
593pub struct CandidatePair {
594    /// The local [`Candidate`]
595    pub local: CandidateOwned,
596    /// The remote [`Candidate`]
597    pub remote: CandidateOwned,
598}
599
600impl CandidatePair {
601    /// Create a new [`CandidatePair`]
602    pub fn new(local: CandidateOwned, remote: CandidateOwned) -> Self {
603        Self { local, remote }
604    }
605}
606
607#[cfg(test)]
608mod tests {
609    use super::*;
610
611    #[test]
612    fn candidate_type() {
613        let _log = crate::tests::test_init_log();
614
615        for (c, r) in [
616            (crate::ffi::RICE_CANDIDATE_TYPE_HOST, CandidateType::Host),
617            (
618                crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
619                CandidateType::ServerReflexive,
620            ),
621            (
622                crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
623                CandidateType::PeerReflexive,
624            ),
625            (
626                crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
627                CandidateType::Relayed,
628            ),
629        ] {
630            assert_eq!(CandidateType::from(c), r);
631            assert_eq!(crate::ffi::RiceCandidateType::from(r), c);
632        }
633    }
634
635    #[test]
636    #[should_panic = "Unknown candidate type"]
637    fn candidate_type_out_of_range() {
638        let _log = crate::tests::test_init_log();
639        let _ = CandidateType::from(u32::MAX);
640    }
641
642    #[test]
643    fn tcp_type() {
644        let _log = crate::tests::test_init_log();
645
646        for (c, r) in [
647            (crate::ffi::RICE_TCP_TYPE_NONE, TcpType::None),
648            (crate::ffi::RICE_TCP_TYPE_ACTIVE, TcpType::Active),
649            (crate::ffi::RICE_TCP_TYPE_PASSIVE, TcpType::Passive),
650            (crate::ffi::RICE_TCP_TYPE_SO, TcpType::So),
651        ] {
652            assert_eq!(TcpType::from(c), r);
653            assert_eq!(crate::ffi::RiceTcpType::from(r), c);
654        }
655    }
656
657    #[test]
658    #[should_panic = "Unknown tcp type value"]
659    fn tcp_type_out_of_range() {
660        let _log = crate::tests::test_init_log();
661        let _ = TcpType::from(u32::MAX);
662    }
663
664    #[test]
665    fn transport_type() {
666        let _log = crate::tests::test_init_log();
667
668        for (c, r) in [
669            (crate::ffi::RICE_TRANSPORT_TYPE_UDP, TransportType::Udp),
670            (crate::ffi::RICE_TRANSPORT_TYPE_TCP, TransportType::Tcp),
671        ] {
672            assert_eq!(TransportType::from(c), r);
673            assert_eq!(crate::ffi::RiceTransportType::from(r), c);
674        }
675    }
676
677    #[test]
678    #[should_panic = "Unknown transport type value"]
679    fn transport_type_out_of_range() {
680        let _log = crate::tests::test_init_log();
681        let _ = TransportType::from(u32::MAX);
682    }
683
684    #[test]
685    fn parse_candidate_error() {
686        let _log = crate::tests::test_init_log();
687
688        for (c, r) in [
689            (crate::ffi::RICE_PARSE_CANDIDATE_ERROR_SUCCESS, Ok(())),
690            (
691                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE,
692                Err(ParseCandidateError::NotCandidate),
693            ),
694            (
695                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION,
696                Err(ParseCandidateError::BadFoundation),
697            ),
698            (
699                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID,
700                Err(ParseCandidateError::BadComponentId),
701            ),
702            (
703                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE,
704                Err(ParseCandidateError::BadTransportType),
705            ),
706            (
707                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY,
708                Err(ParseCandidateError::BadPriority),
709            ),
710            (
711                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS,
712                Err(ParseCandidateError::BadAddress),
713            ),
714            (
715                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE,
716                Err(ParseCandidateError::BadCandidateType),
717            ),
718            (
719                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION,
720                Err(ParseCandidateError::BadExtension),
721            ),
722            (
723                crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED,
724                Err(ParseCandidateError::Malformed),
725            ),
726        ] {
727            assert_eq!(ParseCandidateError::from_c(c), r);
728        }
729    }
730
731    #[test]
732    #[should_panic = "Unknown RiceParseCandidateError value"]
733    fn parse_candidate_error_out_of_range() {
734        let _log = crate::tests::test_init_log();
735        let _ = ParseCandidateError::from_c(i32::MAX);
736    }
737
738    #[test]
739    fn transport_type_from_str() {
740        use core::str::FromStr;
741        let _log = crate::tests::test_init_log();
742
743        for (s, r) in [
744            ("udp", Ok(TransportType::Udp)),
745            ("UDP", Ok(TransportType::Udp)),
746            ("tcp", Ok(TransportType::Tcp)),
747            ("TCP", Ok(TransportType::Tcp)),
748            ("random", Err(ParseTransportTypeError::UnknownTransport)),
749        ] {
750            assert_eq!(TransportType::from_str(s), r);
751        }
752    }
753
754    fn base_address() -> crate::Address {
755        "127.0.0.1:1000".parse().unwrap()
756    }
757
758    fn address() -> crate::Address {
759        "127.0.0.2:2000".parse().unwrap()
760    }
761
762    fn related_address() -> crate::Address {
763        "127.0.0.3:3000".parse().unwrap()
764    }
765
766    #[test]
767    fn candidate_build() {
768        let _log = crate::tests::test_init_log();
769
770        let base = base_address();
771        let addr = address();
772        let related = related_address();
773        let cand = Candidate::builder(
774            1,
775            CandidateType::PeerReflexive,
776            TransportType::Tcp,
777            "foundation",
778            addr.clone(),
779        )
780        .base_address(base.clone())
781        .related_address(related.clone())
782        .tcp_type(TcpType::Active)
783        .priority(1234)
784        .build();
785        assert_eq!(cand.component_id(), 1);
786        assert_eq!(cand.candidate_type(), CandidateType::PeerReflexive);
787        assert_eq!(cand.transport(), TransportType::Tcp);
788        assert_eq!(cand.foundation(), "foundation");
789        assert_eq!(cand.address(), addr);
790        assert_eq!(cand.base_address(), base);
791        assert_eq!(cand.related_address(), Some(related));
792        assert_eq!(cand.tcp_type(), TcpType::Active);
793        assert_eq!(cand.priority(), 1234);
794
795        let cand_clone = cand.clone();
796        assert_eq!(cand, cand_clone);
797    }
798
799    #[test]
800    fn candidate_to_owned() {
801        let _log = crate::tests::test_init_log();
802
803        let base = base_address();
804        let addr = address();
805        let related = related_address();
806        let cand = Candidate::builder(
807            1,
808            CandidateType::PeerReflexive,
809            TransportType::Tcp,
810            "foundation",
811            addr.clone(),
812        )
813        .base_address(base.clone())
814        .related_address(related.clone())
815        .tcp_type(TcpType::Active)
816        .priority(1234)
817        .build();
818        let owned = cand.to_owned();
819        assert_eq!(cand, owned);
820        assert_eq!(owned, cand);
821    }
822
823    #[test]
824    fn candidate_string() {
825        let _log = crate::tests::test_init_log();
826
827        let addr = address();
828        let related = related_address();
829        let cand = Candidate::builder(
830            1,
831            CandidateType::PeerReflexive,
832            TransportType::Tcp,
833            "foundation",
834            addr.clone(),
835        )
836        .related_address(related.clone())
837        .tcp_type(TcpType::Active)
838        .priority(1234)
839        .build();
840
841        let s = cand.to_sdp_string();
842        println!("{s}");
843        let parsed = Candidate::from_sdp_string(&s).unwrap();
844        assert_eq!(parsed, cand);
845
846        let owned = cand.to_owned();
847        let s = cand.to_sdp_string();
848        println!("{s}");
849        let parsed = Candidate::from_sdp_string(&s).unwrap();
850        assert_eq!(parsed, owned);
851    }
852}