1 use sdp::description::session::SessionDescription; 2 use sdp::util::ConnectionRole; 3 4 use serde::{Deserialize, Serialize}; 5 use std::fmt; 6 7 /// DtlsRole indicates the role of the DTLS transport. 8 #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] 9 pub enum DTLSRole { 10 #[default] 11 Unspecified = 0, 12 13 /// DTLSRoleAuto defines the DTLS role is determined based on 14 /// the resolved ICE role: the ICE controlled role acts as the DTLS 15 /// client and the ICE controlling role acts as the DTLS server. 16 #[serde(rename = "auto")] 17 Auto = 1, 18 19 /// DTLSRoleClient defines the DTLS client role. 20 #[serde(rename = "client")] 21 Client = 2, 22 23 /// DTLSRoleServer defines the DTLS server role. 24 #[serde(rename = "server")] 25 Server = 3, 26 } 27 28 /// <https://tools.ietf.org/html/rfc5763> 29 /// The answerer MUST use either a 30 /// setup attribute value of setup:active or setup:passive. Note that 31 /// if the answerer uses setup:passive, then the DTLS handshake will 32 /// not begin until the answerer is received, which adds additional 33 /// latency. setup:active allows the answer and the DTLS handshake to 34 /// occur in parallel. Thus, setup:active is RECOMMENDED. 35 pub(crate) const DEFAULT_DTLS_ROLE_ANSWER: DTLSRole = DTLSRole::Client; 36 37 /// The endpoint that is the offerer MUST use the setup attribute 38 /// value of setup:actpass and be prepared to receive a client_hello 39 /// before it receives the answer. 40 pub(crate) const DEFAULT_DTLS_ROLE_OFFER: DTLSRole = DTLSRole::Auto; 41 42 impl fmt::Display for DTLSRole { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 match *self { 45 DTLSRole::Auto => write!(f, "auto"), 46 DTLSRole::Client => write!(f, "client"), 47 DTLSRole::Server => write!(f, "server"), 48 _ => write!(f, "{}", crate::UNSPECIFIED_STR), 49 } 50 } 51 } 52 53 /// Iterate a SessionDescription from a remote to determine if an explicit 54 /// role can been determined from it. The decision is made from the first role we we parse. 55 /// If no role can be found we return DTLSRoleAuto 56 impl From<&SessionDescription> for DTLSRole { from(session_description: &SessionDescription) -> Self57 fn from(session_description: &SessionDescription) -> Self { 58 for media_section in &session_description.media_descriptions { 59 for attribute in &media_section.attributes { 60 if attribute.key == "setup" { 61 if let Some(value) = &attribute.value { 62 match value.as_str() { 63 "active" => return DTLSRole::Client, 64 "passive" => return DTLSRole::Server, 65 _ => return DTLSRole::Auto, 66 }; 67 } else { 68 return DTLSRole::Auto; 69 } 70 } 71 } 72 } 73 74 DTLSRole::Auto 75 } 76 } 77 78 impl DTLSRole { to_connection_role(self) -> ConnectionRole79 pub(crate) fn to_connection_role(self) -> ConnectionRole { 80 match self { 81 DTLSRole::Client => ConnectionRole::Active, 82 DTLSRole::Server => ConnectionRole::Passive, 83 DTLSRole::Auto => ConnectionRole::Actpass, 84 _ => ConnectionRole::Unspecified, 85 } 86 } 87 } 88 89 #[cfg(test)] 90 mod test { 91 use crate::error::Result; 92 93 use super::*; 94 95 use std::io::Cursor; 96 97 #[test] test_dtls_role_string()98 fn test_dtls_role_string() { 99 let tests = vec![ 100 (DTLSRole::Unspecified, "Unspecified"), 101 (DTLSRole::Auto, "auto"), 102 (DTLSRole::Client, "client"), 103 (DTLSRole::Server, "server"), 104 ]; 105 106 for (role, expected_string) in tests { 107 assert_eq!(role.to_string(), expected_string) 108 } 109 } 110 111 #[test] test_dtls_role_from_remote_sdp() -> Result<()>112 fn test_dtls_role_from_remote_sdp() -> Result<()> { 113 const NO_MEDIA: &str = "v=0 114 o=- 4596489990601351948 2 IN IP4 127.0.0.1 115 s=- 116 t=0 0 117 "; 118 119 const MEDIA_NO_SETUP: &str = "v=0 120 o=- 4596489990601351948 2 IN IP4 127.0.0.1 121 s=- 122 t=0 0 123 m=application 47299 DTLS/SCTP 5000 124 c=IN IP4 192.168.20.129 125 "; 126 127 const MEDIA_SETUP_DECLARED: &str = "v=0 128 o=- 4596489990601351948 2 IN IP4 127.0.0.1 129 s=- 130 t=0 0 131 m=application 47299 DTLS/SCTP 5000 132 c=IN IP4 192.168.20.129 133 a=setup:"; 134 135 let tests = vec![ 136 ("No MediaDescriptions", NO_MEDIA.to_owned(), DTLSRole::Auto), 137 ( 138 "MediaDescription, no setup", 139 MEDIA_NO_SETUP.to_owned(), 140 DTLSRole::Auto, 141 ), 142 ( 143 "MediaDescription, setup:actpass", 144 format!("{}{}\n", MEDIA_SETUP_DECLARED, "actpass"), 145 DTLSRole::Auto, 146 ), 147 ( 148 "MediaDescription, setup:passive", 149 format!("{}{}\n", MEDIA_SETUP_DECLARED, "passive"), 150 DTLSRole::Server, 151 ), 152 ( 153 "MediaDescription, setup:active", 154 format!("{}{}\n", MEDIA_SETUP_DECLARED, "active"), 155 DTLSRole::Client, 156 ), 157 ]; 158 159 for (name, session_description_str, expected_role) in tests { 160 let mut reader = Cursor::new(session_description_str.as_bytes()); 161 let session_description = SessionDescription::unmarshal(&mut reader)?; 162 assert_eq!( 163 DTLSRole::from(&session_description), 164 expected_role, 165 "{name} failed" 166 ); 167 } 168 169 Ok(()) 170 } 171 } 172