rings_transport/
ice_server.rs

1//! This module contains the IceServer structure.
2
3use std::str::FromStr;
4
5use serde::Deserialize;
6use serde::Serialize;
7use url::Url;
8
9use crate::error::IceServerError;
10
11/// WebRTC IceCredentialType enums.
12#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
13pub enum IceCredentialType {
14    /// IceCredentialType::Password describes username and password based
15    /// credentials as described in <https://tools.ietf.org/html/rfc5389>.
16    #[default]
17    Password,
18
19    /// IceCredentialType::Oauth describes token based credential as described
20    /// in <https://tools.ietf.org/html/rfc7635>.
21    /// Not supported in WebRTC 1.0 spec
22    Oauth,
23}
24
25/// This structure is used to validate whether the parameter in String format is valid
26/// and convert it to the format required by the underlying library.
27///
28/// In order to create Connection correctly, each Connection needs to implement the conversion
29/// from IceServer to its underlying library parameters.
30#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
31pub struct IceServer {
32    /// Urls is an array of URIs that can be used as STUN and TURN servers.
33    pub urls: Vec<String>,
34    /// The username to use if the server requires authorization.
35    pub username: String,
36    /// The secret to use for authentication with the
37    pub credential: String,
38    /// CredentialType indicates which type of credential the ICEAgent will use.
39    pub credential_type: IceCredentialType,
40}
41
42impl IceServer {
43    /// Convert String to `Vec<IceServer>`. Will split the string by `;` and parse each part.
44    pub fn vec_from_str(s: &str) -> Result<Vec<Self>, IceServerError> {
45        s.split(';').map(IceServer::from_str).collect()
46    }
47}
48
49impl Default for IceServer {
50    fn default() -> Self {
51        Self {
52            urls: ["stun://stun.l.google.com:19302".to_string()].to_vec(),
53            username: String::default(),
54            credential: String::default(),
55            credential_type: IceCredentialType::default(),
56        }
57    }
58}
59
60/// [stun|turn]://[username]:[password]@[url]
61/// For current implementation all type is `password` as default
62/// E.g: stun://foo:bar@stun.l.google.com:19302
63///      turn://ethereum.org:9090
64///      turn://ryan@ethereum.org:9090/nginx/v2
65impl FromStr for IceServer {
66    type Err = IceServerError;
67    fn from_str(s: &str) -> Result<Self, IceServerError> {
68        let parsed = Url::parse(s)?;
69        let scheme = parsed.scheme();
70        if !(["turn", "stun"].contains(&scheme)) {
71            return Err(IceServerError::SchemeNotSupported(scheme.into()));
72        }
73        if !parsed.has_host() {
74            return Err(IceServerError::UrlMissHost);
75        }
76        let username = parsed.username();
77        let password = parsed.password().unwrap_or("");
78        // must have host
79        let host = parsed.host_str().unwrap();
80        // parse port as `:<port>`
81        let port = parsed
82            .port()
83            .map(|p| format!(":{}", p))
84            .unwrap_or_else(|| "".to_string());
85        let path = parsed.path();
86        let url = format!("{}:{}{}{}", scheme, host, port, path);
87        Ok(Self {
88            urls: vec![url],
89            username: username.to_string(),
90            credential: password.to_string(),
91            credential_type: IceCredentialType::default(),
92        })
93    }
94}
95
96#[cfg(test)]
97mod test {
98    use std::str::FromStr;
99
100    use super::IceServer;
101
102    #[test]
103    fn test_parsing() {
104        let a = "stun://foo:bar@stun.l.google.com:19302";
105        let b = "turn://ethereum.org:9090";
106        let c = "turn://ryan@ethereum.org:9090/nginx/v2";
107        let d = "turn://ryan@ethereum.org/nginx/v2";
108        let e = "http://ryan@ethereum.org/nginx/v2";
109        let ret_a = IceServer::from_str(a).unwrap();
110        let ret_b = IceServer::from_str(b).unwrap();
111        let ret_c = IceServer::from_str(c).unwrap();
112        let ret_d = IceServer::from_str(d).unwrap();
113        let ret_e = IceServer::from_str(e);
114
115        assert_eq!(ret_a.urls[0], "stun:stun.l.google.com:19302".to_string());
116        assert_eq!(ret_a.credential, "bar".to_string());
117        assert_eq!(ret_a.username, "foo".to_string());
118
119        assert_eq!(ret_b.urls[0], "turn:ethereum.org:9090".to_string());
120        assert_eq!(ret_b.credential, "".to_string());
121        assert_eq!(ret_b.username, "".to_string());
122
123        assert_eq!(ret_c.urls[0], "turn:ethereum.org:9090/nginx/v2".to_string());
124        assert_eq!(ret_c.credential, "".to_string());
125        assert_eq!(ret_c.username, "ryan".to_string());
126
127        assert_eq!(ret_d.urls[0], "turn:ethereum.org/nginx/v2".to_string());
128        assert_eq!(ret_d.credential, "".to_string());
129        assert_eq!(ret_d.username, "ryan".to_string());
130
131        assert!(ret_e.is_err());
132    }
133}