xref: /webrtc/sdp/src/util/mod.rs (revision 7ceeeeb0)
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