1 #[cfg(test)] 2 mod util_test; 3 4 use super::error::{Error, Result}; 5 6 use std::collections::HashMap; 7 use std::fmt; 8 9 pub const ATTRIBUTE_KEY: &str = "a="; 10 11 /// ConnectionRole indicates which of the end points should initiate the connection establishment 12 #[derive(Debug, Copy, Clone, PartialEq, Eq)] 13 pub enum ConnectionRole { 14 Unspecified, 15 16 /// ConnectionRoleActive indicates the endpoint will initiate an outgoing connection. 17 Active, 18 19 /// ConnectionRolePassive indicates the endpoint will accept an incoming connection. 20 Passive, 21 22 /// ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or to initiate an outgoing connection. 23 Actpass, 24 25 /// ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being. 26 Holdconn, 27 } 28 29 impl Default for ConnectionRole { 30 fn default() -> Self { 31 ConnectionRole::Unspecified 32 } 33 } 34 35 const CONNECTION_ROLE_ACTIVE_STR: &str = "active"; 36 const CONNECTION_ROLE_PASSIVE_STR: &str = "passive"; 37 const CONNECTION_ROLE_ACTPASS_STR: &str = "actpass"; 38 const CONNECTION_ROLE_HOLDCONN_STR: &str = "holdconn"; 39 40 impl fmt::Display for ConnectionRole { 41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 let s = match self { 43 ConnectionRole::Active => CONNECTION_ROLE_ACTIVE_STR, 44 ConnectionRole::Passive => CONNECTION_ROLE_PASSIVE_STR, 45 ConnectionRole::Actpass => CONNECTION_ROLE_ACTPASS_STR, 46 ConnectionRole::Holdconn => CONNECTION_ROLE_HOLDCONN_STR, 47 _ => "Unspecified", 48 }; 49 write!(f, "{}", s) 50 } 51 } 52 53 impl From<u8> for ConnectionRole { 54 fn from(v: u8) -> Self { 55 match v { 56 1 => ConnectionRole::Active, 57 2 => ConnectionRole::Passive, 58 3 => ConnectionRole::Actpass, 59 4 => ConnectionRole::Holdconn, 60 _ => ConnectionRole::Unspecified, 61 } 62 } 63 } 64 65 impl From<&str> for ConnectionRole { 66 fn from(raw: &str) -> Self { 67 match raw { 68 CONNECTION_ROLE_ACTIVE_STR => ConnectionRole::Active, 69 CONNECTION_ROLE_PASSIVE_STR => ConnectionRole::Passive, 70 CONNECTION_ROLE_ACTPASS_STR => ConnectionRole::Actpass, 71 CONNECTION_ROLE_HOLDCONN_STR => ConnectionRole::Holdconn, 72 _ => ConnectionRole::Unspecified, 73 } 74 } 75 } 76 77 /// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26#section-5.2.1 78 /// Session ID is recommended to be constructed by generating a 64-bit 79 /// quantity with the highest bit set to zero and the remaining 63-bits 80 /// being cryptographically random. 81 pub(crate) fn new_session_id() -> u64 { 82 let c = u64::MAX ^ (1u64 << 63); 83 rand::random::<u64>() & c 84 } 85 86 // Codec represents a codec 87 #[derive(Debug, Clone, Default, PartialEq, Eq)] 88 pub struct Codec { 89 pub payload_type: u8, 90 pub name: String, 91 pub clock_rate: u32, 92 pub encoding_parameters: String, 93 pub fmtp: String, 94 pub rtcp_feedback: Vec<String>, 95 } 96 97 impl fmt::Display for Codec { 98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 write!( 100 f, 101 "{} {}/{}/{} ({}) [{}]", 102 self.payload_type, 103 self.name, 104 self.clock_rate, 105 self.encoding_parameters, 106 self.fmtp, 107 self.rtcp_feedback.join(", "), 108 ) 109 } 110 } 111 112 pub(crate) fn parse_rtpmap(rtpmap: &str) -> Result<Codec> { 113 // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>] 114 let split: Vec<&str> = rtpmap.split_whitespace().collect(); 115 if split.len() != 2 { 116 return Err(Error::MissingWhitespace); 117 } 118 119 let pt_split: Vec<&str> = split[0].split(':').collect(); 120 if pt_split.len() != 2 { 121 return Err(Error::MissingColon); 122 } 123 let payload_type = pt_split[1].parse::<u8>()?; 124 125 let split: Vec<&str> = split[1].split('/').collect(); 126 let name = split[0].to_string(); 127 let parts = split.len(); 128 let clock_rate = if parts > 1 { 129 split[1].parse::<u32>()? 130 } else { 131 0 132 }; 133 let encoding_parameters = if parts > 2 { 134 split[2].to_string() 135 } else { 136 "".to_string() 137 }; 138 139 Ok(Codec { 140 payload_type, 141 name, 142 clock_rate, 143 encoding_parameters, 144 ..Default::default() 145 }) 146 } 147 148 pub(crate) fn parse_fmtp(fmtp: &str) -> Result<Codec> { 149 // a=fmtp:<format> <format specific parameters> 150 let split: Vec<&str> = fmtp.split_whitespace().collect(); 151 if split.len() != 2 { 152 return Err(Error::MissingWhitespace); 153 } 154 155 let fmtp = split[1].to_string(); 156 157 let split: Vec<&str> = split[0].split(':').collect(); 158 if split.len() != 2 { 159 return Err(Error::MissingColon); 160 } 161 let payload_type = split[1].parse::<u8>()?; 162 163 Ok(Codec { 164 payload_type, 165 fmtp, 166 ..Default::default() 167 }) 168 } 169 170 pub(crate) fn parse_rtcp_fb(rtcp_fb: &str) -> Result<Codec> { 171 // a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>] 172 let split: Vec<&str> = rtcp_fb.splitn(2, ' ').collect(); 173 if split.len() != 2 { 174 return Err(Error::MissingWhitespace); 175 } 176 177 let pt_split: Vec<&str> = split[0].split(':').collect(); 178 if pt_split.len() != 2 { 179 return Err(Error::MissingColon); 180 } 181 182 Ok(Codec { 183 payload_type: pt_split[1].parse::<u8>()?, 184 rtcp_feedback: vec![split[1].to_string()], 185 ..Default::default() 186 }) 187 } 188 189 pub(crate) fn merge_codecs(mut codec: Codec, codecs: &mut HashMap<u8, Codec>) { 190 if let Some(saved_codec) = codecs.get_mut(&codec.payload_type) { 191 if saved_codec.payload_type == 0 { 192 saved_codec.payload_type = codec.payload_type 193 } 194 if saved_codec.name.is_empty() { 195 saved_codec.name = codec.name 196 } 197 if saved_codec.clock_rate == 0 { 198 saved_codec.clock_rate = codec.clock_rate 199 } 200 if saved_codec.encoding_parameters.is_empty() { 201 saved_codec.encoding_parameters = codec.encoding_parameters 202 } 203 if saved_codec.fmtp.is_empty() { 204 saved_codec.fmtp = codec.fmtp 205 } 206 saved_codec.rtcp_feedback.append(&mut codec.rtcp_feedback); 207 } else { 208 codecs.insert(codec.payload_type, codec); 209 } 210 } 211 212 fn equivalent_fmtp(want: &str, got: &str) -> bool { 213 let mut want_split: Vec<&str> = want.split(';').collect(); 214 let mut got_split: Vec<&str> = got.split(';').collect(); 215 216 if want_split.len() != got_split.len() { 217 return false; 218 } 219 220 want_split.sort_unstable(); 221 got_split.sort_unstable(); 222 223 for (i, &want_part) in want_split.iter().enumerate() { 224 let want_part = want_part.trim(); 225 let got_part = got_split[i].trim(); 226 if got_part != want_part { 227 return false; 228 } 229 } 230 231 true 232 } 233 234 pub(crate) fn codecs_match(wanted: &Codec, got: &Codec) -> bool { 235 if !wanted.name.is_empty() && wanted.name.to_lowercase() != got.name.to_lowercase() { 236 return false; 237 } 238 if wanted.clock_rate != 0 && wanted.clock_rate != got.clock_rate { 239 return false; 240 } 241 if !wanted.encoding_parameters.is_empty() 242 && wanted.encoding_parameters != got.encoding_parameters 243 { 244 return false; 245 } 246 if !wanted.fmtp.is_empty() && !equivalent_fmtp(&wanted.fmtp, &got.fmtp) { 247 return false; 248 } 249 250 true 251 } 252