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 = -1,
75    /// Invalid foundation value.
76    BadFoundation = -2,
77    /// Invalid component id.
78    BadComponentId = -3,
79    /// Invalid transport type.
80    BadTransportType = -4,
81    /// Invalid priority value.
82    BadPriority = -5,
83    /// Invalid network address.
84    BadAddress = -6,
85    /// Invalid candidate type.
86    BadCandidateType = -7,
87    /// Invalid extension format.
88    BadExtension = -8,
89    /// Data is not well formed.
90    Malformed = -9,
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(Debug)]
137pub struct Candidate {
138    ffi: crate::ffi::RiceCandidate,
139}
140
141unsafe impl Send for Candidate {}
142unsafe impl Sync for Candidate {}
143
144impl PartialEq<Candidate> for Candidate {
145    fn eq(&self, other: &Candidate) -> bool {
146        unsafe { crate::ffi::rice_candidate_eq(&self.ffi, &other.ffi) }
147    }
148}
149
150impl PartialEq<CandidateOwned> for Candidate {
151    fn eq(&self, other: &CandidateOwned) -> bool {
152        unsafe { crate::ffi::rice_candidate_eq(&self.ffi, other.ffi) }
153    }
154}
155
156impl Eq for Candidate {}
157
158impl Clone for Candidate {
159    fn clone(&self) -> Self {
160        unsafe {
161            let mut ret = Self {
162                ffi: crate::ffi::RiceCandidate::zeroed(),
163            };
164            crate::ffi::rice_candidate_copy_into(&self.ffi, &mut ret.ffi);
165            ret
166        }
167    }
168}
169
170impl CandidateApi for Candidate {}
171impl sealed::CandidateAsC for Candidate {
172    fn as_c(&self) -> *const crate::ffi::RiceCandidate {
173        &self.ffi
174    }
175}
176
177impl Drop for Candidate {
178    fn drop(&mut self) {
179        unsafe { crate::ffi::rice_candidate_clear(&mut self.ffi) }
180    }
181}
182
183impl Candidate {
184    /// Builds the candidate
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// # use rice_c::candidate::*;
190    /// # use rice_c::Address;
191    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
192    /// let candidate = Candidate::builder(
193    ///     1,
194    ///     CandidateType::Host,
195    ///     TransportType::Udp,
196    ///     "foundation",
197    ///     addr,
198    /// )
199    /// .priority(1234)
200    /// .build();
201    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
202    /// ```
203    pub fn builder(
204        component_id: usize,
205        ctype: CandidateType,
206        ttype: TransportType,
207        foundation: &str,
208        address: crate::Address,
209    ) -> CandidateBuilder {
210        unsafe {
211            let foundation = CString::new(foundation).unwrap();
212            let mut ret = CandidateBuilder {
213                ffi: crate::ffi::RiceCandidate::zeroed(),
214            };
215            let address = address.into_c_full();
216            let res = crate::ffi::rice_candidate_init(
217                &mut ret.ffi,
218                component_id,
219                ctype.into(),
220                ttype.into(),
221                foundation.as_ptr(),
222                address,
223            );
224            if res != crate::ffi::RICE_ERROR_SUCCESS {
225                let _address = crate::Address::from_c_full(address);
226                panic!("Failed to crate ICE candidate!");
227            }
228            ret
229        }
230    }
231
232    pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
233        &self.ffi
234    }
235
236    pub(crate) unsafe fn from_c_none(candidate: *const crate::ffi::RiceCandidate) -> Self {
237        unsafe {
238            let mut ret = Self {
239                ffi: crate::ffi::RiceCandidate::zeroed(),
240            };
241            crate::ffi::rice_candidate_copy_into(candidate, &mut ret.ffi);
242            ret
243        }
244    }
245
246    pub(crate) fn from_c_full(candidate: crate::ffi::RiceCandidate) -> Self {
247        Self { ffi: candidate }
248    }
249
250    /// Copy this candidate into a heap allocated [`CandidateOwned`].
251    pub fn to_owned(&self) -> CandidateOwned {
252        unsafe {
253            CandidateOwned {
254                ffi: crate::ffi::rice_candidate_copy(&self.ffi),
255            }
256        }
257    }
258
259    /// Serialize this candidate to a string for use in SDP
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// # use rice_c::candidate::*;
265    /// # use rice_c::Address;
266    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
267    /// let candidate = Candidate::builder(
268    ///     1,
269    ///     CandidateType::Host,
270    ///     TransportType::Udp,
271    ///     "foundation",
272    ///     addr,
273    /// )
274    /// .priority(1234)
275    /// .build();
276    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
277    /// ```
278    pub fn to_sdp_string(&self) -> String {
279        unsafe {
280            let res = crate::ffi::rice_candidate_to_sdp_string(&self.ffi);
281            let s = CStr::from_ptr(res);
282            let ret = s.to_str().unwrap().to_owned();
283            crate::ffi::rice_string_free(res);
284            ret
285        }
286    }
287
288    /// Parse an SDP candidate string into a candidate.
289    pub fn from_sdp_string(s: &str) -> Result<Candidate, ParseCandidateError> {
290        let cand_str = std::ffi::CString::new(s).unwrap();
291        unsafe {
292            let mut ret = Candidate {
293                ffi: crate::ffi::RiceCandidate::zeroed(),
294            };
295            let res =
296                crate::ffi::rice_candidate_init_from_sdp_string(&mut ret.ffi, cand_str.as_ptr());
297            ParseCandidateError::from_c(res)?;
298            Ok(ret)
299        }
300    }
301}
302
303/// An ICE candidate.
304///
305/// Backed inside a heap allocation.
306#[derive(Eq)]
307pub struct CandidateOwned {
308    ffi: *mut crate::ffi::RiceCandidate,
309}
310
311unsafe impl Send for CandidateOwned {}
312unsafe impl Sync for CandidateOwned {}
313
314impl core::fmt::Debug for CandidateOwned {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        unsafe {
317            let mut dbg = f.debug_struct("Candidate");
318            let dbg2 = &mut dbg;
319            dbg2.field("ffi", &self.ffi);
320            if !self.ffi.is_null() {
321                dbg2.field("value", &*self.ffi);
322            }
323            dbg.finish()
324        }
325    }
326}
327
328impl PartialEq<CandidateOwned> for CandidateOwned {
329    fn eq(&self, other: &CandidateOwned) -> bool {
330        unsafe { crate::ffi::rice_candidate_eq(self.ffi, other.ffi) }
331    }
332}
333
334impl PartialEq<Candidate> for CandidateOwned {
335    fn eq(&self, other: &Candidate) -> bool {
336        unsafe { crate::ffi::rice_candidate_eq(self.ffi, &other.ffi) }
337    }
338}
339
340impl Clone for CandidateOwned {
341    fn clone(&self) -> Self {
342        unsafe {
343            Self {
344                ffi: crate::ffi::rice_candidate_copy(self.ffi),
345            }
346        }
347    }
348}
349
350impl Drop for CandidateOwned {
351    fn drop(&mut self) {
352        unsafe { crate::ffi::rice_candidate_free(self.ffi) }
353    }
354}
355
356impl CandidateApi for CandidateOwned {}
357impl sealed::CandidateAsC for CandidateOwned {
358    fn as_c(&self) -> *const crate::ffi::RiceCandidate {
359        self.ffi
360    }
361}
362
363impl CandidateOwned {
364    pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
365        self.ffi
366    }
367
368    /// Serialize this candidate to a string for use in SDP
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// # use rice_c::candidate::*;
374    /// # use rice_c::Address;
375    /// let addr: Address = "127.0.0.1:2345".parse().unwrap();
376    /// let candidate = Candidate::builder(
377    ///     1,
378    ///     CandidateType::Host,
379    ///     TransportType::Udp,
380    ///     "foundation",
381    ///     addr,
382    /// )
383    /// .priority(1234)
384    /// .build();
385    /// assert_eq!(candidate.to_sdp_string(), "a=candidate:foundation 1 UDP 1234 127.0.0.1 2345 typ host")
386    /// ```
387    pub fn to_sdp_string(&self) -> String {
388        unsafe {
389            let res = crate::ffi::rice_candidate_to_sdp_string(self.ffi);
390            let s = CStr::from_ptr(res);
391            let ret = s.to_str().unwrap().to_owned();
392            crate::ffi::rice_string_free(res);
393            ret
394        }
395    }
396}
397
398/// A builder for a [`Candidate`]
399#[derive(Debug)]
400pub struct CandidateBuilder {
401    ffi: crate::ffi::RiceCandidate,
402}
403
404impl CandidateBuilder {
405    /// Consume this builder a construct a new [`Candidate`].
406    pub fn build(self) -> Candidate {
407        Candidate { ffi: self.ffi }
408    }
409
410    /// Specify the priority of the to be built candidate
411    pub fn priority(mut self, priority: u32) -> Self {
412        unsafe {
413            crate::ffi::rice_candidate_set_priority(&mut self.ffi, priority);
414            self
415        }
416    }
417
418    /// Specify the base address of the to be built candidate
419    pub fn base_address(mut self, base: crate::Address) -> Self {
420        unsafe {
421            crate::ffi::rice_candidate_set_base_address(&mut self.ffi, base.into_c_full());
422            self
423        }
424    }
425
426    /// Specify the related address of the to be built candidate
427    pub fn related_address(mut self, related: crate::Address) -> Self {
428        unsafe {
429            crate::ffi::rice_candidate_set_related_address(&mut self.ffi, related.into_c_full());
430            self
431        }
432    }
433
434    /// Specify the type of TCP connection of the to be built candidate
435    ///
436    /// - This will panic at build() time if the transport type is not [`TransportType::Tcp`].
437    /// - This will panic at build() time if this function is not called but the
438    ///   transport type is [`TransportType::Tcp`]
439    pub fn tcp_type(mut self, typ: TcpType) -> Self {
440        unsafe {
441            if self.ffi.transport_type != TransportType::Tcp.into() && typ != TcpType::None {
442                panic!("Attempt made to set the TcpType of a non-TCP candidate");
443            }
444            crate::ffi::rice_candidate_set_tcp_type(&mut self.ffi, typ.into());
445            self
446        }
447    }
448
449    // TODO: extensions
450}
451
452/// The type of the candidate
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454#[repr(u32)]
455pub enum CandidateType {
456    /// The candidate is a local network interface
457    Host = crate::ffi::RICE_CANDIDATE_TYPE_HOST,
458    /// The candidate was discovered from incoming data
459    PeerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
460    /// The candidate was discovered by asking an external server (STUN/TURN)
461    ServerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
462    /// The candidate will relay all data through an external server (TURN).
463    Relayed = crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
464}
465
466impl From<crate::ffi::RiceCandidateType> for CandidateType {
467    fn from(value: crate::ffi::RiceCandidateType) -> Self {
468        match value {
469            crate::ffi::RICE_CANDIDATE_TYPE_HOST => Self::Host,
470            crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE => Self::PeerReflexive,
471            crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE => Self::ServerReflexive,
472            crate::ffi::RICE_CANDIDATE_TYPE_RELAYED => Self::Relayed,
473            val => panic!("Unknown candidate type {val:x?}"),
474        }
475    }
476}
477
478impl From<CandidateType> for crate::ffi::RiceCandidateType {
479    fn from(value: CandidateType) -> Self {
480        match value {
481            CandidateType::Host => crate::ffi::RICE_CANDIDATE_TYPE_HOST,
482            CandidateType::PeerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
483            CandidateType::ServerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
484            CandidateType::Relayed => crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
485        }
486    }
487}
488
489/// The type of TCP candidate
490#[derive(Debug, Copy, Clone, PartialEq, Eq)]
491#[repr(u32)]
492pub enum TcpType {
493    /// Not a TCP candidate.
494    None = crate::ffi::RICE_TCP_TYPE_NONE,
495    /// The candidate address will connect to a remote address.
496    Active = crate::ffi::RICE_TCP_TYPE_ACTIVE,
497    /// The candidate will listen for incominng TCP connections.
498    Passive = crate::ffi::RICE_TCP_TYPE_PASSIVE,
499    /// Simultaneous open.  The candidate will both listen for incoming connections, and connect to
500    /// remote addresses.
501    So = crate::ffi::RICE_TCP_TYPE_SO,
502}
503
504impl From<crate::ffi::RiceTcpType> for TcpType {
505    fn from(value: crate::ffi::RiceTcpType) -> Self {
506        match value {
507            crate::ffi::RICE_TCP_TYPE_NONE => Self::None,
508            crate::ffi::RICE_TCP_TYPE_ACTIVE => Self::Active,
509            crate::ffi::RICE_TCP_TYPE_PASSIVE => Self::Passive,
510            crate::ffi::RICE_TCP_TYPE_SO => Self::So,
511            val => panic!("Unknown RiceTcpType valyue {val:x?}"),
512        }
513    }
514}
515
516impl From<TcpType> for crate::ffi::RiceTcpType {
517    fn from(value: TcpType) -> Self {
518        match value {
519            TcpType::None => crate::ffi::RICE_TCP_TYPE_NONE,
520            TcpType::Active => crate::ffi::RICE_TCP_TYPE_ACTIVE,
521            TcpType::Passive => crate::ffi::RICE_TCP_TYPE_PASSIVE,
522            TcpType::So => crate::ffi::RICE_TCP_TYPE_SO,
523        }
524    }
525}
526
527/// The transport type.
528#[derive(Debug, Copy, Clone, PartialEq, Eq)]
529pub enum TransportType {
530    /// UDP transport.
531    Udp,
532    /// TCP transport.
533    Tcp,
534}
535
536impl core::fmt::Display for TransportType {
537    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538        write!(f, "{self:?}")
539    }
540}
541
542impl From<crate::ffi::RiceTransportType> for TransportType {
543    fn from(value: crate::ffi::RiceTransportType) -> Self {
544        match value {
545            crate::ffi::RICE_TRANSPORT_TYPE_UDP => Self::Udp,
546            crate::ffi::RICE_TRANSPORT_TYPE_TCP => Self::Tcp,
547            _ => panic!("Unknown RiceTransportType value"),
548        }
549    }
550}
551
552impl From<TransportType> for crate::ffi::RiceTransportType {
553    fn from(value: TransportType) -> Self {
554        match value {
555            TransportType::Udp => crate::ffi::RICE_TRANSPORT_TYPE_UDP,
556            TransportType::Tcp => crate::ffi::RICE_TRANSPORT_TYPE_TCP,
557        }
558    }
559}
560
561impl core::str::FromStr for TransportType {
562    type Err = ParseTransportTypeError;
563
564    fn from_str(s: &str) -> Result<Self, Self::Err> {
565        if s.eq_ignore_ascii_case("udp") {
566            Ok(Self::Udp)
567        } else if s.eq_ignore_ascii_case("tcp") {
568            Ok(Self::Tcp)
569        } else {
570            Err(ParseTransportTypeError::UnknownTransport)
571        }
572    }
573}
574
575/// Errors when parsing a [`TransportType`]
576#[derive(Debug, thiserror::Error)]
577pub enum ParseTransportTypeError {
578    /// An unknown transport value was provided
579    #[error("Unknown transport value was provided")]
580    UnknownTransport,
581}
582
583/// Paired local and remote candidate
584#[derive(Debug, Clone, PartialEq, Eq)]
585pub struct CandidatePair {
586    /// The local [`Candidate`]
587    pub local: CandidateOwned,
588    /// The remote [`Candidate`]
589    pub remote: CandidateOwned,
590}
591
592impl CandidatePair {
593    /// Create a new [`CandidatePair`]
594    pub fn new(local: CandidateOwned, remote: CandidateOwned) -> Self {
595        Self { local, remote }
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    fn base_address() -> crate::Address {
604        "127.0.0.1:1000".parse().unwrap()
605    }
606
607    fn address() -> crate::Address {
608        "127.0.0.2:2000".parse().unwrap()
609    }
610
611    fn related_address() -> crate::Address {
612        "127.0.0.3:3000".parse().unwrap()
613    }
614
615    #[test]
616    fn candidate_build() {
617        let _log = crate::tests::test_init_log();
618
619        let base = base_address();
620        let addr = address();
621        let related = related_address();
622        let cand = Candidate::builder(
623            1,
624            CandidateType::PeerReflexive,
625            TransportType::Tcp,
626            "foundation",
627            addr.clone(),
628        )
629        .base_address(base.clone())
630        .related_address(related.clone())
631        .tcp_type(TcpType::Active)
632        .build();
633        assert_eq!(cand.component_id(), 1);
634        assert_eq!(cand.candidate_type(), CandidateType::PeerReflexive);
635        assert_eq!(cand.transport(), TransportType::Tcp);
636        assert_eq!(cand.foundation(), "foundation");
637        assert_eq!(cand.address(), addr);
638        assert_eq!(cand.base_address(), base);
639        assert_eq!(cand.related_address(), Some(related));
640        assert_eq!(cand.tcp_type(), TcpType::Active);
641
642        let cand_clone = cand.clone();
643        assert_eq!(cand, cand_clone);
644    }
645}