freeswitch_types/sdp/
available.rs1use crate::sdp::codec::SdpMediaType;
15use crate::sdp::codec_string::{CodecString, CodecStringEntry};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct CodecImplementation {
27 name: String,
28 modname: Option<String>,
29 media_type: Option<SdpMediaType>,
30 rate: Option<u32>,
31 ptime: Option<u32>,
32 bitrate: Option<u32>,
33 channels: Option<u32>,
34}
35
36impl CodecImplementation {
37 pub fn new(name: impl Into<String>) -> Self {
43 Self {
44 name: name.into(),
45 modname: None,
46 media_type: None,
47 rate: None,
48 ptime: None,
49 bitrate: None,
50 channels: None,
51 }
52 }
53
54 pub fn with_media_type(mut self, media_type: SdpMediaType) -> Self {
61 self.media_type = Some(media_type);
62 self
63 }
64
65 pub fn with_modname(mut self, modname: impl Into<String>) -> Self {
67 self.modname = Some(modname.into());
68 self
69 }
70
71 pub fn with_rate(mut self, rate: u32) -> Self {
74 self.rate = Some(rate);
75 self
76 }
77
78 pub fn with_ptime(mut self, ptime: u32) -> Self {
80 self.ptime = Some(ptime);
81 self
82 }
83
84 pub fn with_bitrate(mut self, bitrate: u32) -> Self {
86 self.bitrate = Some(bitrate);
87 self
88 }
89
90 pub fn with_channels(mut self, channels: u32) -> Self {
92 self.channels = Some(channels);
93 self
94 }
95
96 pub fn name(&self) -> &str {
98 &self.name
99 }
100
101 pub fn modname(&self) -> Option<&str> {
103 self.modname
104 .as_deref()
105 }
106
107 pub fn media_type(&self) -> Option<&SdpMediaType> {
109 self.media_type
110 .as_ref()
111 }
112
113 pub fn rate(&self) -> Option<u32> {
115 self.rate
116 }
117
118 pub fn ptime(&self) -> Option<u32> {
120 self.ptime
121 }
122
123 pub fn bitrate(&self) -> Option<u32> {
125 self.bitrate
126 }
127
128 pub fn channels(&self) -> Option<u32> {
130 self.channels
131 }
132}
133
134fn matches_implementation(entry: &CodecStringEntry, imp: &CodecImplementation) -> bool {
138 if !entry
139 .name()
140 .eq_ignore_ascii_case(imp.name())
141 {
142 return false;
143 }
144
145 if let Some(entry_mod) = entry.modname() {
146 match imp.modname() {
147 Some(imp_mod) if entry_mod.eq_ignore_ascii_case(imp_mod) => {}
148 Some(_) => return false,
149 None => {}
150 }
151 }
152
153 if matches!(imp.media_type(), Some(SdpMediaType::Video)) {
156 return true;
157 }
158
159 let is_g722 = entry
162 .name()
163 .eq_ignore_ascii_case("g722");
164
165 if !is_g722 {
168 if let (Some(er), Some(ir)) = (nonzero(entry.rate()), imp.rate()) {
169 if er != ir {
170 return false;
171 }
172 }
173 }
174
175 if let (Some(ep), Some(ip)) = (nonzero(entry.ptime()), imp.ptime()) {
176 if ep != ip {
177 return false;
178 }
179 }
180
181 if let (Some(eb), Some(ib)) = (nonzero(entry.bitrate()), imp.bitrate()) {
182 if eb != ib {
183 return false;
184 }
185 }
186
187 if let Some(ic) = imp.channels() {
190 let required = entry
191 .channels()
192 .unwrap_or(1);
193 if required != 0 && required != ic {
194 return false;
195 }
196 }
197
198 true
199}
200
201fn nonzero(v: Option<u32>) -> Option<u32> {
204 v.filter(|&n| n != 0)
205}
206
207impl CodecString {
208 pub fn retain_available<'a, I>(&mut self, implementations: I) -> Vec<CodecStringEntry>
219 where
220 I: IntoIterator<Item = &'a CodecImplementation>,
221 {
222 let impls: Vec<&CodecImplementation> = implementations
223 .into_iter()
224 .collect();
225
226 let mut removed = Vec::new();
227 let mut kept = Vec::new();
228 for entry in std::mem::take(self) {
229 if impls
230 .iter()
231 .any(|imp| matches_implementation(&entry, imp))
232 {
233 kept.push(entry);
234 } else {
235 removed.push(entry);
236 }
237 }
238 *self = kept
239 .into_iter()
240 .collect();
241 removed
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 fn parse(s: &str) -> CodecString {
250 s.parse()
251 .unwrap()
252 }
253
254 #[test]
255 fn unavailable_codec_removed_case_insensitive_both_directions() {
256 let mut cs = parse("pcmu@20i,EVS~mode=1,AMR");
257 let impls = vec![
258 CodecImplementation::new("PCMU"),
259 CodecImplementation::new("amr"),
260 ];
261 let removed = cs.retain_available(&impls);
262 assert_eq!(removed.len(), 1);
263 assert_eq!(removed[0].name(), "EVS");
264 assert_eq!(removed[0].fmtp(), Some("mode=1"));
265 assert_eq!(cs.len(), 2);
266 let kept: Vec<&str> = cs
267 .entries()
268 .iter()
269 .map(|e| e.name())
270 .collect();
271 assert_eq!(kept, vec!["pcmu", "AMR"]);
272 }
273
274 #[test]
275 fn amr_ptime_mismatch_is_removed() {
276 let mut cs = parse("AMR@8000h@40i");
279 let impls = vec![CodecImplementation::new("AMR")
280 .with_rate(8000)
281 .with_ptime(20)];
282 let removed = cs.retain_available(&impls);
283 assert_eq!(removed.len(), 1);
284 assert_eq!(removed[0].name(), "AMR");
285 assert!(cs.is_empty());
286 }
287
288 #[test]
289 fn amr_ptime_mismatch_survives_name_only_implementation_list() {
290 let mut cs = parse("AMR@8000h@40i");
293 let impls = vec![CodecImplementation::new("AMR")];
294 let removed = cs.retain_available(&impls);
295 assert!(removed.is_empty());
296 assert_eq!(cs.len(), 1);
297 }
298
299 #[test]
300 fn modname_mismatch_is_removed() {
301 let mut cs = CodecString::new();
302 cs.push(
303 CodecStringEntry::new("EVS")
304 .unwrap()
305 .with_module("mod_evs")
306 .unwrap(),
307 )
308 .unwrap();
309 let impls = vec![CodecImplementation::new("EVS").with_modname("mod_other")];
310 let removed = cs.retain_available(&impls);
311 assert_eq!(removed.len(), 1);
312 assert!(cs.is_empty());
313 }
314
315 #[test]
316 fn modname_unknown_on_implementation_does_not_constrain() {
317 let mut cs = CodecString::new();
318 cs.push(
319 CodecStringEntry::new("EVS")
320 .unwrap()
321 .with_module("mod_evs")
322 .unwrap(),
323 )
324 .unwrap();
325 let impls = vec![CodecImplementation::new("EVS")];
326 let removed = cs.retain_available(&impls);
327 assert!(removed.is_empty());
328 assert_eq!(cs.len(), 1);
329 }
330
331 #[test]
332 fn removed_entries_preserve_original_order() {
333 let mut cs = parse("EVS,H264,AMR@8000h@40i");
334 let impls = vec![CodecImplementation::new("PCMU")];
335 let removed = cs.retain_available(&impls);
336 assert_eq!(removed.len(), 3);
337 assert_eq!(removed[0].name(), "EVS");
338 assert_eq!(removed[1].name(), "H264");
339 assert_eq!(removed[2].name(), "AMR");
340 assert!(cs.is_empty());
341 }
342
343 #[test]
346 fn video_implementation_matches_regardless_of_rate() {
347 let mut cs = parse("VP8@8000h");
350 let impls = vec![CodecImplementation::new("VP8")
351 .with_media_type(SdpMediaType::Video)
352 .with_rate(90000)];
353 let removed = cs.retain_available(&impls);
354 assert!(removed.is_empty());
355 assert_eq!(cs.len(), 1);
356 }
357
358 #[test]
361 fn g722_matches_at_either_advertised_rate() {
362 let mut cs = parse("G722@16000h@20i,G722@8000h@20i");
370 let impls = vec![CodecImplementation::new("G722")
371 .with_rate(8000)
372 .with_ptime(20)];
373 let removed = cs.retain_available(&impls);
374 assert!(removed.is_empty());
375 assert_eq!(cs.len(), 2);
376 }
377
378 #[test]
381 fn pcmu_zero_rate_does_not_constrain() {
382 let mut cs = parse("PCMU@0h");
383 let impls = vec![CodecImplementation::new("PCMU").with_rate(8000)];
384 let removed = cs.retain_available(&impls);
385 assert!(removed.is_empty());
386 assert_eq!(cs.len(), 1);
387 }
388
389 #[test]
390 fn pcmu_zero_ptime_does_not_constrain() {
391 let mut cs = parse("PCMU@0i");
392 let impls = vec![CodecImplementation::new("PCMU").with_ptime(20)];
393 let removed = cs.retain_available(&impls);
394 assert!(removed.is_empty());
395 assert_eq!(cs.len(), 1);
396 }
397
398 #[test]
399 fn pcmu_zero_bitrate_does_not_constrain() {
400 let mut cs = parse("PCMU@0b");
401 let impls = vec![CodecImplementation::new("PCMU").with_bitrate(64000)];
402 let removed = cs.retain_available(&impls);
403 assert!(removed.is_empty());
404 assert_eq!(cs.len(), 1);
405 }
406
407 #[test]
408 fn pcmu_zero_channels_does_not_constrain() {
409 let mut cs = parse("PCMU@0c");
410 let impls = vec![CodecImplementation::new("PCMU").with_channels(2)];
411 let removed = cs.retain_available(&impls);
412 assert!(removed.is_empty());
413 assert_eq!(cs.len(), 1);
414 }
415}