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