Skip to main content

sdp_types/
lib.rs

1//
2// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
3//
4// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
5
6//! Crate for handling SDP ([RFC 8866](https://tools.ietf.org/html/rfc8866))
7//! session descriptions, including a parser and serializer.
8//!
9//! ## Serializing an SDP
10//!
11//! ```rust,ignore
12//! // Create SDP session description
13//! let sdp = sdp_types::Session {
14//!     ...
15//! };
16//!
17//! // or
18//! let sdp = sdp_types::Session::new(sdp_types::Origin::new(...), "my-session");
19//!
20//! // or
21//! let sdp = sdp_types::Session::builder(sdp_types::Origin::new(...), "my-session")
22//!     .attribute(...)
23//!     .attribute(...)
24//!     .media(...)
25//!     .media(...)
26//!     ...
27//!     .build();
28//!
29//! // And write it to an `Vec<u8>`
30//! let mut output = Vec::new();
31//! sdp.write(&mut output).unwrap();
32//! ```
33//!
34//! ## Parsing an SDP
35//!
36//! ```rust,no_run
37//! # let data = [0u8];
38//! // Parse SDP session description from a byte slice
39//! let sdp = sdp_types::Session::parse(&data).unwrap();
40//!
41//! // Access the 'tool' attribute
42//! match sdp.get_first_attribute_value("tool") {
43//!     Some(Some(tool)) => println!("tool: {tool}"),
44//!     Some(None) => println!("tool: empty"),
45//!     None => println!("no tool attribute"),
46//! }
47//!
48//! // Access all the 'rtpmap' attributes as an `RtpMap` type
49//! // returns an iterator of type `Iterator<Item = Result<RtpMap, AttributeError>>`
50//! let r = sdp.attributes_typed::<sdp_types::RtpMap>();
51//!
52//! // Access the first 'rtpmap' attribute as an `RtpMap` type:
53//! match sdp.get_first_attribute_typed::<sdp_types::RtpMap>() {
54//!     Some(Ok(rtpmap)) => println!("rtpmap: {rtpmap}"),
55//!     Some(Err(err)) => println!("rtpmap: parsing error: {err}"),
56//!     None => println!("no rtpmap attribute"),
57//! }
58//! ```
59//!
60//! ## Limitations
61//!
62//!  * SDP session descriptions are by default in UTF-8 but an optional `charset`
63//!    attribute can change this for various SDP fields, including various other
64//!    attributes. This is currently not supported, only UTF-8 is supported.
65//!
66//!  * Network addresses, Phone numbers, E-Mail addresses and various other fields
67//!    are currently parsed as a plain string and not according to the SDP
68//!    grammar.
69
70use 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/// Originator of the session.
88///
89/// See [RFC 8866 Section 5.2](https://tools.ietf.org/html/rfc8866#section-5.2) for more details.
90#[derive(Debug, PartialEq, Eq, Clone)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92pub struct Origin {
93    /// User's login on the originating host.
94    pub username: Option<String>,
95    /// Session ID to make the whole `Origin` unique.
96    ///
97    /// Must be a numeric string but this is *not* checked.
98    pub sess_id: String,
99    /// Session version number.
100    pub sess_version: u64,
101    /// Type of network for this session.
102    pub nettype: NetType,
103    /// Type of the `unicast_address`.
104    pub addrtype: AddrType,
105    /// Address where the session was created.
106    pub unicast_address: String,
107}
108
109impl Origin {
110    /// Construct an [`Origin`] with the specified IP `unicast_address`
111    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    /// Construct an [`Origin`]
128    ///
129    /// See also [`Origin::with_ip_addr`]
130    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    /// Sets the `unicast_address` & `addrtype` of `self` from the specified `IpAddr`
148    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    ///
154    /// Returns the `Ok` with the parsed `IpAddr` or `Err` with the string address
155    /// if parsing failed.
156    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    /// Construct an [`crate::builders::Origin`] with the specified IP `unicast_address`
167    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    /// Construct an [`crate::builders::Origin`]
176    ///
177    /// See also [`Origin::builder_with_ip_addr`]
178    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/// Connection data for the session or media.
190///
191/// See [RFC 8866 Section 5.7](https://tools.ietf.org/html/rfc8866#section-5.7) for more details.
192#[derive(Debug, PartialEq, Eq, Clone)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194pub struct Connection {
195    /// Type of network for this connection.
196    pub nettype: NetType,
197    /// Type of the `connection_address`.
198    pub addrtype: AddrType,
199    /// Connection address.
200    pub connection_address: String,
201}
202
203impl Connection {
204    /// Construct a [`Connection`] with the specified IP `connection_address`
205    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    /// Construct a [`Connection`]
215    ///
216    /// See also [`Connection::from_ip_addr`]
217    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    /// Sets the `connection_address` & `addrtype` of `self` from the specified `IpAddr`
226    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    /// Tries to parse the `connection_address` `String` of `self` as `IpAddr`
233    ///
234    /// Returns the `Ok` with the parsed `IpAddr` or `Err` with the string address
235    /// if parsing failed.
236    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/// Bandwidth information for the session or media.
250///
251/// See [RFC 8866 Section 5.8](https://tools.ietf.org/html/rfc8866#section-5.8) for more details.
252#[derive(Debug, PartialEq, Eq, Clone)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub struct Bandwidth {
255    /// Bandwidth type, usually "CT" or "AS".
256    pub bwtype: String,
257    /// Bandwidth.
258    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    /// Tries to parse the `bwtype` `String` of `self` as `BandwidthType`
270    pub fn try_parse_bwtype(&self) -> Result<BandwidthType, ParseEnumError> {
271        BandwidthType::from_str(self.bwtype.as_str())
272    }
273
274    /// Sets the `bwtype` `String` of `self` from the specified `BandwidthType`
275    pub fn set_bwtype(&mut self, bwtype: BandwidthType) {
276        self.bwtype = bwtype.to_string();
277    }
278}
279
280/// Timing information of the session.
281///
282/// See [RFC 8866 Section 5.9](https://tools.ietf.org/html/rfc8866#section-5.9) for more details.
283#[derive(Debug, PartialEq, Eq, Clone)]
284#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
285pub struct Time {
286    /// Start time of the session in seconds since 1900.
287    pub start_time: u64,
288    /// Stop time of the session in seconds since 1900.
289    pub stop_time: u64,
290    /// Repeat times.
291    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/// Repeat times for timing information.
317///
318/// See [RFC 8866 Section 5.10](https://tools.ietf.org/html/rfc8866#section-5.10) for more details.
319#[derive(Debug, PartialEq, Eq, Clone)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub struct Repeat {
322    /// Repeat interval in seconds.
323    pub repeat_interval: u64,
324    /// Duration of one repeat.
325    pub active_duration: u64,
326    /// Offsets for the repeats from the `start_time`.
327    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/// Time zone information for the session.
353///
354/// See [RFC 8866 Section 5.11](https://tools.ietf.org/html/rfc8866#section-5.11) for more details.
355#[derive(Debug, PartialEq, Eq, Clone)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357pub struct TimeZone {
358    /// Time in seconds since 1900 when the adjustment happens.
359    pub adjustment_time: u64,
360    /// Amount of the adjustment in seconds.
361    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/// Encryption key for the session or media.
374///
375/// Note: This field is obsolete and MUST NOT be used. It is included only for legacy reasons
376/// See [RFC 8866 Section 5.12](https://tools.ietf.org/html/rfc8866#section-5.12) for more details.
377#[derive(Debug, PartialEq, Eq, Clone)]
378#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
379pub struct Key {
380    /// Encryption method that is used.
381    pub method: String,
382    /// Encryption key or information to obtain the encryption key.
383    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    /// Constructs a [`crate::Key`] and define its `encryption_key`.
395    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    /// Tries to parse the `method` `String` of `self` as `KeyMethod`
403    pub fn try_parse_keymethod(&self) -> Result<KeyMethod, ParseEnumError> {
404        KeyMethod::from_str(self.method.as_str())
405    }
406
407    /// Sets the `method` `String` of `self` from the specified `KeyMethod`
408    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/// Attributes for the session or media.
423///
424/// See [RFC 8866 Section 5.13](https://tools.ietf.org/html/rfc8866#section-5.13) for more details.
425#[derive(Debug, PartialEq, Eq, Clone)]
426#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
427pub struct Attribute {
428    /// Attribute name.
429    pub attribute: String,
430    /// Attribute value.
431    pub value: Option<String>,
432}
433
434impl Attribute {
435    /// Constructs an [`crate::Attribute`].
436    ///
437    /// Use [`crate::Attribute::with_value`] if you need to define the `value`.
438    pub fn new(attribute: impl ToString) -> Self {
439        Attribute {
440            attribute: attribute.to_string(),
441            value: Default::default(),
442        }
443    }
444
445    /// Constructs an [`crate::Attribute`] and define its `value`.
446    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/// Media description.
455///
456/// See [RFC 8866 Section 5.14](https://tools.ietf.org/html/rfc8866#section-5.14) for more details.
457#[derive(Debug, PartialEq, Eq, Clone)]
458#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
459pub struct Media {
460    /// Media type, e.g. "audio", "video", "text", "application" or "message".
461    pub media: String,
462    /// Transport port to which the media is sent.
463    pub port: u16,
464    /// Number of ports starting at `port` used for the media.
465    pub num_ports: Option<u16>,
466    /// Transport protocol.
467    pub proto: String,
468    /// Media format description.
469    pub fmt: String,
470    /// Media title.
471    pub media_title: Option<String>,
472    /// Connection data for the media.
473    pub connections: Vec<Connection>,
474    /// Bandwidth information for the media.
475    pub bandwidths: Vec<Bandwidth>,
476    /// Encryption key for the media.
477    pub key: Option<Key>,
478    /// Attributes of the media.
479    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    /// Checks if the given attribute exists.
566    pub fn has_attribute(&self, name: &str) -> bool {
567        self.attributes.iter().any(|a| a.attribute == name)
568    }
569
570    /// Gets the first value of the given attribute.
571    ///
572    /// The outter `Option` reflects the availability of the attribute.
573    /// The inner `Option` reflects the optional value of the attribute.
574    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    /// Gets an iterator over all attribute values of the given name.
579    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    /// Tries to parse the `media` `String` of `self` as `MediaType`
590    pub fn try_parse_mediatype(&self) -> Result<MediaType, ParseEnumError> {
591        MediaType::from_str(self.media.as_str())
592    }
593
594    /// Sets the `media` `String` of `self` from the specified `MediaType`
595    pub fn set_mediatype(&mut self, media: MediaType) {
596        self.media = media.to_string();
597    }
598
599    /// Parses the `proto` `String` of `self` as `TransportProto`
600    pub fn parse_transport_proto(&self) -> TransportProto {
601        TransportProto::from(self.proto.as_str())
602    }
603
604    /// Sets the `proto` `String` of `self` from the specified `TransportProto`
605    pub fn set_transport_proto(&mut self, proto: TransportProto) {
606        self.proto = proto.to_string();
607    }
608
609    /// Gets the first value of the given typed attribute.
610    pub fn get_first_attribute_typed<T: TypedAttribute>(
611        &self,
612    ) -> Option<Result<T, AttributeError>> {
613        self.attributes_typed().next()
614    }
615
616    /// Gets an iterator over all attribute values of the given name.
617    ///
618    /// Each item represents the attribute parsing `Result`.
619    ///
620    /// The iterator does not terminate upon an error item; continues with the next attribute
621    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/// SDP session description.
641///
642/// See [RFC 8866 Section 5](https://tools.ietf.org/html/rfc8866#section-5) for more details.
643#[derive(Debug, PartialEq, Eq, Clone)]
644#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
645pub struct Session {
646    /// Originator of the session.
647    pub origin: Origin,
648    /// Name of the session.
649    pub session_name: String,
650    /// Session description.
651    pub session_description: Option<String>,
652    /// URI to additional information about the session.
653    pub uri: Option<String>,
654    /// E-Mail contacts for the session.
655    pub emails: Vec<String>,
656    /// Phone contacts for the session.
657    pub phones: Vec<String>,
658    /// Connection data for the session.
659    pub connection: Option<Connection>,
660    /// Bandwidth information for the session.
661    pub bandwidths: Vec<Bandwidth>,
662    /// Timing information for the session.
663    pub times: Vec<Time>,
664    /// Time zone information for the session.
665    pub time_zones: Vec<TimeZone>,
666    /// Encryption key for the session.
667    pub key: Option<Key>,
668    /// Attributes of the session.
669    pub attributes: Vec<Attribute>,
670    /// Media descriptions for this session.
671    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    /// Checks if the given attribute exists.
790    pub fn has_attribute(&self, name: &str) -> bool {
791        self.attributes.iter().any(|a| a.attribute == name)
792    }
793
794    /// Gets the first value of the given attribute.
795    ///
796    /// The outter `Option` reflects the availability of the attribute.
797    /// The inner `Option` reflects the optional value of the attribute.
798    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    /// Gets an iterator over all attribute values of the given name.
803    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    /// Gets the first value of the given typed attribute.
814    pub fn get_first_attribute_typed<T: TypedAttribute>(
815        &self,
816    ) -> Option<Result<T, AttributeError>> {
817        self.attributes_typed().next()
818    }
819
820    /// Gets an iterator over all attribute values of the given name.
821    ///
822    /// Each item represents the attribute parsing `Result`.
823    ///
824    /// The iterator does not terminate upon an error item; continues with the next attribute
825    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}