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//!
70//! ## Validation
71//!
72//! The parser checks the structure of a session description (line keys, field
73//! counts, and the syntax of numeric and enum fields) but does not enforce
74//! all semantic constraints of the RFCs. Likewise the data model accepts
75//! values that the RFC grammars would not allow, e.g. `Origin::sess_id` must
76//! be a numeric string but this is not checked. Lines containing NUL or CR
77//! bytes are rejected, and the "nettype"/"addrtype" subfields of "o=" and
78//! "c=" must be RFC 8866 tokens; other non-conforming model values are still
79//! written verbatim.
80//!
81//! `Session::write()` writes the data model out as-is without validating it,
82//! apart from stripping characters that would break the line structure (CR,
83//! LF, NUL) and writing a `t=0 0` line if the session description contains no
84//! time description at all.
85//!
86//! Applications that need stricter conformance must validate session
87//! descriptions themselves.
88
89use 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/// Originator of the session.
107///
108/// See [RFC 8866 Section 5.2](https://tools.ietf.org/html/rfc8866#section-5.2) for more details.
109#[derive(Debug, PartialEq, Eq, Clone)]
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111pub struct Origin {
112    /// User's login on the originating host.
113    pub username: Option<String>,
114    /// Session ID to make the whole `Origin` unique.
115    ///
116    /// Must be a numeric string but this is *not* checked.
117    pub sess_id: String,
118    /// Session version number.
119    pub sess_version: u64,
120    /// Type of network for this session.
121    pub nettype: NetType,
122    /// Type of the `unicast_address`.
123    pub addrtype: AddrType,
124    /// Address where the session was created.
125    pub unicast_address: String,
126}
127
128impl Origin {
129    /// Construct an [`Origin`] with the specified IP `unicast_address`
130    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    /// Construct an [`Origin`]
147    ///
148    /// See also [`Origin::with_ip_addr`]
149    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    /// Sets the `unicast_address` & `addrtype` of `self` from the specified `IpAddr`
167    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    ///
173    /// Returns the `Ok` with the parsed `IpAddr` or `Err` with the string address
174    /// if parsing failed.
175    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    /// Construct an [`crate::builders::Origin`] with the specified IP `unicast_address`
186    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    /// Construct an [`crate::builders::Origin`]
195    ///
196    /// See also [`Origin::builder_with_ip_addr`]
197    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/// Connection data for the session or media.
209///
210/// See [RFC 8866 Section 5.7](https://tools.ietf.org/html/rfc8866#section-5.7) for more details.
211#[derive(Debug, PartialEq, Eq, Clone)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213pub struct Connection {
214    /// Type of network for this connection.
215    pub nettype: NetType,
216    /// Type of the `connection_address`.
217    pub addrtype: AddrType,
218    /// Connection address.
219    pub connection_address: String,
220}
221
222impl Connection {
223    /// Construct a [`Connection`] with the specified IP `connection_address`
224    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    /// Construct a [`Connection`]
234    ///
235    /// See also [`Connection::from_ip_addr`]
236    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    /// Sets the `connection_address` & `addrtype` of `self` from the specified `IpAddr`
245    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    /// Tries to parse the `connection_address` `String` of `self` as `IpAddr`
252    ///
253    /// Returns the `Ok` with the parsed `IpAddr` or `Err` with the string address
254    /// if parsing failed.
255    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/// Bandwidth information for the session or media.
269///
270/// See [RFC 8866 Section 5.8](https://tools.ietf.org/html/rfc8866#section-5.8) for more details.
271#[derive(Debug, PartialEq, Eq, Clone)]
272#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
273pub struct Bandwidth {
274    /// Bandwidth type, usually "CT" or "AS".
275    pub bwtype: String,
276    /// Bandwidth.
277    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    /// Tries to parse the `bwtype` `String` of `self` as `BandwidthType`
289    pub fn try_parse_bwtype(&self) -> Result<BandwidthType, ParseEnumError> {
290        BandwidthType::from_str(self.bwtype.as_str())
291    }
292
293    /// Sets the `bwtype` `String` of `self` from the specified `BandwidthType`
294    pub fn set_bwtype(&mut self, bwtype: BandwidthType) {
295        self.bwtype = bwtype.to_string();
296    }
297}
298
299/// Timing information of the session.
300///
301/// See [RFC 8866 Section 5.9](https://tools.ietf.org/html/rfc8866#section-5.9) for more details.
302#[derive(Debug, PartialEq, Eq, Clone)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub struct Time {
305    /// Start time of the session in seconds since 1900.
306    pub start_time: u64,
307    /// Stop time of the session in seconds since 1900.
308    pub stop_time: u64,
309    /// Repeat times.
310    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/// Repeat times for timing information.
336///
337/// See [RFC 8866 Section 5.10](https://tools.ietf.org/html/rfc8866#section-5.10) for more details.
338#[derive(Debug, PartialEq, Eq, Clone)]
339#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
340pub struct Repeat {
341    /// Repeat interval in seconds.
342    pub repeat_interval: u64,
343    /// Duration of one repeat.
344    pub active_duration: u64,
345    /// Offsets for the repeats from the `start_time`.
346    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/// Time zone information for the session.
372///
373/// See [RFC 8866 Section 5.11](https://tools.ietf.org/html/rfc8866#section-5.11) for more details.
374#[derive(Debug, PartialEq, Eq, Clone)]
375#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
376pub struct TimeZone {
377    /// Time in seconds since 1900 when the adjustment happens.
378    pub adjustment_time: u64,
379    /// Amount of the adjustment in seconds.
380    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/// Encryption key for the session or media.
393///
394/// Note: This field is obsolete and MUST NOT be used. It is included only for legacy reasons
395/// See [RFC 8866 Section 5.12](https://tools.ietf.org/html/rfc8866#section-5.12) for more details.
396#[derive(Debug, PartialEq, Eq, Clone)]
397#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
398pub struct Key {
399    /// Encryption method that is used.
400    pub method: String,
401    /// Encryption key or information to obtain the encryption key.
402    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    /// Constructs a [`crate::Key`] and define its `encryption_key`.
414    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    /// Tries to parse the `method` `String` of `self` as `KeyMethod`
422    pub fn try_parse_keymethod(&self) -> Result<KeyMethod, ParseEnumError> {
423        KeyMethod::from_str(self.method.as_str())
424    }
425
426    /// Sets the `method` `String` of `self` from the specified `KeyMethod`
427    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/// Attributes for the session or media.
442///
443/// See [RFC 8866 Section 5.13](https://tools.ietf.org/html/rfc8866#section-5.13) for more details.
444#[derive(Debug, PartialEq, Eq, Clone)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446pub struct Attribute {
447    /// Attribute name.
448    pub attribute: String,
449    /// Attribute value.
450    pub value: Option<String>,
451}
452
453impl Attribute {
454    /// Constructs an [`crate::Attribute`].
455    ///
456    /// Use [`crate::Attribute::with_value`] if you need to define the `value`.
457    pub fn new(attribute: impl ToString) -> Self {
458        Attribute {
459            attribute: attribute.to_string(),
460            value: Default::default(),
461        }
462    }
463
464    /// Constructs an [`crate::Attribute`] and define its `value`.
465    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/// Media description.
474///
475/// See [RFC 8866 Section 5.14](https://tools.ietf.org/html/rfc8866#section-5.14) for more details.
476#[derive(Debug, PartialEq, Eq, Clone)]
477#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
478pub struct Media {
479    /// Media type, e.g. "audio", "video", "text", "application" or "message".
480    pub media: String,
481    /// Transport port to which the media is sent.
482    pub port: u16,
483    /// Number of ports starting at `port` used for the media.
484    pub num_ports: Option<u16>,
485    /// Transport protocol.
486    pub proto: String,
487    /// Media format description.
488    pub fmt: String,
489    /// Media title.
490    pub media_title: Option<String>,
491    /// Connection data for the media.
492    pub connections: Vec<Connection>,
493    /// Bandwidth information for the media.
494    pub bandwidths: Vec<Bandwidth>,
495    /// Encryption key for the media.
496    pub key: Option<Key>,
497    /// Attributes of the media.
498    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    /// Checks if the given attribute exists.
585    ///
586    /// The attribute name is matched case-insensitively.
587    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    /// Gets the first value of the given attribute.
594    ///
595    /// The outer `Option` reflects the availability of the attribute.
596    /// The inner `Option` reflects the optional value of the attribute.
597    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    /// Gets an iterator over all attribute values of the given name.
602    ///
603    /// The attribute name is matched case-insensitively.
604    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    /// Tries to parse the `media` `String` of `self` as `MediaType`
615    pub fn try_parse_mediatype(&self) -> Result<MediaType, ParseEnumError> {
616        MediaType::from_str(self.media.as_str())
617    }
618
619    /// Sets the `media` `String` of `self` from the specified `MediaType`
620    pub fn set_mediatype(&mut self, media: MediaType) {
621        self.media = media.to_string();
622    }
623
624    /// Parses the `proto` `String` of `self` as `TransportProto`
625    pub fn parse_transport_proto(&self) -> TransportProto {
626        TransportProto::from(self.proto.as_str())
627    }
628
629    /// Sets the `proto` `String` of `self` from the specified `TransportProto`
630    pub fn set_transport_proto(&mut self, proto: TransportProto) {
631        self.proto = proto.to_string();
632    }
633
634    /// Gets the first value of the given typed attribute.
635    pub fn get_first_attribute_typed<T: TypedAttribute>(
636        &self,
637    ) -> Option<Result<T, AttributeError>> {
638        self.attributes_typed().next()
639    }
640
641    /// Gets an iterator over all attributes parsed as the typed attribute `T`.
642    ///
643    /// Each item represents the attribute parsing `Result`.
644    ///
645    /// The iterator does not terminate upon an error item; continues with the next attribute
646    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/// SDP session description.
666///
667/// See [RFC 8866 Section 5](https://tools.ietf.org/html/rfc8866#section-5) for more details.
668#[derive(Debug, PartialEq, Eq, Clone)]
669#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
670pub struct Session {
671    /// Originator of the session.
672    pub origin: Origin,
673    /// Name of the session.
674    pub session_name: String,
675    /// Session description.
676    pub session_description: Option<String>,
677    /// URI to additional information about the session.
678    pub uri: Option<String>,
679    /// E-Mail contacts for the session.
680    pub emails: Vec<String>,
681    /// Phone contacts for the session.
682    pub phones: Vec<String>,
683    /// Connection data for the session.
684    pub connection: Option<Connection>,
685    /// Bandwidth information for the session.
686    pub bandwidths: Vec<Bandwidth>,
687    /// Timing information for the session.
688    pub times: Vec<Time>,
689    /// Time zone information for the session.
690    pub time_zones: Vec<TimeZone>,
691    /// Encryption key for the session.
692    pub key: Option<Key>,
693    /// Attributes of the session.
694    pub attributes: Vec<Attribute>,
695    /// Media descriptions for this session.
696    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    /// Checks if the given attribute exists.
815    ///
816    /// The attribute name is matched case-insensitively.
817    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    /// Gets the first value of the given attribute.
824    ///
825    /// The outer `Option` reflects the availability of the attribute.
826    /// The inner `Option` reflects the optional value of the attribute.
827    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    /// Gets an iterator over all attribute values of the given name.
832    ///
833    /// The attribute name is matched case-insensitively.
834    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    /// Gets the first value of the given typed attribute.
845    pub fn get_first_attribute_typed<T: TypedAttribute>(
846        &self,
847    ) -> Option<Result<T, AttributeError>> {
848        self.attributes_typed().next()
849    }
850
851    /// Gets an iterator over all attributes parsed as the typed attribute `T`.
852    ///
853    /// Each item represents the attribute parsing `Result`.
854    ///
855    /// The iterator does not terminate upon an error item; continues with the next attribute
856    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        // The stored case is preserved...
1312        assert_eq!(session.attributes[0].attribute, "Foo");
1313        // ...but lookups match case-insensitively in both directions.
1314        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}