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