Skip to main content

rice_c/
turn.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//! TURN module.
12
13use crate::candidate::TransportType;
14use crate::{AddressFamily, Feature, IntegrityAlgorithm, const_override};
15
16pub use crate::stream::Credentials as TurnCredentials;
17
18/// Configuration for a particular TURN server connection.
19#[derive(Debug)]
20pub struct TurnConfig {
21    ffi: *mut crate::ffi::RiceTurnConfig,
22}
23
24unsafe impl Send for TurnConfig {}
25
26impl TurnConfig {
27    /// Create a new [`TurnConfig`] from the provided details.
28    ///
29    /// # Examples
30    /// ```
31    /// # use rice_c::AddressFamily;
32    /// # use rice_c::turn::{TurnConfig, TurnCredentials};
33    /// # use rice_c::candidate::TransportType;
34    /// # use core::net::SocketAddr;
35    /// let credentials = TurnCredentials::new("user", "pass");
36    /// let server_addr = rice_c::Address::from("127.0.0.1:3478".parse::<SocketAddr>().unwrap());
37    /// let config = TurnConfig::new(
38    ///     TransportType::Udp,
39    ///     server_addr.clone(),
40    ///     credentials.clone(),
41    /// );
42    /// assert_eq!(config.client_transport(), TransportType::Udp);
43    /// assert_eq!(config.addr(), server_addr);
44    /// // FIXME
45    /// //assert_eq!(config.credentials().username(), credentials.username());
46    /// ```
47    pub fn new(
48        client_transport: TransportType,
49        turn_server: crate::Address,
50        credentials: TurnCredentials,
51    ) -> Self {
52        unsafe {
53            let ffi = crate::ffi::rice_turn_config_new(
54                client_transport.into(),
55                const_override(turn_server.as_c()),
56                credentials.into_c_none(),
57            );
58            Self { ffi }
59        }
60    }
61
62    /// The TLS configuration to use for connecting to this TURN server.
63    pub fn tls_config(&self) -> Option<TurnTlsConfig> {
64        unsafe {
65            let ret = crate::ffi::rice_turn_config_get_tls_config(self.ffi);
66            if ret.is_null() {
67                None
68            } else {
69                match crate::ffi::rice_tls_config_variant(ret) {
70                    #[cfg(feature = "openssl")]
71                    crate::ffi::RICE_TLS_VARIANT_OPENSSL => Some(TurnTlsConfig::Openssl(ret)),
72                    #[cfg(feature = "rustls")]
73                    crate::ffi::RICE_TLS_VARIANT_RUSTLS => Some(TurnTlsConfig::Rustls(ret)),
74                    _ => None,
75                }
76            }
77        }
78    }
79
80    /// The TLS configuration to use for connecting to this TURN server.
81    pub fn set_tls_config(&mut self, tls_config: TurnTlsConfig) {
82        unsafe {
83            crate::ffi::rice_turn_config_set_tls_config(self.ffi, tls_config.as_c());
84        }
85    }
86
87    /// The TURN server address to connect to.
88    pub fn addr(&self) -> crate::Address {
89        unsafe { crate::Address::from_c_full(crate::ffi::rice_turn_config_get_addr(self.ffi)) }
90    }
91
92    /// The [`TransportType`] between the client and the TURN server.
93    pub fn client_transport(&self) -> TransportType {
94        unsafe { crate::ffi::rice_turn_config_get_client_transport(self.ffi).into() }
95    }
96
97    /// Set the allocation transport requested from the TURN server.
98    pub fn set_allocation_transport(&mut self, allocation_transport: TransportType) {
99        unsafe {
100            crate::ffi::rice_turn_config_set_allocation_transport(
101                self.ffi,
102                allocation_transport.into(),
103            );
104        }
105    }
106
107    /// Retrieve the allocation transport requested.
108    pub fn allocation_transport(&self) -> TransportType {
109        unsafe { crate::ffi::rice_turn_config_get_allocation_transport(self.ffi).into() }
110    }
111
112    /// Add an [`AddressFamily`] that will be requested.
113    ///
114    /// Duplicate [`AddressFamily`]s are ignored.
115    pub fn add_address_family(&mut self, family: AddressFamily) {
116        unsafe {
117            crate::ffi::rice_turn_config_add_address_family(self.ffi, family.into());
118        }
119    }
120
121    /// Set the [`AddressFamily`] that will be requested.
122    ///
123    /// This will override all previously set [`AddressFamily`]s.
124    pub fn set_address_family(&mut self, family: AddressFamily) {
125        unsafe {
126            crate::ffi::rice_turn_config_set_address_family(self.ffi, family.into());
127        }
128    }
129
130    /// Retrieve the [`AddressFamily`]s that are requested.
131    pub fn address_families(&self) -> Vec<AddressFamily> {
132        unsafe {
133            let mut len = 0;
134            crate::ffi::rice_turn_config_get_address_families(
135                self.ffi,
136                &mut len,
137                core::ptr::null_mut(),
138            );
139            let mut ret = vec![AddressFamily::IPV4; len];
140            crate::ffi::rice_turn_config_get_address_families(
141                self.ffi,
142                &mut len,
143                ret.as_mut_ptr() as _,
144            );
145            ret.resize(len, AddressFamily::IPV4);
146            ret
147        }
148    }
149
150    /// The credentials for accessing the TURN server.
151    pub fn credentials(&self) -> TurnCredentials {
152        unsafe {
153            TurnCredentials::from_c_full(crate::ffi::rice_turn_config_get_credentials(self.ffi))
154        }
155    }
156
157    /// Add a supported integrity algorithm that could be used.
158    pub fn add_supported_integrity(&mut self, integrity: IntegrityAlgorithm) {
159        unsafe {
160            crate::ffi::rice_turn_config_add_supported_integrity(self.ffi, integrity.into());
161        }
162    }
163
164    /// Set the supported integrity algorithm used.
165    pub fn set_supported_integrity(&mut self, integrity: IntegrityAlgorithm) {
166        unsafe {
167            crate::ffi::rice_turn_config_set_supported_integrity(self.ffi, integrity.into());
168        }
169    }
170
171    /// The supported integrity algorithms used.
172    pub fn supported_integrity(&self) -> Vec<IntegrityAlgorithm> {
173        unsafe {
174            let mut len = 0;
175            crate::ffi::rice_turn_config_get_supported_integrity(
176                self.ffi,
177                &mut len,
178                core::ptr::null_mut(),
179            );
180            let mut ret = vec![IntegrityAlgorithm::Sha1; len];
181            crate::ffi::rice_turn_config_get_supported_integrity(
182                self.ffi,
183                &mut len,
184                ret.as_mut_ptr() as _,
185            );
186            ret.resize(len, IntegrityAlgorithm::Sha1);
187            ret
188        }
189    }
190
191    /// Set whether anonymous username usage is required.
192    ///
193    /// A value of `Required` requires the server to support RFC 8489 and the `Userhash` attribute.
194    pub fn set_anonymous_username(&mut self, anon: Feature) {
195        unsafe {
196            crate::ffi::rice_turn_config_set_anonymous_username(self.ffi, anon as _);
197        }
198    }
199
200    /// Whether anonymous username usage is required.
201    ///
202    /// A value of `Required` requires the server to support RFC 8489 and the `Userhash` attribute.
203    pub fn anonymous_username(&self) -> Feature {
204        unsafe { crate::ffi::rice_turn_config_get_anonymous_username(self.ffi).into() }
205    }
206
207    pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceTurnConfig {
208        let ret = self.ffi;
209        core::mem::forget(self);
210        ret
211    }
212}
213
214impl Clone for TurnConfig {
215    fn clone(&self) -> Self {
216        unsafe {
217            Self {
218                ffi: crate::ffi::rice_turn_config_copy(self.ffi),
219            }
220        }
221    }
222}
223
224impl Drop for TurnConfig {
225    fn drop(&mut self) {
226        unsafe {
227            crate::ffi::rice_turn_config_free(self.ffi);
228        }
229    }
230}
231
232/// Configuration parameters for TURN use over (D)TLS.
233#[derive(Debug)]
234pub enum TurnTlsConfig {
235    /// Rustls variant for TLS configuration.
236    #[cfg(feature = "rustls")]
237    Rustls(*mut crate::ffi::RiceTlsConfig),
238    /// Openssl variant for TLS configuration.
239    #[cfg(feature = "openssl")]
240    Openssl(*mut crate::ffi::RiceTlsConfig),
241    /// Dimpl variant for TLS configuration.
242    #[cfg(feature = "dimpl")]
243    Dimpl(*mut crate::ffi::RiceTlsConfig),
244}
245
246impl Clone for TurnTlsConfig {
247    fn clone(&self) -> Self {
248        match self {
249            #[cfg(feature = "dimpl")]
250            Self::Dimpl(cfg) => unsafe { Self::Rustls(crate::ffi::rice_tls_config_ref(*cfg)) },
251            #[cfg(feature = "rustls")]
252            Self::Rustls(cfg) => unsafe { Self::Rustls(crate::ffi::rice_tls_config_ref(*cfg)) },
253            #[cfg(feature = "openssl")]
254            Self::Openssl(cfg) => unsafe { Self::Openssl(crate::ffi::rice_tls_config_ref(*cfg)) },
255        }
256    }
257}
258
259impl Drop for TurnTlsConfig {
260    fn drop(&mut self) {
261        match self {
262            #[cfg(feature = "dimpl")]
263            Self::Dimpl(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) },
264            #[cfg(feature = "rustls")]
265            Self::Rustls(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) },
266            #[cfg(feature = "openssl")]
267            Self::Openssl(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) },
268        }
269    }
270}
271
272impl TurnTlsConfig {
273    /// Construct a new client Rustls TLS configuration with the specified server name.
274    #[cfg(feature = "rustls")]
275    pub fn new_rustls_with_dns(server_name: &str) -> Self {
276        let server_str = std::ffi::CString::new(server_name).unwrap();
277        unsafe {
278            Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_dns(
279                server_str.as_ptr(),
280            ))
281        }
282    }
283
284    /// Construct a new client Rustls TLS configuration with the specified ip.
285    #[cfg(feature = "rustls")]
286    pub fn new_rustls_with_ip(addr: &crate::Address) -> Self {
287        unsafe { Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_ip(addr.as_c())) }
288    }
289
290    /// Construct a new client OpenSSL TLS configuration with the specified transport.
291    #[cfg(feature = "openssl")]
292    pub fn new_openssl(transport: TransportType) -> Self {
293        unsafe { Self::Openssl(crate::ffi::rice_tls_config_new_openssl(transport.into())) }
294    }
295
296    /// Construct a new client `dimpl` TLS configuration.
297    #[cfg(feature = "dimpl")]
298    pub fn new_dimpl() -> Self {
299        unsafe { Self::Dimpl(crate::ffi::rice_tls_config_new_dimpl()) }
300    }
301
302    pub(crate) fn as_c(&self) -> *mut crate::ffi::RiceTlsConfig {
303        #[allow(unreachable_patterns)]
304        let ret = match self {
305            #[cfg(feature = "dimpl")]
306            Self::Dimpl(cfg) => *cfg,
307            #[cfg(feature = "rustls")]
308            Self::Rustls(cfg) => *cfg,
309            #[cfg(feature = "openssl")]
310            Self::Openssl(cfg) => *cfg,
311            _ => core::ptr::null_mut(),
312        };
313        ret
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    use core::net::SocketAddr;
322
323    fn turn_server_address() -> crate::Address {
324        "127.0.0.1:3478".parse::<SocketAddr>().unwrap().into()
325    }
326
327    fn turn_credentials() -> TurnCredentials {
328        TurnCredentials::new("tuser", "tpass")
329    }
330
331    #[test]
332    fn test_config_getter() {
333        let mut cfg = TurnConfig::new(
334            TransportType::Udp,
335            turn_server_address(),
336            turn_credentials(),
337        );
338        assert_eq!(cfg.addr(), turn_server_address());
339        assert_eq!(cfg.client_transport(), TransportType::Udp);
340        // TODO credentials
341        //assert_eq!(cfg.credentials().username(), turn_credentials().username());
342        assert_eq!(&cfg.address_families(), &[AddressFamily::IPV4]);
343        assert_eq!(cfg.allocation_transport(), TransportType::Udp);
344        assert_eq!(&cfg.supported_integrity(), &[IntegrityAlgorithm::Sha1]);
345        assert_eq!(cfg.anonymous_username(), Feature::Auto);
346        assert!(cfg.tls_config().is_none());
347
348        for transport in [TransportType::Udp, TransportType::Tcp] {
349            cfg.set_allocation_transport(transport);
350            assert_eq!(cfg.allocation_transport(), transport);
351        }
352
353        cfg.add_address_family(AddressFamily::IPV6);
354        assert_eq!(
355            &cfg.address_families(),
356            &[AddressFamily::IPV4, AddressFamily::IPV6]
357        );
358        cfg.set_address_family(AddressFamily::IPV6);
359        assert_eq!(&cfg.address_families(), &[AddressFamily::IPV6]);
360
361        cfg.add_supported_integrity(IntegrityAlgorithm::Sha256);
362        assert_eq!(
363            &cfg.supported_integrity(),
364            &[IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256]
365        );
366        cfg.set_supported_integrity(IntegrityAlgorithm::Sha256);
367        assert_eq!(&cfg.supported_integrity(), &[IntegrityAlgorithm::Sha256]);
368
369        for feat in [Feature::Disabled, Feature::Auto, Feature::Required] {
370            cfg.set_anonymous_username(feat);
371            assert_eq!(cfg.anonymous_username(), feat);
372        }
373    }
374
375    #[cfg(feature = "rustls")]
376    mod rustls {
377        use super::*;
378        #[test]
379        fn test_rustls_roundtrip() {
380            let dns = "turn.example.com";
381            let cfg = TurnTlsConfig::new_rustls_with_dns(dns);
382            drop(cfg);
383            let addr = "127.0.0.1:3478".parse::<SocketAddr>().unwrap();
384            let _cfg = TurnTlsConfig::new_rustls_with_ip(&addr.into());
385        }
386
387        #[test]
388        fn test_rustls_getter() {
389            let dns = "turn.example.com";
390            let tls = TurnTlsConfig::new_rustls_with_dns(dns);
391            let mut cfg = TurnConfig::new(
392                TransportType::Tcp,
393                turn_server_address(),
394                turn_credentials(),
395            );
396            cfg.set_tls_config(tls.clone());
397            let retrieved = cfg.tls_config().unwrap();
398            assert!(matches!(retrieved, TurnTlsConfig::Rustls(_)));
399        }
400    }
401
402    #[cfg(feature = "openssl")]
403    mod openssl {
404        use super::*;
405        #[test]
406        fn test_openssl_roundtrip() {
407            let _cfg = TurnTlsConfig::new_openssl(TransportType::Udp);
408        }
409
410        #[test]
411        fn test_openssl_getter() {
412            let tls = TurnTlsConfig::new_openssl(TransportType::Udp);
413            let mut cfg = TurnConfig::new(
414                TransportType::Udp,
415                turn_server_address(),
416                turn_credentials(),
417            );
418            cfg.set_tls_config(tls.clone());
419            let retrieved = cfg.tls_config().unwrap();
420            assert!(matches!(retrieved, TurnTlsConfig::Openssl(_)));
421        }
422    }
423}