1#[cfg(test)]
2mod util_test;
3
4use shared::error::{Error, Result};
5
6use std::collections::HashMap;
7use std::fmt;
8
9pub const ATTRIBUTE_KEY: &str = "a=";
10
11#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
13pub enum ConnectionRole {
14 #[default]
15 Unspecified,
16
17 Active,
19
20 Passive,
22
23 Actpass,
25
26 Holdconn,
28}
29
30const CONNECTION_ROLE_ACTIVE_STR: &str = "active";
31const CONNECTION_ROLE_PASSIVE_STR: &str = "passive";
32const CONNECTION_ROLE_ACTPASS_STR: &str = "actpass";
33const CONNECTION_ROLE_HOLDCONN_STR: &str = "holdconn";
34
35impl fmt::Display for ConnectionRole {
36 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
48impl From<u8> for ConnectionRole {
49 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
60impl From<&str> for ConnectionRole {
61 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
72pub(crate) fn new_session_id() -> u64 {
77 let c = u64::MAX ^ (1u64 << 63);
78 rand::random::<u64>() & c
79}
80
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub 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
92impl fmt::Display for Codec {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(
95 f,
96 "{} {}/{}/{} ({}) [",
97 self.payload_type, self.name, self.clock_rate, self.encoding_parameters, self.fmtp,
98 )?;
99
100 let mut first = true;
101 for part in &self.rtcp_feedback {
102 if first {
103 first = false;
104 write!(f, "{part}")?;
105 } else {
106 write!(f, ", {part}")?;
107 }
108 }
109
110 write!(f, "]")
111 }
112}
113
114pub(crate) fn parse_rtpmap(rtpmap: &str) -> Result<Codec> {
115 let split: Vec<&str> = rtpmap.split_whitespace().collect();
117 if split.len() != 2 {
118 return Err(Error::MissingWhitespace);
119 }
120
121 let pt_split: Vec<&str> = split[0].split(':').collect();
122 if pt_split.len() != 2 {
123 return Err(Error::MissingColon);
124 }
125 let payload_type = pt_split[1].parse::<u8>()?;
126
127 let split: Vec<&str> = split[1].split('/').collect();
128 let name = split[0].to_string();
129 let parts = split.len();
130 let clock_rate = if parts > 1 {
131 split[1].parse::<u32>()?
132 } else {
133 0
134 };
135 let encoding_parameters = if parts > 2 {
136 split[2].to_string()
137 } else {
138 "".to_string()
139 };
140
141 Ok(Codec {
142 payload_type,
143 name,
144 clock_rate,
145 encoding_parameters,
146 ..Default::default()
147 })
148}
149
150pub(crate) fn parse_fmtp(fmtp: &str) -> Result<Codec> {
151 let split: Vec<&str> = fmtp.split_whitespace().collect();
153 if split.len() != 2 {
154 return Err(Error::MissingWhitespace);
155 }
156
157 let fmtp = split[1].to_string();
158
159 let split: Vec<&str> = split[0].split(':').collect();
160 if split.len() != 2 {
161 return Err(Error::MissingColon);
162 }
163 let payload_type = split[1].parse::<u8>()?;
164
165 Ok(Codec {
166 payload_type,
167 fmtp,
168 ..Default::default()
169 })
170}
171
172pub(crate) fn parse_rtcp_fb(rtcp_fb: &str) -> Result<Codec> {
173 let split: Vec<&str> = rtcp_fb.splitn(2, ' ').collect();
175 if split.len() != 2 {
176 return Err(Error::MissingWhitespace);
177 }
178
179 let pt_split: Vec<&str> = split[0].split(':').collect();
180 if pt_split.len() != 2 {
181 return Err(Error::MissingColon);
182 }
183
184 Ok(Codec {
185 payload_type: pt_split[1].parse::<u8>()?,
186 rtcp_feedback: vec![split[1].to_string()],
187 ..Default::default()
188 })
189}
190
191pub(crate) fn merge_codecs(mut codec: Codec, codecs: &mut HashMap<u8, Codec>) {
192 if let Some(saved_codec) = codecs.get_mut(&codec.payload_type) {
193 if saved_codec.payload_type == 0 {
194 saved_codec.payload_type = codec.payload_type
195 }
196 if saved_codec.name.is_empty() {
197 saved_codec.name = codec.name
198 }
199 if saved_codec.clock_rate == 0 {
200 saved_codec.clock_rate = codec.clock_rate
201 }
202 if saved_codec.encoding_parameters.is_empty() {
203 saved_codec.encoding_parameters = codec.encoding_parameters
204 }
205 if saved_codec.fmtp.is_empty() {
206 saved_codec.fmtp = codec.fmtp
207 }
208 saved_codec.rtcp_feedback.append(&mut codec.rtcp_feedback);
209 } else {
210 codecs.insert(codec.payload_type, codec);
211 }
212}
213
214fn equivalent_fmtp(want: &str, got: &str) -> bool {
215 let mut want_split: Vec<&str> = want.split(';').collect();
216 let mut got_split: Vec<&str> = got.split(';').collect();
217
218 if want_split.len() != got_split.len() {
219 return false;
220 }
221
222 want_split.sort_unstable();
223 got_split.sort_unstable();
224
225 for (i, &want_part) in want_split.iter().enumerate() {
226 let want_part = want_part.trim();
227 let got_part = got_split[i].trim();
228 if got_part != want_part {
229 return false;
230 }
231 }
232
233 true
234}
235
236pub(crate) fn codecs_match(wanted: &Codec, got: &Codec) -> bool {
237 if !wanted.name.is_empty() && wanted.name.to_lowercase() != got.name.to_lowercase() {
238 return false;
239 }
240 if wanted.clock_rate != 0 && wanted.clock_rate != got.clock_rate {
241 return false;
242 }
243 if !wanted.encoding_parameters.is_empty()
244 && wanted.encoding_parameters != got.encoding_parameters
245 {
246 return false;
247 }
248 if !wanted.fmtp.is_empty() && !equivalent_fmtp(&wanted.fmtp, &got.fmtp) {
249 return false;
250 }
251
252 true
253}