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