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 Self::from_ip_addr_ttl(connection_address, None)
226 }
227
228 pub fn from_ip_addr_ttl(connection_address: impl Into<IpAddr>, ttl: Option<u8>) -> Self {
235 let ip = connection_address.into();
236 let connection_address = match ttl {
237 Some(ttl) if ip.is_ipv4() => format!("{ip}/{ttl}"),
238 _ => ip.to_string(),
239 };
240 Connection {
241 nettype: NetType::In,
242 addrtype: ip.into(),
243 connection_address,
244 }
245 }
246
247 pub fn new(nettype: NetType, addrtype: AddrType, connection_address: impl ToString) -> Self {
251 Connection {
252 nettype,
253 addrtype,
254 connection_address: connection_address.to_string(),
255 }
256 }
257
258 pub fn set_connection_ip_address(&mut self, connection_address: impl Into<IpAddr>) {
260 let connection_address = connection_address.into();
261 self.addrtype = connection_address.into();
262 self.connection_address = connection_address.to_string();
263 }
264
265 #[deprecated(since = "0.3.1", note = "use Connection::try_address instead")]
274 pub fn try_parse_connection_ip_address(&self) -> Result<IpAddr, &str> {
275 parse_connection_ip_address(&self.connection_address)
276 }
277
278 pub fn try_address(&self) -> Result<ConnectionAddress, ConnectionAddressError> {
289 parse_connection_address(&self.connection_address, &self.addrtype)
290 }
291
292 pub fn is_multicast(&self) -> bool {
297 parse_connection_ip_address(&self.connection_address)
298 .map(|address| address.is_multicast())
299 .unwrap_or(false)
300 }
301
302 pub fn try_is_multicast(&self) -> Result<bool, ConnectionAddressError> {
306 parse_connection_ip_address(&self.connection_address)
307 .map(|address| address.is_multicast())
308 .map_err(|_| ConnectionAddressError::InvalidAddress(self.connection_address.clone()))
309 }
310
311 pub fn is_ipv4(&self) -> bool {
315 parse_connection_ip_address(&self.connection_address)
316 .map(|address| address.is_ipv4())
317 .unwrap_or(false)
318 }
319
320 pub fn is_ipv6(&self) -> bool {
324 parse_connection_ip_address(&self.connection_address)
325 .map(|address| address.is_ipv6())
326 .unwrap_or(false)
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
335pub struct ConnectionAddress {
336 pub ip: IpAddr,
338 pub ttl: Option<u8>,
340 pub count: Option<u8>,
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
347pub enum ConnectionAddressError {
348 #[error("invalid connection address '{0}'")]
351 InvalidAddress(String),
352 #[error("invalid time-to-live in connection address '{0}'")]
355 InvalidTtl(String),
356 #[error("invalid address count in connection address '{0}'")]
359 InvalidCount(String),
360}
361
362pub(crate) fn parse_connection_address(
365 address: &str,
366 addrtype: &AddrType,
367) -> Result<ConnectionAddress, ConnectionAddressError> {
368 let ip = parse_connection_ip_address(address)
369 .map_err(|_| ConnectionAddressError::InvalidAddress(address.to_string()))?;
370 let ttl = match parse_ttl(address, addrtype) {
371 Some(Ok(ttl)) => Some(ttl),
372 Some(Err(_)) => return Err(ConnectionAddressError::InvalidTtl(address.to_string())),
373 None => None,
374 };
375 let count = match parse_number_of_addresses(address, addrtype) {
376 Some(Ok(count)) => Some(count),
377 Some(Err(_)) => return Err(ConnectionAddressError::InvalidCount(address.to_string())),
378 None => None,
379 };
380
381 Ok(ConnectionAddress { ip, ttl, count })
382}
383
384pub(crate) fn parse_connection_ip_address(address: &str) -> Result<IpAddr, &str> {
390 let ip_part = match address.split_once('/') {
391 Some((ip_part, _suffix)) => ip_part,
392 None => address,
393 };
394 ip_part.parse::<IpAddr>().map_err(|_| address)
395}
396
397pub(crate) fn parse_ttl<'a>(address: &'a str, addrtype: &AddrType) -> Option<Result<u8, &'a str>> {
401 if addrtype != &AddrType::Ip4 {
402 return None;
403 }
404
405 let rest = address.split_once('/')?.1;
406 let ttl = match rest.split_once('/') {
407 Some((ttl, _count)) => ttl,
408 None => rest,
409 };
410 Some(ttl.parse::<u8>().map_err(|_| ttl))
411}
412
413pub(crate) fn parse_number_of_addresses<'a>(
417 address: &'a str,
418 addrtype: &AddrType,
419) -> Option<Result<u8, &'a str>> {
420 let rest = address.split_once('/')?.1;
421 let count = match addrtype {
422 AddrType::Ip4 => rest.split_once('/')?.1,
423 AddrType::Ip6 => rest,
424 AddrType::Other(_) => return None,
425 };
426 Some(count.parse::<u8>().map_err(|_| count))
427}
428
429impl<T: Into<IpAddr>> From<T> for Connection {
430 fn from(ip_addr: T) -> Self {
431 Connection::from_ip_addr(ip_addr)
432 }
433}
434
435#[derive(Debug, PartialEq, Eq, Clone)]
439#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
440pub struct Bandwidth {
441 pub bwtype: String,
443 pub bandwidth: u64,
445}
446
447impl Bandwidth {
448 pub fn new(bwtype: BandwidthType, bandwidth: u64) -> Self {
449 Bandwidth {
450 bwtype: bwtype.to_string(),
451 bandwidth,
452 }
453 }
454
455 pub fn try_parse_bwtype(&self) -> Result<BandwidthType, ParseEnumError> {
457 BandwidthType::from_str(self.bwtype.as_str())
458 }
459
460 pub fn set_bwtype(&mut self, bwtype: BandwidthType) {
462 self.bwtype = bwtype.to_string();
463 }
464}
465
466#[derive(Debug, PartialEq, Eq, Clone)]
470#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
471pub struct Time {
472 pub start_time: u64,
474 pub stop_time: u64,
476 pub repeats: Vec<Repeat>,
478}
479
480impl Time {
481 pub fn new(start_time: u64, stop_time: u64) -> Self {
482 Time {
483 start_time,
484 stop_time,
485 repeats: Default::default(),
486 }
487 }
488
489 pub fn add_repeat(&mut self, repeat: Repeat) {
490 self.repeats.push(repeat);
491 }
492
493 pub fn add_repeats(&mut self, repeats: impl IntoIterator<Item = Repeat>) {
494 self.repeats.extend(repeats)
495 }
496
497 pub fn builder(start_time: u64, stop_time: u64) -> builders::Time {
498 builders::Time::new(start_time, stop_time)
499 }
500}
501
502#[derive(Debug, PartialEq, Eq, Clone)]
506#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
507pub struct Repeat {
508 pub repeat_interval: u64,
510 pub active_duration: u64,
512 pub offsets: Vec<u64>,
514}
515
516impl Repeat {
517 pub fn new(repeat_interval: u64, active_duration: u64) -> Self {
518 Repeat {
519 repeat_interval,
520 active_duration,
521 offsets: Default::default(),
522 }
523 }
524
525 pub fn add_offset(&mut self, offset: u64) {
526 self.offsets.push(offset);
527 }
528
529 pub fn add_offsets(&mut self, offsets: impl IntoIterator<Item = u64>) {
530 self.offsets.extend(offsets)
531 }
532
533 pub fn builder(repeat_interval: u64, active_duration: u64) -> builders::Repeat {
534 builders::Repeat::new(repeat_interval, active_duration)
535 }
536}
537
538#[derive(Debug, PartialEq, Eq, Clone)]
542#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
543pub struct TimeZone {
544 pub adjustment_time: u64,
546 pub offset: i64,
548}
549
550impl TimeZone {
551 pub fn new(adjustment_time: u64, offset: i64) -> Self {
552 TimeZone {
553 adjustment_time,
554 offset,
555 }
556 }
557}
558
559#[derive(Debug, PartialEq, Eq, Clone)]
564#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
565pub struct Key {
566 pub method: String,
568 pub encryption_key: Option<String>,
570}
571
572impl Key {
573 pub fn new(method: KeyMethod) -> Self {
574 Key {
575 method: method.to_string(),
576 encryption_key: Default::default(),
577 }
578 }
579
580 pub fn with_encryption_key(method: KeyMethod, encryption_key: impl ToString) -> Self {
582 Key {
583 method: method.to_string(),
584 encryption_key: Some(encryption_key.to_string()),
585 }
586 }
587
588 pub fn try_parse_keymethod(&self) -> Result<KeyMethod, ParseEnumError> {
590 KeyMethod::from_str(self.method.as_str())
591 }
592
593 pub fn set_keymethod(&mut self, method: KeyMethod) {
595 self.method = method.to_string();
596 }
597}
598
599impl From<KeyMethod> for Key {
600 fn from(method: KeyMethod) -> Self {
601 Key {
602 method: method.to_string(),
603 encryption_key: None,
604 }
605 }
606}
607
608#[derive(Debug, PartialEq, Eq, Clone)]
612#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
613pub struct Attribute {
614 pub attribute: String,
616 pub value: Option<String>,
618}
619
620impl Attribute {
621 pub fn new(attribute: impl ToString) -> Self {
625 Attribute {
626 attribute: attribute.to_string(),
627 value: Default::default(),
628 }
629 }
630
631 pub fn with_value(attribute: impl ToString, value: impl ToString) -> Self {
633 Attribute {
634 attribute: attribute.to_string(),
635 value: Some(value.to_string()),
636 }
637 }
638}
639
640#[derive(Debug, PartialEq, Eq, Clone)]
644#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
645pub struct Media {
646 pub media: String,
648 pub port: u16,
650 pub num_ports: Option<u16>,
652 pub proto: String,
654 pub fmt: String,
656 pub media_title: Option<String>,
658 pub connections: Vec<Connection>,
660 pub bandwidths: Vec<Bandwidth>,
662 pub key: Option<Key>,
664 pub attributes: Vec<Attribute>,
666}
667
668impl Media {
669 pub fn new(media: MediaType, port: u16, proto: TransportProto, fmt: impl ToString) -> Self {
670 Media {
671 media: media.to_string(),
672 port,
673 num_ports: Default::default(),
674 proto: proto.to_string(),
675 fmt: fmt.to_string(),
676 media_title: Default::default(),
677 connections: Default::default(),
678 bandwidths: Default::default(),
679 key: Default::default(),
680 attributes: Default::default(),
681 }
682 }
683
684 pub fn builder(
685 media: MediaType,
686 port: u16,
687 proto: TransportProto,
688 fmt: impl ToString,
689 ) -> builders::Media {
690 builders::Media::new(media, port, proto, fmt)
691 }
692
693 pub fn set_num_ports(&mut self, num_ports: u16) {
694 self.num_ports = Some(num_ports);
695 }
696
697 pub fn set_media_title(&mut self, media_title: impl ToString) {
698 self.media_title = Some(media_title.to_string());
699 }
700
701 pub fn add_connection(&mut self, connection: impl Into<Connection>) {
702 self.connections.push(connection.into());
703 }
704
705 pub fn add_connections(
706 &mut self,
707 connections: impl IntoIterator<Item = impl Into<Connection>>,
708 ) {
709 self.connections
710 .extend(connections.into_iter().map(|c| c.into()))
711 }
712
713 pub fn add_bandwidth(&mut self, bandwidth: Bandwidth) {
714 self.bandwidths.push(bandwidth);
715 }
716
717 pub fn add_bandwidths(&mut self, bandwidths: impl IntoIterator<Item = Bandwidth>) {
718 self.bandwidths.extend(bandwidths)
719 }
720
721 pub fn set_encryption_key(&mut self, key: impl Into<Key>) {
722 self.key = Some(key.into());
723 }
724
725 pub fn add_attribute(&mut self, attribute: impl Into<Attribute>) {
726 self.attributes.push(attribute.into());
727 }
728
729 pub fn add_attribute_from_str(&mut self, attribute: impl ToString) {
730 self.attributes.push(Attribute::new(attribute));
731 }
732
733 pub fn add_attribute_with_value(&mut self, attribute: impl ToString, value: impl ToString) {
734 self.attributes
735 .push(Attribute::with_value(attribute, value));
736 }
737
738 pub fn add_attributes(&mut self, attributes: impl IntoIterator<Item = impl Into<Attribute>>) {
739 self.attributes
740 .extend(attributes.into_iter().map(|a| a.into()))
741 }
742
743 pub fn add_attributes_from_strs(
744 &mut self,
745 attributes: impl IntoIterator<Item = impl ToString>,
746 ) {
747 self.attributes
748 .extend(attributes.into_iter().map(|a| Attribute::new(a)))
749 }
750
751 pub fn has_attribute(&self, name: &str) -> bool {
755 self.attributes
756 .iter()
757 .any(|a| a.attribute.eq_ignore_ascii_case(name))
758 }
759
760 pub fn get_first_attribute_value<'a>(&'a self, name: &'a str) -> Option<Option<&'a str>> {
765 self.get_attribute_values(name).next()
766 }
767
768 pub fn get_attribute_values<'a>(
772 &'a self,
773 name: &'a str,
774 ) -> impl Iterator<Item = Option<&'a str>> {
775 self.attributes
776 .iter()
777 .filter(move |a| a.attribute.eq_ignore_ascii_case(name))
778 .map(|a| a.value.as_deref())
779 }
780
781 pub fn try_parse_mediatype(&self) -> Result<MediaType, ParseEnumError> {
783 MediaType::from_str(self.media.as_str())
784 }
785
786 pub fn set_mediatype(&mut self, media: MediaType) {
788 self.media = media.to_string();
789 }
790
791 pub fn parse_transport_proto(&self) -> TransportProto {
793 TransportProto::from(self.proto.as_str())
794 }
795
796 pub fn set_transport_proto(&mut self, proto: TransportProto) {
798 self.proto = proto.to_string();
799 }
800
801 pub fn get_first_attribute_typed<T: TypedAttribute>(
803 &self,
804 ) -> Option<Result<T, AttributeError>> {
805 self.attributes_typed().next()
806 }
807
808 pub fn attributes_typed<'a, T: TypedAttribute>(
814 &'a self,
815 ) -> impl Iterator<Item = Result<T, AttributeError>> + 'a {
816 self.attributes
817 .iter()
818 .filter(move |a| a.attribute.eq_ignore_ascii_case(T::NAME))
819 .map(|a| {
820 T::from_str(a.value.as_deref().unwrap_or_default())
822 })
823 }
824}
825
826#[derive(Debug, PartialEq, Eq, Clone)]
830#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
831pub struct Session {
832 pub origin: Origin,
834 pub session_name: String,
836 pub session_description: Option<String>,
838 pub uri: Option<String>,
840 pub emails: Vec<String>,
842 pub phones: Vec<String>,
844 pub connection: Option<Connection>,
846 pub bandwidths: Vec<Bandwidth>,
848 pub times: Vec<Time>,
850 pub time_zones: Vec<TimeZone>,
852 pub key: Option<Key>,
854 pub attributes: Vec<Attribute>,
856 pub medias: Vec<Media>,
858}
859
860impl Session {
861 pub fn new(origin: Origin, session_name: impl ToString) -> Self {
862 Session {
863 origin,
864 session_name: session_name.to_string(),
865 session_description: Default::default(),
866 uri: Default::default(),
867 emails: Default::default(),
868 phones: Default::default(),
869 connection: Default::default(),
870 bandwidths: Default::default(),
871 times: Default::default(),
872 time_zones: Default::default(),
873 key: Default::default(),
874 attributes: Default::default(),
875 medias: Default::default(),
876 }
877 }
878
879 pub fn builder(origin: Origin, session_name: impl ToString) -> builders::Session {
880 builders::Session::new(origin, session_name)
881 }
882
883 pub fn set_session_description(&mut self, session_description: impl ToString) {
884 self.session_description = Some(session_description.to_string());
885 }
886
887 pub fn set_uri(&mut self, uri: impl ToString) {
888 self.uri = Some(uri.to_string());
889 }
890
891 pub fn add_email(&mut self, email: impl ToString) {
892 self.emails.push(email.to_string());
893 }
894
895 pub fn add_emails(&mut self, emails: impl IntoIterator<Item = impl ToString>) {
896 self.emails
897 .extend(emails.into_iter().map(|e| e.to_string()));
898 }
899
900 pub fn add_phone(&mut self, phone: impl ToString) {
901 self.phones.push(phone.to_string());
902 }
903
904 pub fn add_phones(&mut self, phones: impl IntoIterator<Item = impl ToString>) {
905 self.phones
906 .extend(phones.into_iter().map(|p| p.to_string()));
907 }
908
909 pub fn set_connection(&mut self, connection: impl Into<Connection>) {
910 self.connection = Some(connection.into());
911 }
912
913 pub fn add_bandwidth(&mut self, bandwidth: Bandwidth) {
914 self.bandwidths.push(bandwidth);
915 }
916
917 pub fn add_bandwidths(&mut self, bandwidths: impl IntoIterator<Item = Bandwidth>) {
918 self.bandwidths.extend(bandwidths);
919 }
920
921 pub fn add_time(&mut self, time: Time) {
922 self.times.push(time);
923 }
924
925 pub fn add_times(&mut self, times: impl IntoIterator<Item = Time>) {
926 self.times.extend(times);
927 }
928
929 pub fn add_time_zone(&mut self, time_zone: TimeZone) {
930 self.time_zones.push(time_zone);
931 }
932
933 pub fn add_time_zones(&mut self, time_zones: impl IntoIterator<Item = TimeZone>) {
934 self.time_zones.extend(time_zones);
935 }
936
937 pub fn set_encryption_key(&mut self, key: impl Into<Key>) {
938 self.key = Some(key.into());
939 }
940
941 pub fn add_attribute(&mut self, attribute: impl Into<Attribute>) {
942 self.attributes.push(attribute.into());
943 }
944
945 pub fn add_attribute_from_str(&mut self, attribute: impl ToString) {
946 self.attributes.push(Attribute::new(attribute));
947 }
948
949 pub fn add_attribute_with_value(&mut self, attribute: impl ToString, value: impl ToString) {
950 self.attributes
951 .push(Attribute::with_value(attribute, value));
952 }
953
954 pub fn add_attributes(&mut self, attributes: impl IntoIterator<Item = impl Into<Attribute>>) {
955 self.attributes
956 .extend(attributes.into_iter().map(|a| a.into()))
957 }
958
959 pub fn add_attributes_from_strs(
960 &mut self,
961 attributes: impl IntoIterator<Item = impl ToString>,
962 ) {
963 self.attributes
964 .extend(attributes.into_iter().map(|a| Attribute::new(a)))
965 }
966
967 pub fn add_media(&mut self, media: Media) {
968 self.medias.push(media);
969 }
970
971 pub fn add_medias(&mut self, medias: impl IntoIterator<Item = Media>) {
972 self.medias.extend(medias)
973 }
974
975 pub fn has_attribute(&self, name: &str) -> bool {
979 self.attributes
980 .iter()
981 .any(|a| a.attribute.eq_ignore_ascii_case(name))
982 }
983
984 pub fn get_first_attribute_value<'a>(&'a self, name: &'a str) -> Option<Option<&'a str>> {
989 self.get_attribute_values(name).next()
990 }
991
992 pub fn get_attribute_values<'a>(
996 &'a self,
997 name: &'a str,
998 ) -> impl Iterator<Item = Option<&'a str>> {
999 self.attributes
1000 .iter()
1001 .filter(move |a| a.attribute.eq_ignore_ascii_case(name))
1002 .map(|a| a.value.as_deref())
1003 }
1004
1005 pub fn get_first_attribute_typed<T: TypedAttribute>(
1007 &self,
1008 ) -> Option<Result<T, AttributeError>> {
1009 self.attributes_typed().next()
1010 }
1011
1012 pub fn attributes_typed<'a, T: TypedAttribute>(
1018 &'a self,
1019 ) -> impl Iterator<Item = Result<T, AttributeError>> + 'a {
1020 self.attributes
1021 .iter()
1022 .filter(move |a| a.attribute.eq_ignore_ascii_case(T::NAME))
1023 .map(|a| {
1024 T::from_str(a.value.as_deref().unwrap_or_default())
1026 })
1027 }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use std::net::Ipv4Addr;
1033
1034 use super::*;
1035
1036 #[test]
1037 fn parse_write() {
1038 let sdp = "v=0\r
1039o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
1040s=SDP Seminar\r
1041i=A Seminar on the session description protocol\r
1042u=http://www.example.com/seminars/sdp.pdf\r
1043e=j.doe@example.com (Jane Doe)\r
1044p=+1 617 555-6011\r
1045c=IN IP4 224.2.17.12/127\r
1046b=AS:128\r
1047t=2873397496 2873404696\r
1048r=7d 1h 0 25h\r
1049z=2882844526 -1h 2898848070 0\r
1050k=clear:1234\r
1051a=recvonly\r
1052m=audio 49170 RTP/AVP 0\r
1053a=fmtp:0 0-15\r
1054m=video 51372/2 RTP/AVP 99 97 98\r
1055a=rtpmap:99 h263-1998/90000\r
1056a=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
1057a=extmap:2/sendrecv http://example.com/082005/ext.htm#xmeta short\r
1058";
1059 let parsed = Session::parse(sdp.as_bytes()).unwrap();
1060 let mut written = vec![];
1061 parsed.write(&mut written).unwrap();
1062 assert_eq!(String::from_utf8_lossy(&written), sdp);
1063 assert_eq!(parsed.origin.addrtype, AddrType::Ip4);
1064 assert_ne!(parsed.origin.nettype, NetType::Pstn);
1065 assert_eq!(
1066 parsed.origin.try_parse_unicast_ip_address(),
1067 Ok(IpAddr::V4(std::net::Ipv4Addr::new(10, 47, 16, 5)))
1068 );
1069 assert_eq!(parsed.medias[0].try_parse_mediatype(), Ok(MediaType::Audio));
1070 assert_ne!(
1071 parsed.medias[1].parse_transport_proto(),
1072 TransportProto::RtpSavpf,
1073 );
1074 let f = fallible_iterator::convert(parsed.medias[0].attributes_typed::<Fmtp>())
1075 .collect::<Vec<_>>()
1076 .expect("Valid vector of attributes");
1077 assert_eq!(f.len(), 1);
1078 assert_eq!(f[0].format_specific_params[0].param, "0-15");
1079
1080 let e = fallible_iterator::convert(parsed.medias[1].attributes_typed::<ExtMap>())
1081 .collect::<Vec<_>>()
1082 .expect("Vector of extmap attributes");
1083 assert_eq!(e[0].id, 2);
1084 assert_eq!(e[0].direction, Some(Direction::SendRecv));
1085 assert_eq!(
1086 e[0].uri,
1087 "http://example.com/082005/ext.htm#xmeta".to_string()
1088 );
1089 assert_eq!(e[0].attributes, Some("short".to_string()));
1090
1091 let f = fallible_iterator::convert(parsed.medias[1].attributes_typed::<Fingerprint>())
1092 .collect::<Vec<_>>()
1093 .expect("Vector of fingerprint attributes");
1094
1095 assert_eq!(f[0].hash_func, HashFunc::Sha256);
1096 assert_eq!(f[0].fingerprint[4], 0xB2);
1097 assert_eq!(f[0].fingerprint.last(), Some(&0xAA));
1098 }
1099
1100 #[test]
1101 fn parse_media_attributes() {
1102 let media = Media {
1103 media: "video".into(),
1104 port: 51372,
1105 num_ports: Some(2),
1106 proto: "RTP/AVP".into(),
1107 fmt: "99 100".into(),
1108 media_title: None,
1109 connections: vec![],
1110 bandwidths: vec![],
1111 key: None,
1112 attributes: vec![
1113 Attribute {
1114 attribute: "rtpmap".into(),
1115 value: Some("99 h263-1998/90000".into()),
1116 },
1117 Attribute {
1118 attribute: "rtpmap".into(),
1119 value: Some("100 h264/90000".into()),
1120 },
1121 Attribute {
1122 attribute: "rtpmap".into(),
1123 value: None,
1124 },
1125 Attribute {
1126 attribute: "rtpmap".into(),
1127 value: Some(
1128 RtpMap {
1129 payload_type: 101,
1130 encoding_name: "L16".into(),
1131 clock_rate: 16000,
1132 encoding_params: Some("2".into()),
1133 }
1134 .to_string(),
1135 ),
1136 },
1137 Attribute {
1138 attribute: "fmtp".into(),
1139 value: Some(
1140 Fmtp {
1141 fmt: 100,
1142 format_specific_params: Vec::from([
1143 FmtpParam {
1144 param: "profile-level-id".to_string(),
1145 val: Some("42e016".to_string()),
1146 },
1147 FmtpParam {
1148 param: "max-mbps".to_string(),
1149 val: Some("108000".to_string()),
1150 },
1151 FmtpParam {
1152 param: "max-fs".to_string(),
1153 val: Some("3600".to_string()),
1154 },
1155 ]),
1156 }
1157 .to_string(),
1158 ),
1159 },
1160 Attribute {
1161 attribute: "rtcp".into(),
1162 value: Some(
1163 Rtcp {
1164 port: 53020,
1165 nettype: Some(NetType::In),
1166 addrtype: Some(AddrType::Ip4),
1167 connection_address: Some(
1168 std::net::Ipv4Addr::new(126, 16, 64, 4).to_string(),
1169 ),
1170 }
1171 .to_string(),
1172 ),
1173 },
1174 Attribute {
1175 attribute: "fingerprint".into(),
1176 value: Some(
1177 Fingerprint {
1178 hash_func: HashFunc::Other(("custom").to_string()),
1179 fingerprint: [0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6].to_vec(),
1180 }
1181 .to_string(),
1182 ),
1183 },
1184 ],
1185 };
1186
1187 assert!(media.has_attribute("rtpmap"));
1188 assert!(media.has_attribute("rtcp"));
1189 assert!(!media.has_attribute("foo"));
1190
1191 assert_eq!(
1192 media.get_first_attribute_value("rtpmap"),
1193 Some(Some("99 h263-1998/90000"))
1194 );
1195 assert_eq!(
1196 media.get_first_attribute_value("rtcp"),
1197 Some(Some("53020 IN IP4 126.16.64.4"))
1198 );
1199 assert!(media.get_first_attribute_value("foo").is_none());
1200
1201 assert_eq!(
1202 media.get_attribute_values("rtpmap").collect::<Vec<_>>(),
1203 &[
1204 Some("99 h263-1998/90000"),
1205 Some("100 h264/90000"),
1206 None,
1207 Some("101 L16/16000/2"),
1208 ]
1209 );
1210
1211 let v = media
1212 .attributes_typed::<RtpMap>()
1213 .collect::<Vec<Result<RtpMap, AttributeError>>>();
1214 assert_eq!(v[0].as_ref().unwrap().clock_rate, 90000);
1215 assert_eq!(v[1].as_ref().unwrap().encoding_name, "h264");
1216
1217 let v = media
1218 .attributes_typed::<RtpMap>()
1219 .filter(|attr| {
1220 let Ok(at) = attr else { return false };
1221 at.payload_type == 99
1222 })
1223 .collect::<Vec<_>>();
1224 assert_eq!(v.len(), 1);
1225 assert_eq!(v[0].as_ref().unwrap().encoding_name, "h263-1998");
1226
1227 assert_eq!(
1228 media.get_first_attribute_value("fmtp"),
1229 Some(Some(
1230 "100 profile-level-id=42e016;max-mbps=108000;max-fs=3600"
1231 ))
1232 );
1233
1234 let r = media.attributes_typed::<Rtcp>().collect::<Vec<_>>();
1235 assert_eq!(r[0].as_ref().unwrap().addrtype, Some(AddrType::Ip4));
1236 assert_eq!(r[0].as_ref().unwrap().port, 53020);
1237
1238 let rtcp_attr = media.get_first_attribute_typed::<Rtcp>().unwrap().unwrap();
1239 assert_eq!(rtcp_attr.addrtype, Some(AddrType::Ip4));
1240 assert_eq!(rtcp_attr.port, 53020);
1241
1242 assert_eq!(
1243 media
1244 .get_attribute_values("fingerprint")
1245 .collect::<Vec<_>>(),
1246 &[Some("custom A1:B2:C3:D4:E5:F6")]
1247 );
1248
1249 assert!(media.get_attribute_values("foo").next().is_none());
1250 }
1251
1252 #[test]
1253 fn parse_session_attributes() {
1254 let session = Session {
1255 origin: Origin {
1256 username: Some("jdoe".into()),
1257 sess_id: "2890844526".into(),
1258 sess_version: 2890842807,
1259 nettype: "IN".into(),
1260 addrtype: "IP4".into(),
1261 unicast_address: "10.47.16.5".into(),
1262 },
1263 session_name: "SDP Seminar".into(),
1264 session_description: None,
1265 uri: None,
1266 emails: vec![],
1267 phones: vec![],
1268 connection: None,
1269 bandwidths: vec![],
1270 times: vec![Time {
1271 start_time: 0,
1272 stop_time: 0,
1273 repeats: vec![],
1274 }],
1275 time_zones: vec![],
1276 key: None,
1277 attributes: vec![
1278 Attribute {
1279 attribute: "rtpmap".into(),
1280 value: Some("99 h263-1998/90000".into()),
1281 },
1282 Attribute {
1283 attribute: "rtpmap".into(),
1284 value: Some("100 h264/90000".into()),
1285 },
1286 Attribute {
1287 attribute: "rtpmap".into(),
1288 value: Some(
1289 RtpMap {
1290 payload_type: 101,
1291 encoding_name: "L16".into(),
1292 clock_rate: 16000,
1293 encoding_params: Some("2".into()),
1294 }
1295 .to_string(),
1296 ),
1297 },
1298 Attribute {
1299 attribute: "rtcp".into(),
1300 value: None,
1301 },
1302 Attribute {
1303 attribute: "extmap".into(),
1304 value: Some(
1305 ExtMap {
1306 id: 1,
1307 direction: None,
1308 uri: "URI-toffset".to_string(),
1309 attributes: None,
1310 }
1311 .to_string(),
1312 ),
1313 },
1314 ],
1315 medias: vec![],
1316 };
1317
1318 assert!(session.has_attribute("rtpmap"));
1319 assert!(session.has_attribute("rtcp"));
1320 assert!(!session.has_attribute("foo"));
1321
1322 assert_eq!(
1323 session.get_first_attribute_value("rtpmap"),
1324 Some(Some("99 h263-1998/90000"))
1325 );
1326
1327 let v = session
1328 .get_first_attribute_value("rtpmap")
1329 .unwrap()
1330 .unwrap();
1331 let rtpmap = RtpMap::from_str(v).unwrap();
1332 assert_eq!(rtpmap.clock_rate, 90000);
1333 assert_eq!(rtpmap.encoding_name, "h263-1998");
1334 assert_ne!(rtpmap.encoding_name, "h263");
1335 assert_ne!(rtpmap.encoding_params, Some("2".to_string()));
1336 assert_eq!(rtpmap.payload_type, 99);
1337
1338 let rtpmap = session
1339 .get_first_attribute_typed::<RtpMap>()
1340 .unwrap()
1341 .unwrap();
1342 assert_eq!(rtpmap.clock_rate, 90000);
1343 assert_eq!(rtpmap.encoding_name, "h263-1998");
1344 assert_ne!(rtpmap.encoding_name, "h263");
1345 assert_ne!(rtpmap.encoding_params, Some("2".to_string()));
1346 assert_eq!(rtpmap.payload_type, 99);
1347
1348 assert_eq!(session.get_first_attribute_value("rtcp"), Some(None));
1349
1350 assert!(session.get_first_attribute_value("foo").is_none());
1351
1352 assert_eq!(
1353 session.get_attribute_values("rtpmap").collect::<Vec<_>>(),
1354 &[
1355 Some("99 h263-1998/90000"),
1356 Some("100 h264/90000"),
1357 Some("101 L16/16000/2")
1358 ]
1359 );
1360 assert_eq!(
1361 session.get_attribute_values("rtcp").collect::<Vec<_>>(),
1362 &[None]
1363 );
1364
1365 assert_eq!(
1366 session.get_attribute_values("extmap").collect::<Vec<_>>(),
1367 &[Some("1 URI-toffset")]
1368 );
1369
1370 assert!(session.get_attribute_values("foo").next().is_none());
1371
1372 let a = fallible_iterator::convert(session.attributes_typed::<RtpMap>())
1373 .collect::<Vec<_>>()
1374 .expect("Valid vector of attributes");
1375 assert_eq!(a[2].encoding_name, "L16");
1376 assert_eq!(a[0].payload_type, 99);
1377 }
1378
1379 #[test]
1380 fn origin_parse_address_error() {
1381 use std::net::Ipv6Addr;
1382
1383 let mut origin = Origin {
1384 username: None,
1385 sess_id: "1234".to_string(),
1386 sess_version: 0,
1387 nettype: NetType::In,
1388 addrtype: AddrType::Ip4,
1389 unicast_address: "127.0.0.1".to_string(),
1390 };
1391
1392 assert_eq!(origin.addrtype, AddrType::Ip4);
1393 assert_eq!(
1394 origin.try_parse_unicast_ip_address().unwrap(),
1395 IpAddr::V4(Ipv4Addr::LOCALHOST),
1396 );
1397
1398 origin.unicast_address = "127.0.0.1/24".to_string();
1399 assert_eq!(
1400 origin.try_parse_unicast_ip_address().unwrap_err(),
1401 origin.unicast_address
1402 );
1403
1404 origin.unicast_address = "non-ip".to_string();
1405 assert_eq!(
1406 origin.try_parse_unicast_ip_address().unwrap_err(),
1407 origin.unicast_address
1408 );
1409
1410 origin.set_unicast_ip_address(Ipv6Addr::LOCALHOST);
1411 assert_eq!(origin.addrtype, AddrType::Ip6);
1412 assert_eq!(
1413 origin.try_parse_unicast_ip_address().unwrap(),
1414 IpAddr::V6(Ipv6Addr::LOCALHOST),
1415 );
1416 }
1417
1418 #[test]
1419 #[allow(deprecated)]
1420 fn connection_parse_address_error() {
1421 use std::net::Ipv6Addr;
1422
1423 let mut connection = Connection {
1424 nettype: NetType::In,
1425 addrtype: AddrType::Ip4,
1426 connection_address: "127.0.0.1".to_string(),
1427 };
1428
1429 assert_eq!(connection.addrtype, AddrType::Ip4);
1430 assert_eq!(
1431 connection.try_parse_connection_ip_address().unwrap(),
1432 IpAddr::V4(Ipv4Addr::LOCALHOST),
1433 );
1434
1435 connection.connection_address = "127.0.0.1/24".to_string();
1437 assert_eq!(
1438 connection.try_parse_connection_ip_address().unwrap(),
1439 IpAddr::V4(Ipv4Addr::LOCALHOST),
1440 );
1441
1442 connection.connection_address = "non-ip".to_string();
1443 assert_eq!(
1444 connection.try_parse_connection_ip_address().unwrap_err(),
1445 connection.connection_address
1446 );
1447
1448 connection.set_connection_ip_address(Ipv6Addr::LOCALHOST);
1449 assert_eq!(connection.addrtype, AddrType::Ip6);
1450 assert_eq!(
1451 connection.try_parse_connection_ip_address().unwrap(),
1452 IpAddr::V6(Ipv6Addr::LOCALHOST),
1453 );
1454 }
1455
1456 #[test]
1457 fn connection_try_is_multicast() {
1458 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1");
1459 assert_eq!(connection.try_is_multicast(), Ok(true));
1460
1461 let connection = Connection::new(NetType::In, AddrType::Ip4, "127.0.0.1");
1462 assert_eq!(connection.try_is_multicast(), Ok(false));
1463
1464 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/x");
1466 assert_eq!(connection.try_is_multicast(), Ok(true));
1467
1468 let connection = Connection::new(NetType::In, AddrType::Ip4, "x/16");
1469 assert_eq!(
1470 connection.try_is_multicast().unwrap_err(),
1471 ConnectionAddressError::InvalidAddress("x/16".into())
1472 );
1473 }
1474
1475 #[test]
1476 fn connection_is_ipv4_ipv6() {
1477 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/16");
1478 assert!(connection.is_ipv4());
1479 assert!(!connection.is_ipv6());
1480
1481 let connection = Connection::new(NetType::In, AddrType::Ip6, "ff12::5/3");
1482 assert!(connection.is_ipv6());
1483 assert!(!connection.is_ipv4());
1484
1485 let connection = Connection::new(NetType::In, AddrType::Ip4, "x/16");
1486 assert!(!connection.is_ipv4());
1487 assert!(!connection.is_ipv6());
1488 }
1489
1490 #[test]
1491 fn connection_try_address() {
1492 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/16/2");
1493 assert_eq!(
1494 connection.try_address().unwrap(),
1495 ConnectionAddress {
1496 ip: "224.2.1.1".parse().unwrap(),
1497 ttl: Some(16),
1498 count: Some(2),
1499 }
1500 );
1501
1502 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/16");
1503 assert_eq!(
1504 connection.try_address().unwrap(),
1505 ConnectionAddress {
1506 ip: "224.2.1.1".parse().unwrap(),
1507 ttl: Some(16),
1508 count: None,
1509 }
1510 );
1511
1512 let connection = Connection::new(NetType::In, AddrType::Ip4, "127.0.0.1");
1513 assert_eq!(
1514 connection.try_address().unwrap(),
1515 ConnectionAddress {
1516 ip: "127.0.0.1".parse().unwrap(),
1517 ttl: None,
1518 count: None,
1519 }
1520 );
1521
1522 let connection = Connection::new(NetType::In, AddrType::Ip6, "ff12::5/3");
1524 assert_eq!(
1525 connection.try_address().unwrap(),
1526 ConnectionAddress {
1527 ip: "ff12::5".parse().unwrap(),
1528 ttl: None,
1529 count: Some(3),
1530 }
1531 );
1532
1533 let connection = Connection::new(NetType::In, AddrType::Ip4, "x/16");
1534 assert_eq!(
1535 connection.try_address().unwrap_err(),
1536 ConnectionAddressError::InvalidAddress("x/16".into())
1537 );
1538
1539 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/x");
1540 assert_eq!(
1541 connection.try_address().unwrap_err(),
1542 ConnectionAddressError::InvalidTtl("224.2.1.1/x".into())
1543 );
1544
1545 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/300");
1546 assert_eq!(
1547 connection.try_address().unwrap_err(),
1548 ConnectionAddressError::InvalidTtl("224.2.1.1/300".into())
1549 );
1550
1551 let connection = Connection::new(NetType::In, AddrType::Ip4, "224.2.1.1/16/x");
1552 assert_eq!(
1553 connection.try_address().unwrap_err(),
1554 ConnectionAddressError::InvalidCount("224.2.1.1/16/x".into())
1555 );
1556 }
1557
1558 #[test]
1559 fn connection_from_ip_addr_ttl() {
1560 use std::net::Ipv6Addr;
1561
1562 let connection = Connection::from_ip_addr_ttl(Ipv4Addr::new(224, 2, 1, 1), Some(16));
1563 assert_eq!(connection.nettype, NetType::In);
1564 assert_eq!(connection.addrtype, AddrType::Ip4);
1565 assert_eq!(connection.connection_address, "224.2.1.1/16");
1566
1567 let connection =
1570 Connection::from_ip_addr_ttl(Ipv6Addr::new(0xff12, 0, 0, 0, 0, 0, 0, 5), Some(16));
1571 assert_eq!(connection.addrtype, AddrType::Ip6);
1572 assert_eq!(connection.connection_address, "ff12::5");
1573
1574 let connection = Connection::from_ip_addr_ttl(Ipv4Addr::new(224, 2, 1, 1), None);
1575 assert_eq!(connection.connection_address, "224.2.1.1");
1576 }
1577
1578 #[test]
1579 fn attribute_lookup_case_insensitive() {
1580 let sdp = b"v=0\r
1581o=- 0 0 IN IP4 0.0.0.0\r
1582s=x\r
1583t=0 0\r
1584a=Foo:Bar\r
1585m=audio 49170 RTP/AVP 0\r
1586a=RTPMAP:0 pcma/8000\r
1587";
1588 let session = Session::parse(&sdp[..]).unwrap();
1589
1590 assert_eq!(session.attributes[0].attribute, "Foo");
1592 assert!(session.has_attribute("foo"));
1594 assert!(session.has_attribute("FOO"));
1595 assert_eq!(session.get_first_attribute_value("FOO"), Some(Some("Bar")));
1596
1597 let media = &session.medias[0];
1598 assert!(media.has_attribute("rtpmap"));
1599 assert!(media.has_attribute("RtPmAp"));
1600 assert_eq!(
1601 media.get_attribute_values("rtpmap").collect::<Vec<_>>(),
1602 vec![Some("0 pcma/8000")]
1603 );
1604 }
1605}