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 {
137 if !entry
138 .name()
139 .eq_ignore_ascii_case(imp.name())
140 {
141 return false;
142 }
143
144 if let Some(entry_mod) = entry.modname() {
145 match imp.modname() {
146 Some(imp_mod) if entry_mod.eq_ignore_ascii_case(imp_mod) => {}
147 Some(_) => return false,
148 None => {}
149 }
150 }
151
152 if matches!(imp.media_type(), Some(SdpMediaType::Video)) {
155 return true;
156 }
157
158 let is_g722 = entry
161 .name()
162 .eq_ignore_ascii_case("g722");
163
164 if !is_g722 {
167 if let (Some(er), Some(ir)) = (nonzero(entry.rate()), imp.rate()) {
168 if er != ir {
169 return false;
170 }
171 }
172 }
173
174 if let (Some(ep), Some(ip)) = (nonzero(entry.ptime()), imp.ptime()) {
175 if ep != ip {
176 return false;
177 }
178 }
179
180 if let (Some(eb), Some(ib)) = (nonzero(entry.bitrate()), imp.bitrate()) {
181 if eb != ib {
182 return false;
183 }
184 }
185
186 if let Some(ic) = imp.channels() {
189 let required = entry
190 .channels()
191 .unwrap_or(1);
192 if required != 0 && required != ic {
193 return false;
194 }
195 }
196
197 true
198}
199
200fn nonzero(v: Option<u32>) -> Option<u32> {
203 v.filter(|&n| n != 0)
204}
205
206impl CodecString {
207 pub fn retain_available<'a, I>(&mut self, implementations: I) -> Vec<CodecStringEntry>
218 where
219 I: IntoIterator<Item = &'a CodecImplementation>,
220 {
221 let impls: Vec<&CodecImplementation> = implementations
222 .into_iter()
223 .collect();
224
225 let mut removed = Vec::new();
226 let mut kept = Vec::new();
227 for entry in std::mem::take(self) {
228 if impls
229 .iter()
230 .any(|imp| matches_implementation(&entry, imp))
231 {
232 kept.push(entry);
233 } else {
234 removed.push(entry);
235 }
236 }
237 *self = kept
238 .into_iter()
239 .collect();
240 removed
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 fn parse(s: &str) -> CodecString {
249 s.parse()
250 .unwrap()
251 }
252
253 #[test]
254 fn unavailable_codec_removed_case_insensitive_both_directions() {
255 let mut cs = parse("pcmu@20i,EVS~mode=1,AMR");
256 let impls = vec![
257 CodecImplementation::new("PCMU"),
258 CodecImplementation::new("amr"),
259 ];
260 let removed = cs.retain_available(&impls);
261 assert_eq!(removed.len(), 1);
262 assert_eq!(removed[0].name(), "EVS");
263 assert_eq!(removed[0].fmtp(), Some("mode=1"));
264 assert_eq!(cs.len(), 2);
265 let kept: Vec<&str> = cs
266 .entries()
267 .iter()
268 .map(|e| e.name())
269 .collect();
270 assert_eq!(kept, vec!["pcmu", "AMR"]);
271 }
272
273 #[test]
274 fn amr_ptime_mismatch_is_removed() {
275 let mut cs = parse("AMR@8000h@40i");
278 let impls = vec![CodecImplementation::new("AMR")
279 .with_rate(8000)
280 .with_ptime(20)];
281 let removed = cs.retain_available(&impls);
282 assert_eq!(removed.len(), 1);
283 assert_eq!(removed[0].name(), "AMR");
284 assert!(cs.is_empty());
285 }
286
287 #[test]
288 fn amr_ptime_mismatch_survives_name_only_implementation_list() {
289 let mut cs = parse("AMR@8000h@40i");
292 let impls = vec![CodecImplementation::new("AMR")];
293 let removed = cs.retain_available(&impls);
294 assert!(removed.is_empty());
295 assert_eq!(cs.len(), 1);
296 }
297
298 #[test]
299 fn modname_mismatch_is_removed() {
300 let mut cs = CodecString::new();
301 cs.push(
302 CodecStringEntry::new("EVS")
303 .unwrap()
304 .with_module("mod_evs")
305 .unwrap(),
306 )
307 .unwrap();
308 let impls = vec![CodecImplementation::new("EVS").with_modname("mod_other")];
309 let removed = cs.retain_available(&impls);
310 assert_eq!(removed.len(), 1);
311 assert!(cs.is_empty());
312 }
313
314 #[test]
315 fn modname_unknown_on_implementation_does_not_constrain() {
316 let mut cs = CodecString::new();
317 cs.push(
318 CodecStringEntry::new("EVS")
319 .unwrap()
320 .with_module("mod_evs")
321 .unwrap(),
322 )
323 .unwrap();
324 let impls = vec![CodecImplementation::new("EVS")];
325 let removed = cs.retain_available(&impls);
326 assert!(removed.is_empty());
327 assert_eq!(cs.len(), 1);
328 }
329
330 #[test]
331 fn removed_entries_preserve_original_order() {
332 let mut cs = parse("EVS,H264,AMR@8000h@40i");
333 let impls = vec![CodecImplementation::new("PCMU")];
334 let removed = cs.retain_available(&impls);
335 assert_eq!(removed.len(), 3);
336 assert_eq!(removed[0].name(), "EVS");
337 assert_eq!(removed[1].name(), "H264");
338 assert_eq!(removed[2].name(), "AMR");
339 assert!(cs.is_empty());
340 }
341
342 #[test]
345 fn video_implementation_matches_regardless_of_rate() {
346 let mut cs = parse("VP8@8000h");
349 let impls = vec![CodecImplementation::new("VP8")
350 .with_media_type(SdpMediaType::Video)
351 .with_rate(90000)];
352 let removed = cs.retain_available(&impls);
353 assert!(removed.is_empty());
354 assert_eq!(cs.len(), 1);
355 }
356
357 #[test]
360 fn g722_matches_at_either_advertised_rate() {
361 let mut cs = parse("G722@16000h@20i,G722@8000h@20i");
369 let impls = vec![CodecImplementation::new("G722")
370 .with_rate(8000)
371 .with_ptime(20)];
372 let removed = cs.retain_available(&impls);
373 assert!(removed.is_empty());
374 assert_eq!(cs.len(), 2);
375 }
376
377 #[test]
380 fn pcmu_zero_rate_does_not_constrain() {
381 let mut cs = parse("PCMU@0h");
382 let impls = vec![CodecImplementation::new("PCMU").with_rate(8000)];
383 let removed = cs.retain_available(&impls);
384 assert!(removed.is_empty());
385 assert_eq!(cs.len(), 1);
386 }
387
388 #[test]
389 fn pcmu_zero_ptime_does_not_constrain() {
390 let mut cs = parse("PCMU@0i");
391 let impls = vec![CodecImplementation::new("PCMU").with_ptime(20)];
392 let removed = cs.retain_available(&impls);
393 assert!(removed.is_empty());
394 assert_eq!(cs.len(), 1);
395 }
396
397 #[test]
398 fn pcmu_zero_bitrate_does_not_constrain() {
399 let mut cs = parse("PCMU@0b");
400 let impls = vec![CodecImplementation::new("PCMU").with_bitrate(64000)];
401 let removed = cs.retain_available(&impls);
402 assert!(removed.is_empty());
403 assert_eq!(cs.len(), 1);
404 }
405
406 #[test]
407 fn pcmu_zero_channels_does_not_constrain() {
408 let mut cs = parse("PCMU@0c");
409 let impls = vec![CodecImplementation::new("PCMU").with_channels(2)];
410 let removed = cs.retain_available(&impls);
411 assert!(removed.is_empty());
412 assert_eq!(cs.len(), 1);
413 }
414}