1use std::fmt;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16#[non_exhaustive]
17pub enum SdpMediaType {
18 Audio,
20 Video,
22 Application,
24 Text,
26 Message,
28 Other(String),
30}
31
32impl fmt::Display for SdpMediaType {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Audio => f.write_str("audio"),
36 Self::Video => f.write_str("video"),
37 Self::Application => f.write_str("application"),
38 Self::Text => f.write_str("text"),
39 Self::Message => f.write_str("message"),
40 Self::Other(s) => f.write_str(s),
41 }
42 }
43}
44
45impl FromStr for SdpMediaType {
46 type Err = std::convert::Infallible;
47
48 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 Ok(match s {
50 "audio" => Self::Audio,
51 "video" => Self::Video,
52 "application" => Self::Application,
53 "text" => Self::Text,
54 "message" => Self::Message,
55 other => Self::Other(other.to_string()),
56 })
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum SdpDirection {
64 SendRecv,
66 SendOnly,
68 RecvOnly,
70 Inactive,
72}
73
74impl fmt::Display for SdpDirection {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::SendRecv => f.write_str("sendrecv"),
78 Self::SendOnly => f.write_str("sendonly"),
79 Self::RecvOnly => f.write_str("recvonly"),
80 Self::Inactive => f.write_str("inactive"),
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct ParseSdpDirectionError(String);
88
89impl fmt::Display for ParseSdpDirectionError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "unrecognized SDP direction attribute: {:?}", self.0)
92 }
93}
94
95impl std::error::Error for ParseSdpDirectionError {}
96
97impl FromStr for SdpDirection {
98 type Err = ParseSdpDirectionError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 match s {
102 "sendrecv" => Ok(Self::SendRecv),
103 "sendonly" => Ok(Self::SendOnly),
104 "recvonly" => Ok(Self::RecvOnly),
105 "inactive" => Ok(Self::Inactive),
106 other => Err(ParseSdpDirectionError(other.to_string())),
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
116pub struct SdpCodec {
117 media: SdpMediaType,
118 payload_type: u8,
122 name: String,
123 clock_rate: u32,
124 channels: Option<u32>,
125 fmtp: Option<String>,
126 ptime: Option<u32>,
127 maxptime: Option<u32>,
128 bitrate: Option<u32>,
129 direction: SdpDirection,
130 has_rtpmap: bool,
131}
132
133impl SdpCodec {
134 pub fn new(
142 media: SdpMediaType,
143 payload_type: u8,
144 name: impl Into<String>,
145 clock_rate: u32,
146 ) -> Self {
147 Self {
148 media,
149 payload_type,
150 name: name.into(),
151 clock_rate,
152 channels: None,
153 fmtp: None,
154 ptime: None,
155 maxptime: None,
156 bitrate: None,
157 direction: SdpDirection::SendRecv,
158 has_rtpmap: false,
159 }
160 }
161
162 pub fn with_channels(mut self, channels: u32) -> Self {
164 self.channels = Some(channels);
165 self
166 }
167
168 pub fn with_fmtp(mut self, fmtp: impl Into<String>) -> Self {
170 self.fmtp = Some(fmtp.into());
171 self
172 }
173
174 pub fn with_ptime(mut self, ptime: u32) -> Self {
176 self.ptime = Some(ptime);
177 self
178 }
179
180 pub fn with_maxptime(mut self, maxptime: u32) -> Self {
182 self.maxptime = Some(maxptime);
183 self
184 }
185
186 pub fn with_bitrate(mut self, bitrate: u32) -> Self {
188 self.bitrate = Some(bitrate);
189 self
190 }
191
192 pub fn with_direction(mut self, direction: SdpDirection) -> Self {
194 self.direction = direction;
195 self
196 }
197
198 pub fn with_rtpmap(mut self) -> Self {
200 self.has_rtpmap = true;
201 self
202 }
203
204 pub fn media(&self) -> &SdpMediaType {
206 &self.media
207 }
208
209 pub fn payload_type(&self) -> u8 {
215 self.payload_type
216 }
217
218 pub fn name(&self) -> &str {
220 &self.name
221 }
222
223 pub fn clock_rate(&self) -> u32 {
225 self.clock_rate
226 }
227
228 pub fn channels(&self) -> Option<u32> {
230 self.channels
231 }
232
233 pub fn fmtp(&self) -> Option<&str> {
235 self.fmtp
236 .as_deref()
237 }
238
239 pub fn ptime(&self) -> Option<u32> {
241 self.ptime
242 }
243
244 pub fn maxptime(&self) -> Option<u32> {
249 self.maxptime
250 }
251
252 pub fn bitrate(&self) -> Option<u32> {
254 self.bitrate
255 }
256
257 pub fn direction(&self) -> SdpDirection {
259 self.direction
260 }
261
262 pub fn has_rtpmap(&self) -> bool {
266 self.has_rtpmap
267 }
268
269 pub(crate) fn fmtp_mut(&mut self) -> &mut Option<String> {
273 &mut self.fmtp
274 }
275
276 pub(crate) fn ptime_mut(&mut self) -> &mut Option<u32> {
278 &mut self.ptime
279 }
280
281 pub(crate) fn maxptime_mut(&mut self) -> &mut Option<u32> {
283 &mut self.maxptime
284 }
285
286 pub(crate) fn bitrate_mut(&mut self) -> &mut Option<u32> {
288 &mut self.bitrate
289 }
290
291 pub(crate) fn channels_mut(&mut self) -> &mut Option<u32> {
293 &mut self.channels
294 }
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
299#[non_exhaustive]
300pub enum NonCodecKind {
301 TelephoneEvent,
303 ComfortNoise,
305}
306
307impl fmt::Display for NonCodecKind {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 Self::TelephoneEvent => f.write_str("telephone-event"),
311 Self::ComfortNoise => f.write_str("CN"),
312 }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
325#[non_exhaustive]
326pub struct NonCodecPayload {
327 pub kind: NonCodecKind,
329 pub payload_type: u8,
331 pub media_type: SdpMediaType,
333 pub clock_rate: u32,
335 pub fmtp: Option<String>,
343 pub has_rtpmap: bool,
346}
347
348impl fmt::Display for NonCodecPayload {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 write!(f, "{} {}/{}", self.payload_type, self.kind, self.clock_rate)?;
351 if let Some(fmtp) = &self.fmtp {
352 write!(f, " {fmtp}")?;
353 }
354 Ok(())
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
365 fn non_codec_payload_display_reproduces_its_rtpmap() {
366 let te = NonCodecPayload {
367 kind: NonCodecKind::TelephoneEvent,
368 payload_type: 101,
369 media_type: SdpMediaType::Audio,
370 clock_rate: 8000,
371 fmtp: Some("0-16".to_string()),
372 has_rtpmap: true,
373 };
374 assert_eq!(te.to_string(), "101 telephone-event/8000 0-16");
375
376 let cn = NonCodecPayload {
377 kind: NonCodecKind::ComfortNoise,
378 payload_type: 13,
379 media_type: SdpMediaType::Audio,
380 clock_rate: 8000,
381 fmtp: None,
382 has_rtpmap: false,
383 };
384 assert_eq!(cn.to_string(), "13 CN/8000");
385 }
386
387 #[test]
388 fn non_codec_kind_display_is_the_canonical_encoding_name() {
389 assert_eq!(NonCodecKind::TelephoneEvent.to_string(), "telephone-event");
390 assert_eq!(NonCodecKind::ComfortNoise.to_string(), "CN");
391 }
392
393 #[test]
396 fn sdp_media_type_display() {
397 assert_eq!(SdpMediaType::Audio.to_string(), "audio");
398 assert_eq!(SdpMediaType::Video.to_string(), "video");
399 assert_eq!(SdpMediaType::Application.to_string(), "application");
400 assert_eq!(SdpMediaType::Text.to_string(), "text");
401 assert_eq!(SdpMediaType::Message.to_string(), "message");
402 assert_eq!(
403 SdpMediaType::Other("image".to_string()).to_string(),
404 "image"
405 );
406 }
407
408 #[test]
409 fn sdp_media_type_from_str_known() {
410 assert_eq!(
411 "audio"
412 .parse::<SdpMediaType>()
413 .unwrap(),
414 SdpMediaType::Audio
415 );
416 assert_eq!(
417 "video"
418 .parse::<SdpMediaType>()
419 .unwrap(),
420 SdpMediaType::Video
421 );
422 assert_eq!(
423 "application"
424 .parse::<SdpMediaType>()
425 .unwrap(),
426 SdpMediaType::Application
427 );
428 assert_eq!(
429 "text"
430 .parse::<SdpMediaType>()
431 .unwrap(),
432 SdpMediaType::Text
433 );
434 assert_eq!(
435 "message"
436 .parse::<SdpMediaType>()
437 .unwrap(),
438 SdpMediaType::Message
439 );
440 }
441
442 #[test]
443 fn sdp_media_type_from_str_unknown_becomes_other() {
444 let t = "image"
447 .parse::<SdpMediaType>()
448 .unwrap();
449 assert_eq!(t, SdpMediaType::Other("image".to_string()));
450 }
451
452 #[test]
453 fn sdp_media_type_round_trip() {
454 for s in &[
455 "audio",
456 "video",
457 "application",
458 "text",
459 "message",
460 "datachannel",
461 ] {
462 let parsed: SdpMediaType = s
463 .parse()
464 .unwrap();
465 assert_eq!(&parsed.to_string(), s);
466 }
467 }
468
469 #[test]
472 fn sdp_direction_display() {
473 assert_eq!(SdpDirection::SendRecv.to_string(), "sendrecv");
474 assert_eq!(SdpDirection::SendOnly.to_string(), "sendonly");
475 assert_eq!(SdpDirection::RecvOnly.to_string(), "recvonly");
476 assert_eq!(SdpDirection::Inactive.to_string(), "inactive");
477 }
478
479 #[test]
480 fn sdp_direction_from_str() {
481 assert_eq!(
482 "sendrecv"
483 .parse::<SdpDirection>()
484 .unwrap(),
485 SdpDirection::SendRecv
486 );
487 assert_eq!(
488 "sendonly"
489 .parse::<SdpDirection>()
490 .unwrap(),
491 SdpDirection::SendOnly
492 );
493 assert_eq!(
494 "recvonly"
495 .parse::<SdpDirection>()
496 .unwrap(),
497 SdpDirection::RecvOnly
498 );
499 assert_eq!(
500 "inactive"
501 .parse::<SdpDirection>()
502 .unwrap(),
503 SdpDirection::Inactive
504 );
505 }
506
507 #[test]
508 fn sdp_direction_from_str_error() {
509 let err = "halfduplex"
510 .parse::<SdpDirection>()
511 .unwrap_err();
512 assert!(err
513 .to_string()
514 .contains("halfduplex"));
515 }
516
517 #[test]
518 fn sdp_direction_round_trip() {
519 for dir in &[
520 SdpDirection::SendRecv,
521 SdpDirection::SendOnly,
522 SdpDirection::RecvOnly,
523 SdpDirection::Inactive,
524 ] {
525 let parsed: SdpDirection = dir
526 .to_string()
527 .parse()
528 .unwrap();
529 assert_eq!(&parsed, dir);
530 }
531 }
532
533 fn make_audio_codec() -> SdpCodec {
536 SdpCodec::new(SdpMediaType::Audio, 0, "PCMU", 8000)
537 .with_channels(1)
538 .with_ptime(20)
539 .with_bitrate(64000)
540 }
541
542 #[test]
543 fn sdp_codec_new_defaults() {
544 let c = SdpCodec::new(SdpMediaType::Audio, 8, "PCMA", 8000);
545 assert_eq!(c.channels(), None);
546 assert_eq!(c.fmtp(), None);
547 assert_eq!(c.ptime(), None);
548 assert_eq!(c.maxptime(), None);
549 assert_eq!(c.bitrate(), None);
550 assert_eq!(c.direction(), SdpDirection::SendRecv);
551 assert!(!c.has_rtpmap());
552 }
553
554 #[test]
555 fn sdp_codec_builders_set_optional_fields() {
556 let c = SdpCodec::new(SdpMediaType::Audio, 9, "G722", 8000)
557 .with_maxptime(40)
558 .with_direction(SdpDirection::RecvOnly);
559 assert_eq!(c.maxptime(), Some(40));
560 assert_eq!(c.direction(), SdpDirection::RecvOnly);
561 }
562
563 #[test]
564 fn sdp_codec_accessors() {
565 let c = make_audio_codec();
566 assert_eq!(c.media(), &SdpMediaType::Audio);
567 assert_eq!(c.payload_type(), 0);
568 assert_eq!(c.name(), "PCMU");
569 assert_eq!(c.clock_rate(), 8000);
570 assert_eq!(c.channels(), Some(1));
571 assert_eq!(c.fmtp(), None);
572 assert_eq!(c.ptime(), Some(20));
573 assert_eq!(c.maxptime(), None);
574 assert_eq!(c.bitrate(), Some(64000));
575 assert_eq!(c.direction(), SdpDirection::SendRecv);
576 assert!(!c.has_rtpmap());
577 }
578
579 #[test]
580 fn sdp_codec_with_rtpmap() {
581 let c = SdpCodec::new(SdpMediaType::Audio, 111, "opus", 48000)
582 .with_channels(2)
583 .with_fmtp("minptime=10;useinbandfec=1")
584 .with_ptime(20)
585 .with_rtpmap();
586 assert_eq!(c.name(), "opus");
587 assert_eq!(c.clock_rate(), 48000);
588 assert_eq!(c.channels(), Some(2));
589 assert_eq!(c.fmtp(), Some("minptime=10;useinbandfec=1"));
590 assert!(c.has_rtpmap());
591 }
592
593 #[test]
594 fn sdp_codec_video_no_channels() {
595 let c = SdpCodec::new(SdpMediaType::Video, 99, "H264", 90000).with_rtpmap();
596 assert_eq!(c.media(), &SdpMediaType::Video);
597 assert!(c
598 .channels()
599 .is_none());
600 }
601
602 #[test]
603 fn sdp_codec_mutable_accessors() {
604 let mut c = make_audio_codec();
605 *c.ptime_mut() = Some(30);
606 assert_eq!(c.ptime(), Some(30));
607
608 *c.fmtp_mut() = Some("mode=20".into());
609 assert_eq!(c.fmtp(), Some("mode=20"));
610
611 *c.bitrate_mut() = None;
612 assert!(c
613 .bitrate()
614 .is_none());
615
616 *c.maxptime_mut() = Some(40);
617 assert_eq!(c.maxptime(), Some(40));
618
619 *c.channels_mut() = Some(2);
620 assert_eq!(c.channels(), Some(2));
621 }
622}