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