1use std::{net::IpAddr, str::FromStr};
71
72use bstr::*;
73use fallible_iterator::FallibleIterator;
74
75pub mod attributes;
76pub mod builders;
77pub mod clock_signalling;
78pub mod enums;
79mod parser;
80mod writer;
81
82pub use attributes::*;
83pub use clock_signalling::*;
84pub use enums::*;
85pub use parser::ParserError;
86
87#[derive(Debug, PartialEq, Eq, Clone)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub struct Origin {
93 pub username: Option<String>,
95 pub sess_id: String,
99 pub sess_version: u64,
101 pub nettype: NetType,
103 pub addrtype: AddrType,
105 pub unicast_address: String,
107}
108
109impl Origin {
110 pub fn with_ip_addr(
112 sess_id: impl ToString,
113 sess_version: u64,
114 unicast_address: impl Into<IpAddr>,
115 ) -> Self {
116 let unicast_address = unicast_address.into();
117 Origin {
118 username: Default::default(),
119 sess_id: sess_id.to_string(),
120 sess_version,
121 nettype: NetType::In,
122 addrtype: unicast_address.into(),
123 unicast_address: unicast_address.to_string(),
124 }
125 }
126
127 pub fn new(
131 sess_id: impl ToString,
132 sess_version: u64,
133 nettype: NetType,
134 addrtype: AddrType,
135 unicast_address: impl ToString,
136 ) -> Self {
137 Origin {
138 username: Default::default(),
139 sess_id: sess_id.to_string(),
140 sess_version,
141 nettype,
142 addrtype,
143 unicast_address: unicast_address.to_string(),
144 }
145 }
146
147 pub fn set_unicast_ip_address(&mut self, unicast_address: impl Into<IpAddr>) {
149 let unicast_address = unicast_address.into();
150 self.addrtype = unicast_address.into();
151 self.unicast_address = unicast_address.to_string();
152 }
153 pub fn try_parse_unicast_ip_address(&self) -> Result<IpAddr, &str> {
157 self.unicast_address
158 .parse::<IpAddr>()
159 .map_err(|_| self.unicast_address.as_str())
160 }
161
162 pub fn set_username(&mut self, username: impl ToString) {
163 self.username = Some(username.to_string());
164 }
165
166 pub fn builder_with_ip_addr(
168 sess_id: impl ToString,
169 sess_version: u64,
170 unicast_address: impl Into<IpAddr>,
171 ) -> builders::Origin {
172 builders::Origin::with_ip_addr(sess_id, sess_version, unicast_address)
173 }
174
175 pub fn builder(
179 sess_id: impl ToString,
180 sess_version: u64,
181 nettype: NetType,
182 addrtype: AddrType,
183 unicast_address: impl ToString,
184 ) -> builders::Origin {
185 builders::Origin::new(sess_id, sess_version, nettype, addrtype, unicast_address)
186 }
187}
188
189#[derive(Debug, PartialEq, Eq, Clone)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194pub struct Connection {
195 pub nettype: NetType,
197 pub addrtype: AddrType,
199 pub connection_address: String,
201}
202
203impl Connection {
204 pub fn from_ip_addr(connection_address: impl Into<IpAddr>) -> Self {
206 let connection_address = connection_address.into();
207 Connection {
208 nettype: NetType::In,
209 addrtype: connection_address.into(),
210 connection_address: connection_address.to_string(),
211 }
212 }
213
214 pub fn new(nettype: NetType, addrtype: AddrType, connection_address: impl ToString) -> Self {
218 Connection {
219 nettype,
220 addrtype,
221 connection_address: connection_address.to_string(),
222 }
223 }
224
225 pub fn set_connection_ip_address(&mut self, connection_address: impl Into<IpAddr>) {
227 let connection_address = connection_address.into();
228 self.addrtype = connection_address.into();
229 self.connection_address = connection_address.to_string();
230 }
231
232 pub fn try_parse_connection_ip_address(&self) -> Result<IpAddr, &str> {
237 self.connection_address
238 .parse::<IpAddr>()
239 .map_err(|_| self.connection_address.as_str())
240 }
241}
242
243impl<T: Into<IpAddr>> From<T> for Connection {
244 fn from(ip_addr: T) -> Self {
245 Connection::from_ip_addr(ip_addr)
246 }
247}
248
249#[derive(Debug, PartialEq, Eq, Clone)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub struct Bandwidth {
255 pub bwtype: String,
257 pub bandwidth: u64,
259}
260
261impl Bandwidth {
262 pub fn new(bwtype: BandwidthType, bandwidth: u64) -> Self {
263 Bandwidth {
264 bwtype: bwtype.to_string(),
265 bandwidth,
266 }
267 }
268
269 pub fn try_parse_bwtype(&self) -> Result<BandwidthType, ParseEnumError> {
271 BandwidthType::from_str(self.bwtype.as_str())
272 }
273
274 pub fn set_bwtype(&mut self, bwtype: BandwidthType) {
276 self.bwtype = bwtype.to_string();
277 }
278}
279
280#[derive(Debug, PartialEq, Eq, Clone)]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285pub struct Time {
286 pub start_time: u64,
288 pub stop_time: u64,
290 pub repeats: Vec<Repeat>,
292}
293
294impl Time {
295 pub fn new(start_time: u64, stop_time: u64) -> Self {
296 Time {
297 start_time,
298 stop_time,
299 repeats: Default::default(),
300 }
301 }
302
303 pub fn add_repeat(&mut self, repeat: Repeat) {
304 self.repeats.push(repeat);
305 }
306
307 pub fn add_repeats(&mut self, repeats: impl IntoIterator<Item = Repeat>) {
308 self.repeats.extend(repeats)
309 }
310
311 pub fn builder(start_time: u64, stop_time: u64) -> builders::Time {
312 builders::Time::new(start_time, stop_time)
313 }
314}
315
316#[derive(Debug, PartialEq, Eq, Clone)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub struct Repeat {
322 pub repeat_interval: u64,
324 pub active_duration: u64,
326 pub offsets: Vec<u64>,
328}
329
330impl Repeat {
331 pub fn new(repeat_interval: u64, active_duration: u64) -> Self {
332 Repeat {
333 repeat_interval,
334 active_duration,
335 offsets: Default::default(),
336 }
337 }
338
339 pub fn add_offset(&mut self, offset: u64) {
340 self.offsets.push(offset);
341 }
342
343 pub fn add_offsets(&mut self, offsets: impl IntoIterator<Item = u64>) {
344 self.offsets.extend(offsets)
345 }
346
347 pub fn builder(repeat_interval: u64, active_duration: u64) -> builders::Repeat {
348 builders::Repeat::new(repeat_interval, active_duration)
349 }
350}
351
352#[derive(Debug, PartialEq, Eq, Clone)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357pub struct TimeZone {
358 pub adjustment_time: u64,
360 pub offset: i64,
362}
363
364impl TimeZone {
365 pub fn new(adjustment_time: u64, offset: i64) -> Self {
366 TimeZone {
367 adjustment_time,
368 offset,
369 }
370 }
371}
372
373#[derive(Debug, PartialEq, Eq, Clone)]
378#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
379pub struct Key {
380 pub method: String,
382 pub encryption_key: Option<String>,
384}
385
386impl Key {
387 pub fn new(method: KeyMethod) -> Self {
388 Key {
389 method: method.to_string(),
390 encryption_key: Default::default(),
391 }
392 }
393
394 pub fn with_encryption_key(method: KeyMethod, encryption_key: impl ToString) -> Self {
396 Key {
397 method: method.to_string(),
398 encryption_key: Some(encryption_key.to_string()),
399 }
400 }
401
402 pub fn try_parse_keymethod(&self) -> Result<KeyMethod, ParseEnumError> {
404 KeyMethod::from_str(self.method.as_str())
405 }
406
407 pub fn set_keymethod(&mut self, method: KeyMethod) {
409 self.method = method.to_string();
410 }
411}
412
413impl From<KeyMethod> for Key {
414 fn from(method: KeyMethod) -> Self {
415 Key {
416 method: method.to_string(),
417 encryption_key: None,
418 }
419 }
420}
421
422#[derive(Debug, PartialEq, Eq, Clone)]
426#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
427pub struct Attribute {
428 pub attribute: String,
430 pub value: Option<String>,
432}
433
434impl Attribute {
435 pub fn new(attribute: impl ToString) -> Self {
439 Attribute {
440 attribute: attribute.to_string(),
441 value: Default::default(),
442 }
443 }
444
445 pub fn with_value(attribute: impl ToString, value: impl ToString) -> Self {
447 Attribute {
448 attribute: attribute.to_string(),
449 value: Some(value.to_string()),
450 }
451 }
452}
453
454#[derive(Debug, PartialEq, Eq, Clone)]
458#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
459pub struct Media {
460 pub media: String,
462 pub port: u16,
464 pub num_ports: Option<u16>,
466 pub proto: String,
468 pub fmt: String,
470 pub media_title: Option<String>,
472 pub connections: Vec<Connection>,
474 pub bandwidths: Vec<Bandwidth>,
476 pub key: Option<Key>,
478 pub attributes: Vec<Attribute>,
480}
481
482impl Media {
483 pub fn new(media: MediaType, port: u16, proto: TransportProto, fmt: impl ToString) -> Self {
484 Media {
485 media: media.to_string(),
486 port,
487 num_ports: Default::default(),
488 proto: proto.to_string(),
489 fmt: fmt.to_string(),
490 media_title: Default::default(),
491 connections: Default::default(),
492 bandwidths: Default::default(),
493 key: Default::default(),
494 attributes: Default::default(),
495 }
496 }
497
498 pub fn builder(
499 media: MediaType,
500 port: u16,
501 proto: TransportProto,
502 fmt: impl ToString,
503 ) -> builders::Media {
504 builders::Media::new(media, port, proto, fmt)
505 }
506
507 pub fn set_num_ports(&mut self, num_ports: u16) {
508 self.num_ports = Some(num_ports);
509 }
510
511 pub fn set_media_title(&mut self, media_title: impl ToString) {
512 self.media_title = Some(media_title.to_string());
513 }
514
515 pub fn add_connection(&mut self, connection: impl Into<Connection>) {
516 self.connections.push(connection.into());
517 }
518
519 pub fn add_connections(
520 &mut self,
521 connections: impl IntoIterator<Item = impl Into<Connection>>,
522 ) {
523 self.connections
524 .extend(connections.into_iter().map(|c| c.into()))
525 }
526
527 pub fn add_bandwidth(&mut self, bandwidth: Bandwidth) {
528 self.bandwidths.push(bandwidth);
529 }
530
531 pub fn add_bandwidths(&mut self, bandwidths: impl IntoIterator<Item = Bandwidth>) {
532 self.bandwidths.extend(bandwidths)
533 }
534
535 pub fn set_encryption_key(&mut self, key: impl Into<Key>) {
536 self.key = Some(key.into());
537 }
538
539 pub fn add_attribute(&mut self, attribute: impl Into<Attribute>) {
540 self.attributes.push(attribute.into());
541 }
542
543 pub fn add_attribute_from_str(&mut self, attribute: impl ToString) {
544 self.attributes.push(Attribute::new(attribute));
545 }
546
547 pub fn add_attribute_with_value(&mut self, attribute: impl ToString, value: impl ToString) {
548 self.attributes
549 .push(Attribute::with_value(attribute, value));
550 }
551
552 pub fn add_attributes(&mut self, attributes: impl IntoIterator<Item = impl Into<Attribute>>) {
553 self.attributes
554 .extend(attributes.into_iter().map(|a| a.into()))
555 }
556
557 pub fn add_attributes_from_strs(
558 &mut self,
559 attributes: impl IntoIterator<Item = impl ToString>,
560 ) {
561 self.attributes
562 .extend(attributes.into_iter().map(|a| Attribute::new(a)))
563 }
564
565 pub fn has_attribute(&self, name: &str) -> bool {
567 self.attributes.iter().any(|a| a.attribute == name)
568 }
569
570 pub fn get_first_attribute_value<'a>(&'a self, name: &'a str) -> Option<Option<&'a str>> {
575 self.get_attribute_values(name).next()
576 }
577
578 pub fn get_attribute_values<'a>(
580 &'a self,
581 name: &'a str,
582 ) -> impl Iterator<Item = Option<&'a str>> {
583 self.attributes
584 .iter()
585 .filter(move |a| a.attribute == name)
586 .map(|a| a.value.as_deref())
587 }
588
589 pub fn try_parse_mediatype(&self) -> Result<MediaType, ParseEnumError> {
591 MediaType::from_str(self.media.as_str())
592 }
593
594 pub fn set_mediatype(&mut self, media: MediaType) {
596 self.media = media.to_string();
597 }
598
599 pub fn parse_transport_proto(&self) -> TransportProto {
601 TransportProto::from(self.proto.as_str())
602 }
603
604 pub fn set_transport_proto(&mut self, proto: TransportProto) {
606 self.proto = proto.to_string();
607 }
608
609 pub fn get_first_attribute_typed<T: TypedAttribute>(
611 &self,
612 ) -> Option<Result<T, AttributeError>> {
613 self.attributes_typed().next()
614 }
615
616 pub fn attributes_typed<'a, T: TypedAttribute>(
622 &'a self,
623 ) -> impl Iterator<Item = Result<T, AttributeError>> + 'a {
624 self.attributes
625 .iter()
626 .filter(move |a| a.attribute.eq_ignore_ascii_case(T::NAME))
627 .map(|a| {
628 let Some(s) = &a.value else {
629 return Err(AttributeError::Other {
630 error: "No value for the attribute".to_string(),
631 attr: T::NAME.to_string(),
632 });
633 };
634
635 T::from_str(s)
636 })
637 }
638}
639
640#[derive(Debug, PartialEq, Eq, Clone)]
644#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
645pub struct Session {
646 pub origin: Origin,
648 pub session_name: String,
650 pub session_description: Option<String>,
652 pub uri: Option<String>,
654 pub emails: Vec<String>,
656 pub phones: Vec<String>,
658 pub connection: Option<Connection>,
660 pub bandwidths: Vec<Bandwidth>,
662 pub times: Vec<Time>,
664 pub time_zones: Vec<TimeZone>,
666 pub key: Option<Key>,
668 pub attributes: Vec<Attribute>,
670 pub medias: Vec<Media>,
672}
673
674impl Session {
675 pub fn new(origin: Origin, session_name: impl ToString) -> Self {
676 Session {
677 origin,
678 session_name: session_name.to_string(),
679 session_description: Default::default(),
680 uri: Default::default(),
681 emails: Default::default(),
682 phones: Default::default(),
683 connection: Default::default(),
684 bandwidths: Default::default(),
685 times: Default::default(),
686 time_zones: Default::default(),
687 key: Default::default(),
688 attributes: Default::default(),
689 medias: Default::default(),
690 }
691 }
692
693 pub fn builder(origin: Origin, session_name: impl ToString) -> builders::Session {
694 builders::Session::new(origin, session_name)
695 }
696
697 pub fn set_session_description(&mut self, session_description: impl ToString) {
698 self.session_description = Some(session_description.to_string());
699 }
700
701 pub fn set_uri(&mut self, uri: impl ToString) {
702 self.uri = Some(uri.to_string());
703 }
704
705 pub fn add_email(&mut self, email: impl ToString) {
706 self.emails.push(email.to_string());
707 }
708
709 pub fn add_emails(&mut self, emails: impl IntoIterator<Item = impl ToString>) {
710 self.emails
711 .extend(emails.into_iter().map(|e| e.to_string()));
712 }
713
714 pub fn add_phone(&mut self, phone: impl ToString) {
715 self.phones.push(phone.to_string());
716 }
717
718 pub fn add_phones(&mut self, phones: impl IntoIterator<Item = impl ToString>) {
719 self.phones
720 .extend(phones.into_iter().map(|p| p.to_string()));
721 }
722
723 pub fn set_connection(&mut self, connection: impl Into<Connection>) {
724 self.connection = Some(connection.into());
725 }
726
727 pub fn add_bandwidth(&mut self, bandwidth: Bandwidth) {
728 self.bandwidths.push(bandwidth);
729 }
730
731 pub fn add_bandwidths(&mut self, bandwidths: impl IntoIterator<Item = Bandwidth>) {
732 self.bandwidths.extend(bandwidths);
733 }
734
735 pub fn add_time(&mut self, time: Time) {
736 self.times.push(time);
737 }
738
739 pub fn add_times(&mut self, times: impl IntoIterator<Item = Time>) {
740 self.times.extend(times);
741 }
742
743 pub fn add_time_zone(&mut self, time_zone: TimeZone) {
744 self.time_zones.push(time_zone);
745 }
746
747 pub fn add_time_zones(&mut self, time_zones: impl IntoIterator<Item = TimeZone>) {
748 self.time_zones.extend(time_zones);
749 }
750
751 pub fn set_encryption_key(&mut self, key: impl Into<Key>) {
752 self.key = Some(key.into());
753 }
754
755 pub fn add_attribute(&mut self, attribute: impl Into<Attribute>) {
756 self.attributes.push(attribute.into());
757 }
758
759 pub fn add_attribute_from_str(&mut self, attribute: impl ToString) {
760 self.attributes.push(Attribute::new(attribute));
761 }
762
763 pub fn add_attribute_with_value(&mut self, attribute: impl ToString, value: impl ToString) {
764 self.attributes
765 .push(Attribute::with_value(attribute, value));
766 }
767
768 pub fn add_attributes(&mut self, attributes: impl IntoIterator<Item = impl Into<Attribute>>) {
769 self.attributes
770 .extend(attributes.into_iter().map(|a| a.into()))
771 }
772
773 pub fn add_attributes_from_strs(
774 &mut self,
775 attributes: impl IntoIterator<Item = impl ToString>,
776 ) {
777 self.attributes
778 .extend(attributes.into_iter().map(|a| Attribute::new(a)))
779 }
780
781 pub fn add_media(&mut self, media: Media) {
782 self.medias.push(media);
783 }
784
785 pub fn add_medias(&mut self, medias: impl IntoIterator<Item = Media>) {
786 self.medias.extend(medias)
787 }
788
789 pub fn has_attribute(&self, name: &str) -> bool {
791 self.attributes.iter().any(|a| a.attribute == name)
792 }
793
794 pub fn get_first_attribute_value<'a>(&'a self, name: &'a str) -> Option<Option<&'a str>> {
799 self.get_attribute_values(name).next()
800 }
801
802 pub fn get_attribute_values<'a>(
804 &'a self,
805 name: &'a str,
806 ) -> impl Iterator<Item = Option<&'a str>> {
807 self.attributes
808 .iter()
809 .filter(move |a| a.attribute == name)
810 .map(|a| a.value.as_deref())
811 }
812
813 pub fn get_first_attribute_typed<T: TypedAttribute>(
815 &self,
816 ) -> Option<Result<T, AttributeError>> {
817 self.attributes_typed().next()
818 }
819
820 pub fn attributes_typed<'a, T: TypedAttribute>(
826 &'a self,
827 ) -> impl Iterator<Item = Result<T, AttributeError>> + 'a {
828 self.attributes
829 .iter()
830 .filter(move |a| a.attribute.eq_ignore_ascii_case(T::NAME))
831 .map(|a| {
832 let Some(s) = &a.value else {
833 return Err(AttributeError::Other {
834 error: "No value for the attribute".to_string(),
835 attr: T::NAME.to_string(),
836 });
837 };
838
839 T::from_str(s)
840 })
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use std::net::Ipv4Addr;
847
848 use super::*;
849
850 #[test]
851 fn parse_write() {
852 let sdp = "v=0\r
853o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
854s=SDP Seminar\r
855i=A Seminar on the session description protocol\r
856u=http://www.example.com/seminars/sdp.pdf\r
857e=j.doe@example.com (Jane Doe)\r
858p=+1 617 555-6011\r
859c=IN IP4 224.2.17.12/127\r
860b=AS:128\r
861t=2873397496 2873404696\r
862r=7d 1h 0 25h\r
863z=2882844526 -1h 2898848070 0\r
864k=clear:1234\r
865a=recvonly\r
866m=audio 49170 RTP/AVP 0\r
867a=fmtp:0 0-15\r
868m=video 51372/2 RTP/AVP 99 97 98\r
869a=rtpmap:99 h263-1998/90000\r
870a=fingerprint:sha-256 3A:96:6D:57:B2:C2:C7:61:A0:46:3E:1C:97:39:D3:F7:0A:88:A0:B1:EC:03:FB:10:A5:5D:3A:37:AB:DD:02:AA\r
871a=extmap:2/sendrecv http://example.com/082005/ext.htm#xmeta short\r
872";
873 let parsed = Session::parse(sdp.as_bytes()).unwrap();
874 let mut written = vec![];
875 parsed.write(&mut written).unwrap();
876 assert_eq!(String::from_utf8_lossy(&written), sdp);
877 assert_eq!(parsed.origin.addrtype, AddrType::Ip4);
878 assert_ne!(parsed.origin.nettype, NetType::Pstn);
879 assert_eq!(
880 parsed.origin.try_parse_unicast_ip_address(),
881 Ok(IpAddr::V4(std::net::Ipv4Addr::new(10, 47, 16, 5)))
882 );
883 assert_eq!(parsed.medias[0].try_parse_mediatype(), Ok(MediaType::Audio));
884 assert_ne!(
885 parsed.medias[1].parse_transport_proto(),
886 TransportProto::RtpSavpf,
887 );
888 let f = fallible_iterator::convert(parsed.medias[0].attributes_typed::<Fmtp>())
889 .collect::<Vec<_>>()
890 .expect("Valid vector of attributes");
891 assert_eq!(f.len(), 1);
892 assert_eq!(f[0].format_specific_params[0].param, "0-15");
893
894 let e = fallible_iterator::convert(parsed.medias[1].attributes_typed::<ExtMap>())
895 .collect::<Vec<_>>()
896 .expect("Vector of extmap attributes");
897 assert_eq!(e[0].id, 2);
898 assert_eq!(e[0].direction, Some(Direction::SendRecv));
899 assert_eq!(
900 e[0].uri,
901 "http://example.com/082005/ext.htm#xmeta".to_string()
902 );
903 assert_eq!(e[0].attributes, Some("short".to_string()));
904
905 let f = fallible_iterator::convert(parsed.medias[1].attributes_typed::<Fingerprint>())
906 .collect::<Vec<_>>()
907 .expect("Vector of fingerprint attributes");
908
909 assert_eq!(f[0].hash_func, HashFunc::Sha256);
910 assert_eq!(f[0].fingerprint[4], 0xB2);
911 assert_eq!(f[0].fingerprint.last(), Some(&0xAA));
912 }
913
914 #[test]
915 fn parse_media_attributes() {
916 let media = Media {
917 media: "video".into(),
918 port: 51372,
919 num_ports: Some(2),
920 proto: "RTP/AVP".into(),
921 fmt: "99 100".into(),
922 media_title: None,
923 connections: vec![],
924 bandwidths: vec![],
925 key: None,
926 attributes: vec![
927 Attribute {
928 attribute: "rtpmap".into(),
929 value: Some("99 h263-1998/90000".into()),
930 },
931 Attribute {
932 attribute: "rtpmap".into(),
933 value: Some("100 h264/90000".into()),
934 },
935 Attribute {
936 attribute: "rtpmap".into(),
937 value: None,
938 },
939 Attribute {
940 attribute: "rtpmap".into(),
941 value: Some(
942 RtpMap {
943 payload_type: 101,
944 encoding_name: "L16".into(),
945 clock_rate: 16000,
946 encoding_params: Some("2".into()),
947 }
948 .to_string(),
949 ),
950 },
951 Attribute {
952 attribute: "fmtp".into(),
953 value: Some(
954 Fmtp {
955 fmt: 100,
956 format_specific_params: Vec::from([
957 FmtpParam {
958 param: "profile-level-id".to_string(),
959 val: Some("42e016".to_string()),
960 },
961 FmtpParam {
962 param: "max-mbps".to_string(),
963 val: Some("108000".to_string()),
964 },
965 FmtpParam {
966 param: "max-fs".to_string(),
967 val: Some("3600".to_string()),
968 },
969 ]),
970 }
971 .to_string(),
972 ),
973 },
974 Attribute {
975 attribute: "rtcp".into(),
976 value: Some(
977 Rtcp {
978 port: 53020,
979 nettype: NetType::In,
980 addrtype: AddrType::Ip4,
981 connection_address: std::net::Ipv4Addr::new(126, 16, 64, 4).to_string(),
982 }
983 .to_string(),
984 ),
985 },
986 Attribute {
987 attribute: "fingerprint".into(),
988 value: Some(
989 Fingerprint {
990 hash_func: HashFunc::Other(("custom").to_string()),
991 fingerprint: [0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6].to_vec(),
992 }
993 .to_string(),
994 ),
995 },
996 ],
997 };
998
999 assert!(media.has_attribute("rtpmap"));
1000 assert!(media.has_attribute("rtcp"));
1001 assert!(!media.has_attribute("foo"));
1002
1003 assert_eq!(
1004 media.get_first_attribute_value("rtpmap"),
1005 Some(Some("99 h263-1998/90000"))
1006 );
1007 assert_eq!(
1008 media.get_first_attribute_value("rtcp"),
1009 Some(Some("53020 IN IP4 126.16.64.4"))
1010 );
1011 assert!(media.get_first_attribute_value("foo").is_none());
1012
1013 assert_eq!(
1014 media.get_attribute_values("rtpmap").collect::<Vec<_>>(),
1015 &[
1016 Some("99 h263-1998/90000"),
1017 Some("100 h264/90000"),
1018 None,
1019 Some("101 L16/16000/2"),
1020 ]
1021 );
1022
1023 let v = media
1024 .attributes_typed::<RtpMap>()
1025 .collect::<Vec<Result<RtpMap, AttributeError>>>();
1026 assert_eq!(v[0].as_ref().unwrap().clock_rate, 90000);
1027 assert_eq!(v[1].as_ref().unwrap().encoding_name, "h264");
1028
1029 let v = media
1030 .attributes_typed::<RtpMap>()
1031 .filter(|attr| {
1032 let Ok(at) = attr else { return false };
1033 at.payload_type == 99
1034 })
1035 .collect::<Vec<_>>();
1036 assert_eq!(v.len(), 1);
1037 assert_eq!(v[0].as_ref().unwrap().encoding_name, "h263-1998");
1038
1039 assert_eq!(
1040 media.get_first_attribute_value("fmtp"),
1041 Some(Some(
1042 "100 profile-level-id=42e016;max-mbps=108000;max-fs=3600"
1043 ))
1044 );
1045
1046 let r = media.attributes_typed::<Rtcp>().collect::<Vec<_>>();
1047 assert_eq!(r[0].as_ref().unwrap().addrtype, AddrType::Ip4);
1048 assert_eq!(r[0].as_ref().unwrap().port, 53020);
1049
1050 let rtcp_attr = media.get_first_attribute_typed::<Rtcp>().unwrap().unwrap();
1051 assert_eq!(rtcp_attr.addrtype, AddrType::Ip4);
1052 assert_eq!(rtcp_attr.port, 53020);
1053
1054 assert_eq!(
1055 media
1056 .get_attribute_values("fingerprint")
1057 .collect::<Vec<_>>(),
1058 &[Some("custom A1:B2:C3:D4:E5:F6")]
1059 );
1060
1061 assert!(media.get_attribute_values("foo").next().is_none());
1062 }
1063
1064 #[test]
1065 fn parse_session_attributes() {
1066 let session = Session {
1067 origin: Origin {
1068 username: Some("jdoe".into()),
1069 sess_id: "2890844526".into(),
1070 sess_version: 2890842807,
1071 nettype: "IN".into(),
1072 addrtype: "IP4".into(),
1073 unicast_address: "10.47.16.5".into(),
1074 },
1075 session_name: "SDP Seminar".into(),
1076 session_description: None,
1077 uri: None,
1078 emails: vec![],
1079 phones: vec![],
1080 connection: None,
1081 bandwidths: vec![],
1082 times: vec![Time {
1083 start_time: 0,
1084 stop_time: 0,
1085 repeats: vec![],
1086 }],
1087 time_zones: vec![],
1088 key: None,
1089 attributes: vec![
1090 Attribute {
1091 attribute: "rtpmap".into(),
1092 value: Some("99 h263-1998/90000".into()),
1093 },
1094 Attribute {
1095 attribute: "rtpmap".into(),
1096 value: Some("100 h264/90000".into()),
1097 },
1098 Attribute {
1099 attribute: "rtpmap".into(),
1100 value: Some(
1101 RtpMap {
1102 payload_type: 101,
1103 encoding_name: "L16".into(),
1104 clock_rate: 16000,
1105 encoding_params: Some("2".into()),
1106 }
1107 .to_string(),
1108 ),
1109 },
1110 Attribute {
1111 attribute: "rtcp".into(),
1112 value: None,
1113 },
1114 Attribute {
1115 attribute: "extmap".into(),
1116 value: Some(
1117 ExtMap {
1118 id: 1,
1119 direction: None,
1120 uri: "URI-toffset".to_string(),
1121 attributes: None,
1122 }
1123 .to_string(),
1124 ),
1125 },
1126 ],
1127 medias: vec![],
1128 };
1129
1130 assert!(session.has_attribute("rtpmap"));
1131 assert!(session.has_attribute("rtcp"));
1132 assert!(!session.has_attribute("foo"));
1133
1134 assert_eq!(
1135 session.get_first_attribute_value("rtpmap"),
1136 Some(Some("99 h263-1998/90000"))
1137 );
1138
1139 let v = session
1140 .get_first_attribute_value("rtpmap")
1141 .unwrap()
1142 .unwrap();
1143 let rtpmap = RtpMap::from_str(v).unwrap();
1144 assert_eq!(rtpmap.clock_rate, 90000);
1145 assert_eq!(rtpmap.encoding_name, "h263-1998");
1146 assert_ne!(rtpmap.encoding_name, "h263");
1147 assert_ne!(rtpmap.encoding_params, Some("2".to_string()));
1148 assert_eq!(rtpmap.payload_type, 99);
1149
1150 let rtpmap = session
1151 .get_first_attribute_typed::<RtpMap>()
1152 .unwrap()
1153 .unwrap();
1154 assert_eq!(rtpmap.clock_rate, 90000);
1155 assert_eq!(rtpmap.encoding_name, "h263-1998");
1156 assert_ne!(rtpmap.encoding_name, "h263");
1157 assert_ne!(rtpmap.encoding_params, Some("2".to_string()));
1158 assert_eq!(rtpmap.payload_type, 99);
1159
1160 assert_eq!(session.get_first_attribute_value("rtcp"), Some(None));
1161
1162 assert!(session.get_first_attribute_value("foo").is_none());
1163
1164 assert_eq!(
1165 session.get_attribute_values("rtpmap").collect::<Vec<_>>(),
1166 &[
1167 Some("99 h263-1998/90000"),
1168 Some("100 h264/90000"),
1169 Some("101 L16/16000/2")
1170 ]
1171 );
1172 assert_eq!(
1173 session.get_attribute_values("rtcp").collect::<Vec<_>>(),
1174 &[None]
1175 );
1176
1177 assert_eq!(
1178 session.get_attribute_values("extmap").collect::<Vec<_>>(),
1179 &[Some("1 URI-toffset")]
1180 );
1181
1182 assert!(session.get_attribute_values("foo").next().is_none());
1183
1184 let a = fallible_iterator::convert(session.attributes_typed::<RtpMap>())
1185 .collect::<Vec<_>>()
1186 .expect("Valid vector of attributes");
1187 assert_eq!(a[2].encoding_name, "L16");
1188 assert_eq!(a[0].payload_type, 99);
1189 }
1190
1191 #[test]
1192 fn origin_parse_address_error() {
1193 use std::net::Ipv6Addr;
1194
1195 let mut origin = Origin {
1196 username: None,
1197 sess_id: "1234".to_string(),
1198 sess_version: 0,
1199 nettype: NetType::In,
1200 addrtype: AddrType::Ip4,
1201 unicast_address: "127.0.0.1".to_string(),
1202 };
1203
1204 assert_eq!(origin.addrtype, AddrType::Ip4);
1205 assert_eq!(
1206 origin.try_parse_unicast_ip_address().unwrap(),
1207 IpAddr::V4(Ipv4Addr::LOCALHOST),
1208 );
1209
1210 origin.unicast_address = "127.0.0.1/24".to_string();
1211 assert_eq!(
1212 origin.try_parse_unicast_ip_address().unwrap_err(),
1213 origin.unicast_address
1214 );
1215
1216 origin.unicast_address = "non-ip".to_string();
1217 assert_eq!(
1218 origin.try_parse_unicast_ip_address().unwrap_err(),
1219 origin.unicast_address
1220 );
1221
1222 origin.set_unicast_ip_address(Ipv6Addr::LOCALHOST);
1223 assert_eq!(origin.addrtype, AddrType::Ip6);
1224 assert_eq!(
1225 origin.try_parse_unicast_ip_address().unwrap(),
1226 IpAddr::V6(Ipv6Addr::LOCALHOST),
1227 );
1228 }
1229
1230 #[test]
1231 fn connection_parse_address_error() {
1232 use std::net::Ipv6Addr;
1233
1234 let mut connection = Connection {
1235 nettype: NetType::In,
1236 addrtype: AddrType::Ip4,
1237 connection_address: "127.0.0.1".to_string(),
1238 };
1239
1240 assert_eq!(connection.addrtype, AddrType::Ip4);
1241 assert_eq!(
1242 connection.try_parse_connection_ip_address().unwrap(),
1243 IpAddr::V4(Ipv4Addr::LOCALHOST),
1244 );
1245
1246 connection.connection_address = "127.0.0.1/24".to_string();
1247 assert_eq!(
1248 connection.try_parse_connection_ip_address().unwrap_err(),
1249 connection.connection_address
1250 );
1251
1252 connection.connection_address = "non-ip".to_string();
1253 assert_eq!(
1254 connection.try_parse_connection_ip_address().unwrap_err(),
1255 connection.connection_address
1256 );
1257
1258 connection.set_connection_ip_address(Ipv6Addr::LOCALHOST);
1259 assert_eq!(connection.addrtype, AddrType::Ip6);
1260 assert_eq!(
1261 connection.try_parse_connection_ip_address().unwrap(),
1262 IpAddr::V6(Ipv6Addr::LOCALHOST),
1263 );
1264 }
1265}