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, 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    ///     TransportType::Udp,
42    ///     &[AddressFamily::IPV4],
43    ///     None,
44    /// );
45    /// assert_eq!(config.client_transport(), TransportType::Udp);
46    /// assert_eq!(config.addr(), server_addr);
47    /// // FIXME
48    /// //assert_eq!(config.credentials().username(), credentials.username());
49    /// ```
50    pub fn new(
51        client_transport: TransportType,
52        turn_server: crate::Address,
53        credentials: TurnCredentials,
54        allocation_transport: TransportType,
55        families: &[AddressFamily],
56        tls_config: Option<TurnTlsConfig>,
57    ) -> Self {
58        unsafe {
59            let tls_config = if let Some(tls_config) = tls_config {
60                tls_config.into_c_full()
61            } else {
62                core::ptr::null_mut()
63            };
64            let families = families
65                .iter()
66                .map(|&family| family as u32)
67                .collect::<Vec<_>>();
68            let ffi = crate::ffi::rice_turn_config_new(
69                client_transport.into(),
70                const_override(turn_server.as_c()),
71                credentials.into_c_none(),
72                allocation_transport.into(),
73                families.len(),
74                families.as_ptr(),
75                tls_config,
76            );
77            Self { ffi }
78        }
79    }
80
81    /// The TLS configuration to use for connecting to this TURN server.
82    pub fn tls_config(&self) -> Option<TurnTlsConfig> {
83        unsafe {
84            let ret = crate::ffi::rice_turn_config_get_tls_config(self.ffi);
85            if ret.is_null() {
86                None
87            } else {
88                match crate::ffi::rice_tls_config_variant(ret) {
89                    #[cfg(feature = "openssl")]
90                    crate::ffi::RICE_TLS_VARIANT_OPENSSL => Some(TurnTlsConfig::Openssl(ret)),
91                    #[cfg(feature = "rustls")]
92                    crate::ffi::RICE_TLS_VARIANT_RUSTLS => Some(TurnTlsConfig::Rustls(ret)),
93                    _ => None,
94                }
95            }
96        }
97    }
98
99    /// The TURN server address to connect to.
100    pub fn addr(&self) -> crate::Address {
101        unsafe { crate::Address::from_c_full(crate::ffi::rice_turn_config_get_addr(self.ffi)) }
102    }
103
104    /// The [`TransportType`] between the client and the TURN server.
105    pub fn client_transport(&self) -> TransportType {
106        unsafe { crate::ffi::rice_turn_config_get_client_transport(self.ffi).into() }
107    }
108
109    /// The credentials for accessing the TURN server.
110    pub fn credentials(&self) -> TurnCredentials {
111        unsafe {
112            TurnCredentials::from_c_full(crate::ffi::rice_turn_config_get_credentials(self.ffi))
113        }
114    }
115
116    pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceTurnConfig {
117        let ret = self.ffi;
118        core::mem::forget(self);
119        ret
120    }
121}
122
123impl Clone for TurnConfig {
124    fn clone(&self) -> Self {
125        unsafe {
126            Self {
127                ffi: crate::ffi::rice_turn_config_ref(self.ffi),
128            }
129        }
130    }
131}
132
133impl Drop for TurnConfig {
134    fn drop(&mut self) {
135        unsafe {
136            crate::ffi::rice_turn_config_unref(self.ffi);
137        }
138    }
139}
140
141/// Configuration parameters for TURN use over (D)TLS.
142#[derive(Debug)]
143pub enum TurnTlsConfig {
144    /// Rustls variant for TLS configuration.
145    #[cfg(feature = "rustls")]
146    Rustls(*mut crate::ffi::RiceTlsConfig),
147    /// Openssl variant for TLS configuration.
148    #[cfg(feature = "openssl")]
149    Openssl(*mut crate::ffi::RiceTlsConfig),
150}
151
152impl Clone for TurnTlsConfig {
153    fn clone(&self) -> Self {
154        match self {
155            #[cfg(feature = "rustls")]
156            Self::Rustls(cfg) => unsafe { Self::Rustls(crate::ffi::rice_tls_config_ref(*cfg)) },
157            #[cfg(feature = "openssl")]
158            Self::Openssl(cfg) => unsafe { Self::Openssl(crate::ffi::rice_tls_config_ref(*cfg)) },
159        }
160    }
161}
162
163impl Drop for TurnTlsConfig {
164    fn drop(&mut self) {
165        match self {
166            #[cfg(feature = "rustls")]
167            Self::Rustls(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) },
168            #[cfg(feature = "openssl")]
169            Self::Openssl(cfg) => unsafe { crate::ffi::rice_tls_config_unref(*cfg) },
170        }
171    }
172}
173
174impl TurnTlsConfig {
175    /// Construct a new client Rustls TLS configuration with the specified server name.
176    #[cfg(feature = "rustls")]
177    pub fn new_rustls_with_dns(server_name: &str) -> Self {
178        let server_str = std::ffi::CString::new(server_name).unwrap();
179        unsafe {
180            Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_dns(
181                server_str.as_ptr(),
182            ))
183        }
184    }
185
186    /// Construct a new client Rustls TLS configuration with the specified ip.
187    #[cfg(feature = "rustls")]
188    pub fn new_rustls_with_ip(addr: &crate::Address) -> Self {
189        unsafe { Self::Rustls(crate::ffi::rice_tls_config_new_rustls_with_ip(addr.as_c())) }
190    }
191
192    /// Construct a new client OpenSSL TLS configuration with the specified transport.
193    #[cfg(feature = "openssl")]
194    pub fn new_openssl(transport: TransportType) -> Self {
195        unsafe { Self::Openssl(crate::ffi::rice_tls_config_new_openssl(transport.into())) }
196    }
197
198    pub(crate) fn into_c_full(self) -> *mut crate::ffi::RiceTlsConfig {
199        #[allow(unreachable_patterns)]
200        let ret = match self {
201            #[cfg(feature = "rustls")]
202            Self::Rustls(cfg) => cfg,
203            #[cfg(feature = "openssl")]
204            Self::Openssl(cfg) => cfg,
205            _ => core::ptr::null_mut(),
206        };
207        core::mem::forget(self);
208        ret
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    use core::net::SocketAddr;
217
218    fn turn_server_address() -> crate::Address {
219        "127.0.0.1:3478".parse::<SocketAddr>().unwrap().into()
220    }
221
222    fn turn_credentials() -> TurnCredentials {
223        TurnCredentials::new("tuser", "tpass")
224    }
225
226    #[test]
227    fn test_config_getter() {
228        let cfg = TurnConfig::new(
229            TransportType::Udp,
230            turn_server_address(),
231            turn_credentials(),
232            TransportType::Udp,
233            &[AddressFamily::IPV4],
234            None,
235        );
236        assert_eq!(cfg.addr(), turn_server_address());
237        assert_eq!(cfg.client_transport(), TransportType::Udp);
238        // TODO credentials
239        //assert_eq!(cfg.credentials().username(), turn_credentials().username());
240        assert!(cfg.tls_config().is_none());
241    }
242
243    #[cfg(feature = "rustls")]
244    mod rustls {
245        use super::*;
246        #[test]
247        fn test_rustls_roundtrip() {
248            let dns = "turn.example.com";
249            let cfg = TurnTlsConfig::new_rustls_with_dns(dns);
250            drop(cfg);
251            let addr = "127.0.0.1:3478".parse::<SocketAddr>().unwrap();
252            let _cfg = TurnTlsConfig::new_rustls_with_ip(&addr.into());
253        }
254
255        #[test]
256        fn test_rustls_getter() {
257            let dns = "turn.example.com";
258            let tls = TurnTlsConfig::new_rustls_with_dns(dns);
259            let cfg = TurnConfig::new(
260                TransportType::Tcp,
261                turn_server_address(),
262                turn_credentials(),
263                TransportType::Udp,
264                &[AddressFamily::IPV4],
265                Some(tls.clone()),
266            );
267            let retrieved = cfg.tls_config().unwrap();
268            assert!(matches!(retrieved, TurnTlsConfig::Rustls(_)));
269        }
270    }
271
272    #[cfg(feature = "openssl")]
273    mod openssl {
274        use super::*;
275        #[test]
276        fn test_openssl_roundtrip() {
277            let _cfg = TurnTlsConfig::new_openssl(TransportType::Udp);
278        }
279
280        #[test]
281        fn test_openssl_getter() {
282            let tls = TurnTlsConfig::new_openssl(TransportType::Udp);
283            let cfg = TurnConfig::new(
284                TransportType::Udp,
285                turn_server_address(),
286                turn_credentials(),
287                TransportType::Udp,
288                &[AddressFamily::IPV4],
289                Some(tls),
290            );
291            let retrieved = cfg.tls_config().unwrap();
292            assert!(matches!(retrieved, TurnTlsConfig::Openssl(_)));
293        }
294    }
295}