Skip to main content

rtc_sdp/description/
session.rs

1//! The session description — a whole SDP document.
2//!
3//! A [`SessionDescription`](crate::description::session::SessionDescription) is the session-level fields (`v=`, `o=`, `s=`, `t=`, …) followed by
4//! any number of [`MediaDescription`](crate::description::media::MediaDescription)s. `unmarshal` parses one
5//! from a string and `marshal` prints it back; the round trip is faithful, including attributes
6//! this crate does not interpret.
7//!
8//! The `ATTR_KEY_*` constants name the `a=` attributes WebRTC relies on — `mid`, `msid`,
9//! `setup`, `rtcp-mux`, `extmap` and the direction flags — and the `SEMANTIC_TOKEN_*` constants
10//! name the grouping semantics used with `a=group` and `a=msid-semantic`.
11
12use std::collections::HashMap;
13use std::convert::TryFrom;
14use std::time::{Duration, SystemTime, UNIX_EPOCH};
15use std::{fmt, io};
16
17use url::Url;
18
19use crate::lexer::*;
20use crate::util::*;
21use shared::error::{Error, Result};
22
23use super::common::*;
24use super::media::*;
25
26/// Constants for SDP attributes used in JSEP
27pub const ATTR_KEY_CANDIDATE: &str = "candidate";
28/// `a=end-of-candidates`: ICE gathering for this section is complete.
29pub const ATTR_KEY_END_OF_CANDIDATES: &str = "end-of-candidates";
30/// `a=identity`: an identity assertion for the session.
31pub const ATTR_KEY_IDENTITY: &str = "identity";
32/// `a=group`: groups m-lines, as BUNDLE does.
33pub const ATTR_KEY_GROUP: &str = "group";
34/// `a=ssrc`: declares an SSRC and its attributes.
35pub const ATTR_KEY_SSRC: &str = "ssrc";
36/// `a=ssrc-group`: relates SSRCs, such as an RTX stream to its primary.
37pub const ATTR_KEY_SSRC_GROUP: &str = "ssrc-group";
38/// `a=msid`: associates a track with a media stream.
39pub const ATTR_KEY_MSID: &str = "msid";
40/// `a=msid-semantic`: declares the semantics used by `a=msid`.
41pub const ATTR_KEY_MSID_SEMANTIC: &str = "msid-semantic";
42/// `a=setup`: the DTLS role — `active`, `passive` or `actpass`.
43pub const ATTR_KEY_CONNECTION_SETUP: &str = "setup";
44/// `a=mid`: the media identification tag that names this m-line.
45pub const ATTR_KEY_MID: &str = "mid";
46/// `a=ice-lite`: the endpoint is an ICE-lite implementation.
47pub const ATTR_KEY_ICELITE: &str = "ice-lite";
48/// `a=rtcp-mux`: RTP and RTCP share one port.
49pub const ATTR_KEY_RTCPMUX: &str = "rtcp-mux";
50/// `a=rtcp-rsize`: reduced-size RTCP is supported.
51pub const ATTR_KEY_RTCPRSIZE: &str = "rtcp-rsize";
52/// `a=inactive`: neither send nor receive.
53pub const ATTR_KEY_INACTIVE: &str = "inactive";
54/// `a=recvonly`: receive only.
55pub const ATTR_KEY_RECV_ONLY: &str = "recvonly";
56/// `a=sendonly`: send only.
57pub const ATTR_KEY_SEND_ONLY: &str = "sendonly";
58/// `a=sendrecv`: send and receive.
59pub const ATTR_KEY_SEND_RECV: &str = "sendrecv";
60/// `a=extmap`: maps an RTP header-extension URI to an id.
61pub const ATTR_KEY_EXT_MAP: &str = "extmap";
62/// `a=extmap-allow-mixed`: one- and two-byte header extensions may be mixed.
63pub const ATTR_KEY_EXTMAP_ALLOW_MIXED: &str = "extmap-allow-mixed";
64/// `a=max-message-size`: the largest SCTP message this endpoint accepts.
65pub const ATTR_KEY_MAX_MESSAGE_SIZE: &str = "max-message-size";
66
67/// Constants for semantic tokens used in JSEP
68pub const SEMANTIC_TOKEN_LIP_SYNCHRONIZATION: &str = "LS";
69/// `FID`: flow identification — the SSRCs carry the same content, as RTX does.
70pub const SEMANTIC_TOKEN_FLOW_IDENTIFICATION: &str = "FID";
71/// `FEC`: one SSRC carries error-correction data for another.
72pub const SEMANTIC_TOKEN_FORWARD_ERROR_CORRECTION: &str = "FEC";
73// https://datatracker.ietf.org/doc/html/rfc5956#section-4.1
74/// `FEC-FR`: the FEC framework grouping semantic.
75pub const SEMANTIC_TOKEN_FORWARD_ERROR_CORRECTION_FRAMEWORK: &str = "FEC-FR";
76/// `WMS`: WebRTC media streams, used with `a=msid-semantic`.
77pub const SEMANTIC_TOKEN_WEBRTC_MEDIA_STREAMS: &str = "WMS";
78
79/// Version describes the value provided by the "v=" field which gives
80/// the version of the Session Description Protocol.
81pub type Version = isize;
82
83/// Origin defines the structure for the "o=" field which provides the
84/// originator of the session plus a session identifier and version number.
85#[derive(Debug, Default, Clone)]
86pub struct Origin {
87    /// The originator's user name, or `-` when withheld.
88    pub username: String,
89    /// A globally unique session identifier.
90    pub session_id: u64,
91    /// Incremented on each modification of the session description.
92    pub session_version: u64,
93    /// The network type, always `IN` in practice.
94    pub network_type: String,
95    /// The address type, `IP4` or `IP6`.
96    pub address_type: String,
97    /// The originator's address. WebRTC does not use it and sends a placeholder.
98    pub unicast_address: String,
99}
100
101impl fmt::Display for Origin {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(
104            f,
105            "{} {} {} {} {} {}",
106            self.username,
107            self.session_id,
108            self.session_version,
109            self.network_type,
110            self.address_type,
111            self.unicast_address,
112        )
113    }
114}
115
116impl Origin {
117    /// An empty session description with SDP version 0.
118    pub fn new() -> Self {
119        Origin {
120            username: "".to_owned(),
121            session_id: 0,
122            session_version: 0,
123            network_type: "".to_owned(),
124            address_type: "".to_owned(),
125            unicast_address: "".to_owned(),
126        }
127    }
128}
129
130/// SessionName describes a structured representations for the "s=" field
131/// and is the textual session name.
132pub type SessionName = String;
133
134/// EmailAddress describes a structured representations for the "e=" line
135/// which specifies email contact information for the person responsible for
136/// the conference.
137pub type EmailAddress = String;
138
139/// PhoneNumber describes a structured representations for the "p=" line
140/// specify phone contact information for the person responsible for the
141/// conference.
142pub type PhoneNumber = String;
143
144/// TimeZone defines the structured object for "z=" line which describes
145/// repeated sessions scheduling.
146#[derive(Debug, Default, Clone)]
147pub struct TimeZone {
148    /// The time at which the offset takes effect.
149    pub adjustment_time: u64,
150    /// The offset from the session's base time, in seconds.
151    pub offset: i64,
152}
153
154impl fmt::Display for TimeZone {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{} {}", self.adjustment_time, self.offset)
157    }
158}
159
160/// TimeDescription describes "t=", "r=" fields of the session description
161/// which are used to specify the start and stop times for a session as well as
162/// repeat intervals and durations for the scheduled session.
163#[derive(Debug, Default, Clone)]
164pub struct TimeDescription {
165    /// `t=<start-time> <stop-time>`
166    ///
167    /// <https://tools.ietf.org/html/rfc4566#section-5.9>
168    pub timing: Timing,
169
170    /// `r=<repeat interval> <active duration> <offsets from start-time>`
171    ///
172    /// <https://tools.ietf.org/html/rfc4566#section-5.10>
173    pub repeat_times: Vec<RepeatTime>,
174}
175
176/// Timing defines the "t=" field's structured representation for the start and
177/// stop times.
178#[derive(Debug, Default, Clone)]
179pub struct Timing {
180    /// Session start time in NTP seconds; `0` means unbounded.
181    pub start_time: u64,
182    /// Session stop time in NTP seconds; `0` means unbounded.
183    pub stop_time: u64,
184}
185
186impl fmt::Display for Timing {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        write!(f, "{} {}", self.start_time, self.stop_time)
189    }
190}
191
192/// RepeatTime describes the "r=" fields of the session description which
193/// represents the intervals and durations for repeated scheduled sessions.
194#[derive(Debug, Default, Clone)]
195pub struct RepeatTime {
196    /// How often the session repeats, in seconds.
197    pub interval: i64,
198    /// How long each repetition lasts, in seconds.
199    pub duration: i64,
200    /// Offsets from the start time at which the session repeats.
201    pub offsets: Vec<i64>,
202}
203
204impl fmt::Display for RepeatTime {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "{} {}", self.interval, self.duration)?;
207
208        for value in &self.offsets {
209            write!(f, " {value}")?;
210        }
211        Ok(())
212    }
213}
214
215/// SessionDescription is a a well-defined format for conveying sufficient
216/// information to discover and participate in a multimedia session.
217#[derive(Debug, Default, Clone)]
218pub struct SessionDescription {
219    /// `v=0`
220    ///
221    /// <https://tools.ietf.org/html/rfc4566#section-5.1>
222    pub version: Version,
223
224    /// `o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>`
225    ///
226    /// <https://tools.ietf.org/html/rfc4566#section-5.2>
227    pub origin: Origin,
228
229    /// `s=<session name>`
230    ///
231    /// <https://tools.ietf.org/html/rfc4566#section-5.3>
232    pub session_name: SessionName,
233
234    /// `i=<session description>`
235    ///
236    /// <https://tools.ietf.org/html/rfc4566#section-5.4>
237    pub session_information: Option<Information>,
238
239    /// `u=<uri>`
240    ///
241    /// <https://tools.ietf.org/html/rfc4566#section-5.5>
242    pub uri: Option<Url>,
243
244    /// `e=<email-address>`
245    ///
246    /// <https://tools.ietf.org/html/rfc4566#section-5.6>
247    pub email_address: Option<EmailAddress>,
248
249    /// `p=<phone-number>`
250    ///
251    /// <https://tools.ietf.org/html/rfc4566#section-5.6>
252    pub phone_number: Option<PhoneNumber>,
253
254    /// `c=<nettype> <addrtype> <connection-address>`
255    ///
256    /// <https://tools.ietf.org/html/rfc4566#section-5.7>
257    pub connection_information: Option<ConnectionInformation>,
258
259    /// `b=<bwtype>:<bandwidth>`
260    ///
261    /// <https://tools.ietf.org/html/rfc4566#section-5.8>
262    pub bandwidth: Vec<Bandwidth>,
263
264    /// <https://tools.ietf.org/html/rfc4566#section-5.9>
265    /// <https://tools.ietf.org/html/rfc4566#section-5.10>
266    pub time_descriptions: Vec<TimeDescription>,
267
268    /// `z=<adjustment time> <offset> <adjustment time> <offset> ...`
269    ///
270    /// <https://tools.ietf.org/html/rfc4566#section-5.11>
271    pub time_zones: Vec<TimeZone>,
272
273    /// `k=<method>`
274    ///
275    /// `k=<method>:<encryption key>`
276    ///
277    /// <https://tools.ietf.org/html/rfc4566#section-5.12>
278    pub encryption_key: Option<EncryptionKey>,
279
280    /// `a=<attribute>`
281    ///
282    /// `a=<attribute>:<value>`
283    ///
284    /// <https://tools.ietf.org/html/rfc4566#section-5.13>
285    pub attributes: Vec<Attribute>,
286
287    /// <https://tools.ietf.org/html/rfc4566#section-5.14>
288    pub media_descriptions: Vec<MediaDescription>,
289}
290
291impl fmt::Display for SessionDescription {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        write_key_value(f, "v=", Some(&self.version))?;
294        write_key_value(f, "o=", Some(&self.origin))?;
295        write_key_value(f, "s=", Some(&self.session_name))?;
296
297        write_key_value(f, "i=", self.session_information.as_ref())?;
298
299        if let Some(uri) = &self.uri {
300            write_key_value(f, "u=", Some(uri))?;
301        }
302        write_key_value(f, "e=", self.email_address.as_ref())?;
303        write_key_value(f, "p=", self.phone_number.as_ref())?;
304        if let Some(connection_information) = &self.connection_information {
305            write_key_value(f, "c=", Some(&connection_information))?;
306        }
307
308        for bandwidth in &self.bandwidth {
309            write_key_value(f, "b=", Some(&bandwidth))?;
310        }
311        for time_description in &self.time_descriptions {
312            write_key_value(f, "t=", Some(&time_description.timing))?;
313            for repeat_time in &time_description.repeat_times {
314                write_key_value(f, "r=", Some(&repeat_time))?;
315            }
316        }
317
318        write_key_slice_of_values(f, "z=", &self.time_zones)?;
319
320        write_key_value(f, "k=", self.encryption_key.as_ref())?;
321        for attribute in &self.attributes {
322            write_key_value(f, "a=", Some(&attribute))?;
323        }
324
325        for media_description in &self.media_descriptions {
326            write_key_value(f, "m=", Some(&media_description.media_name))?;
327            write_key_value(f, "i=", media_description.media_title.as_ref())?;
328            if let Some(connection_information) = &media_description.connection_information {
329                write_key_value(f, "c=", Some(&connection_information))?;
330            }
331            for bandwidth in &media_description.bandwidth {
332                write_key_value(f, "b=", Some(&bandwidth))?;
333            }
334            write_key_value(f, "k=", media_description.encryption_key.as_ref())?;
335            for attribute in &media_description.attributes {
336                write_key_value(f, "a=", Some(&attribute))?;
337            }
338        }
339
340        Ok(())
341    }
342}
343
344/// Reset cleans the SessionDescription, and sets all fields back to their default values
345impl SessionDescription {
346    /// API to match draft-ietf-rtcweb-jsep
347    /// Move to webrtc or its own package?
348    /// NewJSEPSessionDescription creates a new SessionDescription with
349    /// some settings that are required by the JSEP spec.
350    pub fn new_jsep_session_description(identity: bool) -> Self {
351        let d = SessionDescription {
352            version: 0,
353            origin: Origin {
354                username: "-".to_string(),
355                session_id: new_session_id(),
356                session_version: SystemTime::now()
357                    .duration_since(UNIX_EPOCH)
358                    .unwrap_or_else(|_| Duration::from_secs(0))
359                    .subsec_nanos() as u64,
360                network_type: "IN".to_string(),
361                address_type: "IP4".to_string(),
362                unicast_address: "0.0.0.0".to_string(),
363            },
364            session_name: "-".to_string(),
365            session_information: None,
366            uri: None,
367            email_address: None,
368            phone_number: None,
369            connection_information: None,
370            bandwidth: vec![],
371            time_descriptions: vec![TimeDescription {
372                timing: Timing {
373                    start_time: 0,
374                    stop_time: 0,
375                },
376                repeat_times: vec![],
377            }],
378            time_zones: vec![],
379            encryption_key: None,
380            attributes: vec![], // TODO: implement trickle ICE
381            media_descriptions: vec![],
382        };
383
384        if identity {
385            d.with_property_attribute(ATTR_KEY_IDENTITY.to_string())
386        } else {
387            d
388        }
389    }
390
391    /// WithPropertyAttribute adds a property attribute 'a=key' to the session description
392    pub fn with_property_attribute(mut self, key: String) -> Self {
393        self.attributes.push(Attribute::new(key, None));
394        self
395    }
396
397    /// WithValueAttribute adds a value attribute 'a=key:value' to the session description
398    pub fn with_value_attribute(mut self, key: String, value: String) -> Self {
399        self.attributes.push(Attribute::new(key, Some(value)));
400        self
401    }
402
403    /// WithFingerprint adds a fingerprint to the session description
404    pub fn with_fingerprint(self, algorithm: String, value: String) -> Self {
405        self.with_value_attribute("fingerprint".to_string(), algorithm + " " + value.as_str())
406    }
407
408    /// WithMedia adds a media description to the session description
409    pub fn with_media(mut self, md: MediaDescription) -> Self {
410        self.media_descriptions.push(md);
411        self
412    }
413
414    fn build_codec_map(&self) -> HashMap<u8, Codec> {
415        let mut codecs: HashMap<u8, Codec> = HashMap::new();
416
417        for m in &self.media_descriptions {
418            codecs.extend(m.codecs());
419        }
420
421        codecs
422    }
423
424    /// get_codec_for_payload_type scans the SessionDescription for the given payload type and returns the codec
425    pub fn get_codec_for_payload_type(&self, payload_type: u8) -> Result<Codec> {
426        let codecs = self.build_codec_map();
427
428        if let Some(codec) = codecs.get(&payload_type) {
429            Ok(codec.clone())
430        } else {
431            Err(Error::PayloadTypeNotFound)
432        }
433    }
434
435    /// get_payload_type_for_codec scans the SessionDescription for a codec that matches the provided codec
436    /// as closely as possible and returns its payload type
437    pub fn get_payload_type_for_codec(&self, wanted: &Codec) -> Result<u8> {
438        let codecs = self.build_codec_map();
439
440        for (payload_type, codec) in codecs.iter() {
441            if codecs_match(wanted, codec) {
442                return Ok(*payload_type);
443            }
444        }
445
446        Err(Error::CodecNotFound)
447    }
448
449    /// Returns whether an attribute exists
450    pub fn has_attribute(&self, key: &str) -> bool {
451        self.attributes.iter().any(|a| a.key == key)
452    }
453
454    /// Attribute returns the value of an attribute and if it exists
455    pub fn attribute(&self, key: &str) -> Option<&String> {
456        for a in &self.attributes {
457            if a.key == key {
458                return a.value.as_ref();
459            }
460        }
461        None
462    }
463
464    /// Marshal takes a SDP struct to text
465    ///
466    /// <https://tools.ietf.org/html/rfc4566#section-5>
467    ///
468    /// Session description
469    ///    v=  (protocol version)
470    ///    o=  (originator and session identifier)
471    ///    s=  (session name)
472    ///    i=* (session information)
473    ///    u=* (URI of description)
474    ///    e=* (email address)
475    ///    p=* (phone number)
476    ///    c=* (connection information -- not required if included in
477    ///         all media)
478    ///    b=* (zero or more bandwidth information lines)
479    ///    One or more time descriptions ("t=" and "r=" lines; see below)
480    ///    z=* (time zone adjustments)
481    ///    k=* (encryption key)
482    ///    a=* (zero or more session attribute lines)
483    ///    Zero or more media descriptions
484    ///
485    /// Time description
486    ///    t=  (time the session is active)
487    ///    r=* (zero or more repeat times)
488    ///
489    /// Media description, if present
490    ///    m=  (media name and transport address)
491    ///    i=* (media title)
492    ///    c=* (connection information -- optional if included at
493    ///         session level)
494    ///    b=* (zero or more bandwidth information lines)
495    ///    k=* (encryption key)
496    ///    a=* (zero or more media attribute lines)
497    pub fn marshal(&self) -> String {
498        self.to_string()
499    }
500
501    /// Unmarshal is the primary function that deserializes the session description
502    /// message and stores it inside of a structured SessionDescription object.
503    ///
504    /// The States Transition Table describes the computation flow between functions
505    /// (namely s1, s2, s3, ...) for a parsing procedure that complies with the
506    /// specifications laid out by the rfc4566#section-5 as well as by JavaScript
507    /// Session Establishment Protocol draft. Links:
508    ///     <https://tools.ietf.org/html/rfc4566#section-5>
509    ///     <https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24>
510    ///
511    /// <https://tools.ietf.org/html/rfc4566#section-5>
512    ///
513    /// Session description
514    ///    v=  (protocol version)
515    ///    o=  (originator and session identifier)
516    ///    s=  (session name)
517    ///    i=* (session information)
518    ///    u=* (URI of description)
519    ///    e=* (email address)
520    ///    p=* (phone number)
521    ///    c=* (connection information -- not required if included in
522    ///         all media)
523    ///    b=* (zero or more bandwidth information lines)
524    ///    One or more time descriptions ("t=" and "r=" lines; see below)
525    ///    z=* (time zone adjustments)
526    ///    k=* (encryption key)
527    ///    a=* (zero or more session attribute lines)
528    ///    Zero or more media descriptions
529    ///
530    /// Time description
531    ///    t=  (time the session is active)
532    ///    r=* (zero or more repeat times)
533    ///
534    /// Media description, if present
535    ///    m=  (media name and transport address)
536    ///    i=* (media title)
537    ///    c=* (connection information -- optional if included at
538    ///         session level)
539    ///    b=* (zero or more bandwidth information lines)
540    ///    k=* (encryption key)
541    ///    a=* (zero or more media attribute lines)
542    ///
543    /// In order to generate the following state table and draw subsequent
544    /// deterministic finite-state automota ("DFA") the following regex was used to
545    /// derive the DFA:
546    ///    vosi?u?e?p?c?b*(tr*)+z?k?a*(mi?c?b*k?a*)*
547    /// possible place and state to exit:
548    ///                    **   * * *  ** * * * *
549    ///                    99   1 1 1  11 1 1 1 1
550    ///                         3 1 1  26 5 5 4 4
551    ///
552    /// Please pay close attention to the `k`, and `a` parsing states. In the table
553    /// below in order to distinguish between the states belonging to the media
554    /// description as opposed to the session description, the states are marked
555    /// with an asterisk ("a*", "k*").
556    ///
557    /// ```ignore
558    /// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
559    /// | STATES | a* | a*,k* | a  | a,k | b  | b,c | e | i  | m  | o | p | r,t | s | t | u  | v | z  |
560    /// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
561    /// |   s1   |    |       |    |     |    |     |   |    |    |   |   |     |   |   |    | 2 |    |
562    /// |   s2   |    |       |    |     |    |     |   |    |    | 3 |   |     |   |   |    |   |    |
563    /// |   s3   |    |       |    |     |    |     |   |    |    |   |   |     | 4 |   |    |   |    |
564    /// |   s4   |    |       |    |     |    |   5 | 6 |  7 |    |   | 8 |     |   | 9 | 10 |   |    |
565    /// |   s5   |    |       |    |     |  5 |     |   |    |    |   |   |     |   | 9 |    |   |    |
566    /// |   s6   |    |       |    |     |    |   5 |   |    |    |   | 8 |     |   | 9 |    |   |    |
567    /// |   s7   |    |       |    |     |    |   5 | 6 |    |    |   | 8 |     |   | 9 | 10 |   |    |
568    /// |   s8   |    |       |    |     |    |   5 |   |    |    |   |   |     |   | 9 |    |   |    |
569    /// |   s9   |    |       |    |  11 |    |     |   |    | 12 |   |   |   9 |   |   |    |   | 13 |
570    /// |   s10  |    |       |    |     |    |   5 | 6 |    |    |   | 8 |     |   | 9 |    |   |    |
571    /// |   s11  |    |       | 11 |     |    |     |   |    | 12 |   |   |     |   |   |    |   |    |
572    /// |   s12  |    |    14 |    |     |    |  15 |   | 16 | 12 |   |   |     |   |   |    |   |    |
573    /// |   s13  |    |       |    |  11 |    |     |   |    | 12 |   |   |     |   |   |    |   |    |
574    /// |   s14  | 14 |       |    |     |    |     |   |    | 12 |   |   |     |   |   |    |   |    |
575    /// |   s15  |    |    14 |    |     | 15 |     |   |    | 12 |   |   |     |   |   |    |   |    |
576    /// |   s16  |    |    14 |    |     |    |  15 |   |    | 12 |   |   |     |   |   |    |   |    |
577    /// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+
578    /// ```
579    pub fn unmarshal<R: io::BufRead + io::Seek>(reader: &mut R) -> Result<Self> {
580        let mut lexer = Lexer {
581            desc: SessionDescription {
582                version: 0,
583                origin: Origin::new(),
584                session_name: "".to_owned(),
585                session_information: None,
586                uri: None,
587                email_address: None,
588                phone_number: None,
589                connection_information: None,
590                bandwidth: vec![],
591                time_descriptions: vec![],
592                time_zones: vec![],
593                encryption_key: None,
594                attributes: vec![],
595                media_descriptions: vec![],
596            },
597            reader,
598        };
599
600        let mut state = Some(StateFn { f: s1 });
601        while let Some(s) = state {
602            state = (s.f)(&mut lexer)?;
603        }
604
605        Ok(lexer.desc)
606    }
607}
608
609impl From<SessionDescription> for String {
610    fn from(sdp: SessionDescription) -> String {
611        sdp.marshal()
612    }
613}
614
615impl TryFrom<String> for SessionDescription {
616    type Error = Error;
617    fn try_from(sdp_string: String) -> Result<Self> {
618        let mut reader = io::Cursor::new(sdp_string.as_bytes());
619        let session_description = SessionDescription::unmarshal(&mut reader)?;
620        Ok(session_description)
621    }
622}
623
624fn s1<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
625    let (key, _) = read_type(lexer.reader)?;
626    if &key == b"v=" {
627        return Ok(Some(StateFn {
628            f: unmarshal_protocol_version,
629        }));
630    }
631
632    Err(Error::SdpInvalidSyntax(String::from_utf8(key)?))
633}
634
635fn s2<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
636    let (key, _) = read_type(lexer.reader)?;
637    if &key == b"o=" {
638        return Ok(Some(StateFn {
639            f: unmarshal_origin,
640        }));
641    }
642
643    Err(Error::SdpInvalidSyntax(String::from_utf8(key)?))
644}
645
646fn s3<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
647    let (key, _) = read_type(lexer.reader)?;
648    if &key == b"s=" {
649        return Ok(Some(StateFn {
650            f: unmarshal_session_name,
651        }));
652    }
653
654    Err(Error::SdpInvalidSyntax(String::from_utf8(key)?))
655}
656
657fn s4<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
658    let (key, _) = read_type(lexer.reader)?;
659    match key.as_slice() {
660        b"i=" => Ok(Some(StateFn {
661            f: unmarshal_session_information,
662        })),
663        b"u=" => Ok(Some(StateFn { f: unmarshal_uri })),
664        b"e=" => Ok(Some(StateFn { f: unmarshal_email })),
665        b"p=" => Ok(Some(StateFn { f: unmarshal_phone })),
666        b"c=" => Ok(Some(StateFn {
667            f: unmarshal_session_connection_information,
668        })),
669        b"b=" => Ok(Some(StateFn {
670            f: unmarshal_session_bandwidth,
671        })),
672        b"t=" => Ok(Some(StateFn {
673            f: unmarshal_timing,
674        })),
675        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
676    }
677}
678
679fn s5<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
680    let (key, _) = read_type(lexer.reader)?;
681    match key.as_slice() {
682        b"b=" => Ok(Some(StateFn {
683            f: unmarshal_session_bandwidth,
684        })),
685        b"t=" => Ok(Some(StateFn {
686            f: unmarshal_timing,
687        })),
688        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
689    }
690}
691
692fn s6<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
693    let (key, _) = read_type(lexer.reader)?;
694    match key.as_slice() {
695        b"p=" => Ok(Some(StateFn { f: unmarshal_phone })),
696        b"c=" => Ok(Some(StateFn {
697            f: unmarshal_session_connection_information,
698        })),
699        b"b=" => Ok(Some(StateFn {
700            f: unmarshal_session_bandwidth,
701        })),
702        b"t=" => Ok(Some(StateFn {
703            f: unmarshal_timing,
704        })),
705        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
706    }
707}
708
709fn s7<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
710    let (key, _) = read_type(lexer.reader)?;
711    match key.as_slice() {
712        b"u=" => Ok(Some(StateFn { f: unmarshal_uri })),
713        b"e=" => Ok(Some(StateFn { f: unmarshal_email })),
714        b"p=" => Ok(Some(StateFn { f: unmarshal_phone })),
715        b"c=" => Ok(Some(StateFn {
716            f: unmarshal_session_connection_information,
717        })),
718        b"b=" => Ok(Some(StateFn {
719            f: unmarshal_session_bandwidth,
720        })),
721        b"t=" => Ok(Some(StateFn {
722            f: unmarshal_timing,
723        })),
724        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
725    }
726}
727
728fn s8<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
729    let (key, _) = read_type(lexer.reader)?;
730    match key.as_slice() {
731        b"c=" => Ok(Some(StateFn {
732            f: unmarshal_session_connection_information,
733        })),
734        b"b=" => Ok(Some(StateFn {
735            f: unmarshal_session_bandwidth,
736        })),
737        b"t=" => Ok(Some(StateFn {
738            f: unmarshal_timing,
739        })),
740        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
741    }
742}
743
744fn s9<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
745    let (key, num_bytes) = read_type(lexer.reader)?;
746    if key.is_empty() && num_bytes == 0 {
747        return Ok(None);
748    }
749
750    match key.as_slice() {
751        b"z=" => Ok(Some(StateFn {
752            f: unmarshal_time_zones,
753        })),
754        b"k=" => Ok(Some(StateFn {
755            f: unmarshal_session_encryption_key,
756        })),
757        b"a=" => Ok(Some(StateFn {
758            f: unmarshal_session_attribute,
759        })),
760        b"r=" => Ok(Some(StateFn {
761            f: unmarshal_repeat_times,
762        })),
763        b"t=" => Ok(Some(StateFn {
764            f: unmarshal_timing,
765        })),
766        b"m=" => Ok(Some(StateFn {
767            f: unmarshal_media_description,
768        })),
769        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
770    }
771}
772
773fn s10<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
774    let (key, _) = read_type(lexer.reader)?;
775    match key.as_slice() {
776        b"e=" => Ok(Some(StateFn { f: unmarshal_email })),
777        b"p=" => Ok(Some(StateFn { f: unmarshal_phone })),
778        b"c=" => Ok(Some(StateFn {
779            f: unmarshal_session_connection_information,
780        })),
781        b"b=" => Ok(Some(StateFn {
782            f: unmarshal_session_bandwidth,
783        })),
784        b"t=" => Ok(Some(StateFn {
785            f: unmarshal_timing,
786        })),
787        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
788    }
789}
790
791fn s11<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
792    let (key, num_bytes) = read_type(lexer.reader)?;
793    if key.is_empty() && num_bytes == 0 {
794        return Ok(None);
795    }
796
797    match key.as_slice() {
798        b"a=" => Ok(Some(StateFn {
799            f: unmarshal_session_attribute,
800        })),
801        b"m=" => Ok(Some(StateFn {
802            f: unmarshal_media_description,
803        })),
804        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
805    }
806}
807
808fn s12<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
809    let (key, num_bytes) = read_type(lexer.reader)?;
810    if key.is_empty() && num_bytes == 0 {
811        return Ok(None);
812    }
813
814    match key.as_slice() {
815        b"a=" => Ok(Some(StateFn {
816            f: unmarshal_media_attribute,
817        })),
818        b"k=" => Ok(Some(StateFn {
819            f: unmarshal_media_encryption_key,
820        })),
821        b"b=" => Ok(Some(StateFn {
822            f: unmarshal_media_bandwidth,
823        })),
824        b"c=" => Ok(Some(StateFn {
825            f: unmarshal_media_connection_information,
826        })),
827        b"i=" => Ok(Some(StateFn {
828            f: unmarshal_media_title,
829        })),
830        b"m=" => Ok(Some(StateFn {
831            f: unmarshal_media_description,
832        })),
833        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
834    }
835}
836
837fn s13<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
838    let (key, num_bytes) = read_type(lexer.reader)?;
839    if key.is_empty() && num_bytes == 0 {
840        return Ok(None);
841    }
842
843    match key.as_slice() {
844        b"a=" => Ok(Some(StateFn {
845            f: unmarshal_session_attribute,
846        })),
847        b"k=" => Ok(Some(StateFn {
848            f: unmarshal_session_encryption_key,
849        })),
850        b"m=" => Ok(Some(StateFn {
851            f: unmarshal_media_description,
852        })),
853        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
854    }
855}
856
857fn s14<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
858    let (key, num_bytes) = read_type(lexer.reader)?;
859    if key.is_empty() && num_bytes == 0 {
860        return Ok(None);
861    }
862
863    match key.as_slice() {
864        b"a=" => Ok(Some(StateFn {
865            f: unmarshal_media_attribute,
866        })),
867        // Non-spec ordering
868        b"k=" => Ok(Some(StateFn {
869            f: unmarshal_media_encryption_key,
870        })),
871        // Non-spec ordering
872        b"b=" => Ok(Some(StateFn {
873            f: unmarshal_media_bandwidth,
874        })),
875        // Non-spec ordering
876        b"c=" => Ok(Some(StateFn {
877            f: unmarshal_media_connection_information,
878        })),
879        // Non-spec ordering
880        b"i=" => Ok(Some(StateFn {
881            f: unmarshal_media_title,
882        })),
883        b"m=" => Ok(Some(StateFn {
884            f: unmarshal_media_description,
885        })),
886        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
887    }
888}
889
890fn s15<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
891    let (key, num_bytes) = read_type(lexer.reader)?;
892    if key.is_empty() && num_bytes == 0 {
893        return Ok(None);
894    }
895
896    match key.as_slice() {
897        b"a=" => Ok(Some(StateFn {
898            f: unmarshal_media_attribute,
899        })),
900        b"k=" => Ok(Some(StateFn {
901            f: unmarshal_media_encryption_key,
902        })),
903        b"b=" => Ok(Some(StateFn {
904            f: unmarshal_media_bandwidth,
905        })),
906        b"c=" => Ok(Some(StateFn {
907            f: unmarshal_media_connection_information,
908        })),
909        // Non-spec ordering
910        b"i=" => Ok(Some(StateFn {
911            f: unmarshal_media_title,
912        })),
913        b"m=" => Ok(Some(StateFn {
914            f: unmarshal_media_description,
915        })),
916        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
917    }
918}
919
920fn s16<'a, R: io::BufRead + io::Seek>(lexer: &mut Lexer<'a, R>) -> Result<Option<StateFn<'a, R>>> {
921    let (key, num_bytes) = read_type(lexer.reader)?;
922    if key.is_empty() && num_bytes == 0 {
923        return Ok(None);
924    }
925
926    match key.as_slice() {
927        b"a=" => Ok(Some(StateFn {
928            f: unmarshal_media_attribute,
929        })),
930        b"k=" => Ok(Some(StateFn {
931            f: unmarshal_media_encryption_key,
932        })),
933        b"c=" => Ok(Some(StateFn {
934            f: unmarshal_media_connection_information,
935        })),
936        b"b=" => Ok(Some(StateFn {
937            f: unmarshal_media_bandwidth,
938        })),
939        // Non-spec ordering
940        b"i=" => Ok(Some(StateFn {
941            f: unmarshal_media_title,
942        })),
943        b"m=" => Ok(Some(StateFn {
944            f: unmarshal_media_description,
945        })),
946        _ => Err(Error::SdpInvalidSyntax(String::from_utf8(key)?)),
947    }
948}
949
950fn unmarshal_protocol_version<'a, R: io::BufRead + io::Seek>(
951    lexer: &mut Lexer<'a, R>,
952) -> Result<Option<StateFn<'a, R>>> {
953    let (value, _) = read_value(lexer.reader)?;
954
955    let version = value.parse::<u32>()?;
956
957    // As off the latest draft of the rfc this value is required to be 0.
958    // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-5.8.1
959    if version != 0 {
960        return Err(Error::SdpInvalidSyntax(value));
961    }
962
963    Ok(Some(StateFn { f: s2 }))
964}
965
966fn unmarshal_origin<'a, R: io::BufRead + io::Seek>(
967    lexer: &mut Lexer<'a, R>,
968) -> Result<Option<StateFn<'a, R>>> {
969    let (value, _) = read_value(lexer.reader)?;
970
971    let fields: Vec<&str> = value.split_whitespace().collect();
972    if fields.len() != 6 {
973        return Err(Error::SdpInvalidSyntax(format!("`o={value}`")));
974    }
975
976    let session_id = fields[1].parse::<u64>()?;
977    let session_version = fields[2].parse::<u64>()?;
978
979    // Set according to currently registered with IANA
980    // https://tools.ietf.org/html/rfc4566#section-8.2.6
981    let i = index_of(fields[3], &["IN"]);
982    if i == -1 {
983        return Err(Error::SdpInvalidValue(fields[3].to_owned()));
984    }
985
986    // Set according to currently registered with IANA
987    // https://tools.ietf.org/html/rfc4566#section-8.2.7
988    let i = index_of(fields[4], &["IP4", "IP6"]);
989    if i == -1 {
990        return Err(Error::SdpInvalidValue(fields[4].to_owned()));
991    }
992
993    // TODO validated UnicastAddress
994
995    lexer.desc.origin = Origin {
996        username: fields[0].to_owned(),
997        session_id,
998        session_version,
999        network_type: fields[3].to_owned(),
1000        address_type: fields[4].to_owned(),
1001        unicast_address: fields[5].to_owned(),
1002    };
1003
1004    Ok(Some(StateFn { f: s3 }))
1005}
1006
1007fn unmarshal_session_name<'a, R: io::BufRead + io::Seek>(
1008    lexer: &mut Lexer<'a, R>,
1009) -> Result<Option<StateFn<'a, R>>> {
1010    let (value, _) = read_value(lexer.reader)?;
1011    lexer.desc.session_name = value;
1012    Ok(Some(StateFn { f: s4 }))
1013}
1014
1015fn unmarshal_session_information<'a, R: io::BufRead + io::Seek>(
1016    lexer: &mut Lexer<'a, R>,
1017) -> Result<Option<StateFn<'a, R>>> {
1018    let (value, _) = read_value(lexer.reader)?;
1019    lexer.desc.session_information = Some(value);
1020    Ok(Some(StateFn { f: s7 }))
1021}
1022
1023fn unmarshal_uri<'a, R: io::BufRead + io::Seek>(
1024    lexer: &mut Lexer<'a, R>,
1025) -> Result<Option<StateFn<'a, R>>> {
1026    let (value, _) = read_value(lexer.reader)?;
1027    lexer.desc.uri = Some(Url::parse(&value)?);
1028    Ok(Some(StateFn { f: s10 }))
1029}
1030
1031fn unmarshal_email<'a, R: io::BufRead + io::Seek>(
1032    lexer: &mut Lexer<'a, R>,
1033) -> Result<Option<StateFn<'a, R>>> {
1034    let (value, _) = read_value(lexer.reader)?;
1035    lexer.desc.email_address = Some(value);
1036    Ok(Some(StateFn { f: s6 }))
1037}
1038
1039fn unmarshal_phone<'a, R: io::BufRead + io::Seek>(
1040    lexer: &mut Lexer<'a, R>,
1041) -> Result<Option<StateFn<'a, R>>> {
1042    let (value, _) = read_value(lexer.reader)?;
1043    lexer.desc.phone_number = Some(value);
1044    Ok(Some(StateFn { f: s8 }))
1045}
1046
1047fn unmarshal_session_connection_information<'a, R: io::BufRead + io::Seek>(
1048    lexer: &mut Lexer<'a, R>,
1049) -> Result<Option<StateFn<'a, R>>> {
1050    let (value, _) = read_value(lexer.reader)?;
1051    lexer.desc.connection_information = unmarshal_connection_information(&value)?;
1052    Ok(Some(StateFn { f: s5 }))
1053}
1054
1055fn unmarshal_connection_information(value: &str) -> Result<Option<ConnectionInformation>> {
1056    let fields: Vec<&str> = value.split_whitespace().collect();
1057    if fields.len() < 2 {
1058        return Err(Error::SdpInvalidSyntax(format!("`c={value}`")));
1059    }
1060
1061    // Set according to currently registered with IANA
1062    // https://tools.ietf.org/html/rfc4566#section-8.2.6
1063    let i = index_of(fields[0], &["IN"]);
1064    if i == -1 {
1065        return Err(Error::SdpInvalidValue(fields[0].to_owned()));
1066    }
1067
1068    // Set according to currently registered with IANA
1069    // https://tools.ietf.org/html/rfc4566#section-8.2.7
1070    let i = index_of(fields[1], &["IP4", "IP6"]);
1071    if i == -1 {
1072        return Err(Error::SdpInvalidValue(fields[1].to_owned()));
1073    }
1074
1075    let address = if fields.len() > 2 {
1076        Some(Address {
1077            address: fields[2].to_owned(),
1078            ttl: None,
1079            range: None,
1080        })
1081    } else {
1082        None
1083    };
1084
1085    Ok(Some(ConnectionInformation {
1086        network_type: fields[0].to_owned(),
1087        address_type: fields[1].to_owned(),
1088        address,
1089    }))
1090}
1091
1092fn unmarshal_session_bandwidth<'a, R: io::BufRead + io::Seek>(
1093    lexer: &mut Lexer<'a, R>,
1094) -> Result<Option<StateFn<'a, R>>> {
1095    let (value, _) = read_value(lexer.reader)?;
1096    lexer.desc.bandwidth.push(unmarshal_bandwidth(&value)?);
1097    Ok(Some(StateFn { f: s5 }))
1098}
1099
1100fn unmarshal_bandwidth(value: &str) -> Result<Bandwidth> {
1101    let mut parts: Vec<&str> = value.split(':').collect();
1102    if parts.len() != 2 {
1103        return Err(Error::SdpInvalidSyntax(format!("`b={value}`")));
1104    }
1105
1106    let experimental = parts[0].starts_with("X-");
1107    if experimental {
1108        parts[0] = parts[0].trim_start_matches("X-");
1109    }
1110    // RFC 8866 section 5.8: SDP parsers MUST ignore bandwidth-fields with unknown <bwtype> names.
1111    // Accept any bandwidth type instead of validating against a specific list.
1112
1113    let bandwidth = parts[1].parse::<u64>()?;
1114
1115    Ok(Bandwidth {
1116        experimental,
1117        bandwidth_type: parts[0].to_owned(),
1118        bandwidth,
1119    })
1120}
1121
1122fn unmarshal_timing<'a, R: io::BufRead + io::Seek>(
1123    lexer: &mut Lexer<'a, R>,
1124) -> Result<Option<StateFn<'a, R>>> {
1125    let (value, _) = read_value(lexer.reader)?;
1126
1127    let fields: Vec<&str> = value.split_whitespace().collect();
1128    if fields.len() < 2 {
1129        return Err(Error::SdpInvalidSyntax(format!("`t={value}`")));
1130    }
1131
1132    let start_time = fields[0].parse::<u64>()?;
1133    let stop_time = fields[1].parse::<u64>()?;
1134
1135    lexer.desc.time_descriptions.push(TimeDescription {
1136        timing: Timing {
1137            start_time,
1138            stop_time,
1139        },
1140        repeat_times: vec![],
1141    });
1142
1143    Ok(Some(StateFn { f: s9 }))
1144}
1145
1146fn unmarshal_repeat_times<'a, R: io::BufRead + io::Seek>(
1147    lexer: &mut Lexer<'a, R>,
1148) -> Result<Option<StateFn<'a, R>>> {
1149    let (value, _) = read_value(lexer.reader)?;
1150
1151    let fields: Vec<&str> = value.split_whitespace().collect();
1152    if fields.len() < 3 {
1153        return Err(Error::SdpInvalidSyntax(format!("`r={value}`")));
1154    }
1155
1156    if let Some(latest_time_desc) = lexer.desc.time_descriptions.last_mut() {
1157        let interval = parse_time_units(fields[0])?;
1158        let duration = parse_time_units(fields[1])?;
1159        let mut offsets = vec![];
1160        for field in fields.iter().skip(2) {
1161            let offset = parse_time_units(field)?;
1162            offsets.push(offset);
1163        }
1164        latest_time_desc.repeat_times.push(RepeatTime {
1165            interval,
1166            duration,
1167            offsets,
1168        });
1169
1170        Ok(Some(StateFn { f: s9 }))
1171    } else {
1172        Err(Error::SdpEmptyTimeDescription)
1173    }
1174}
1175
1176fn unmarshal_time_zones<'a, R: io::BufRead + io::Seek>(
1177    lexer: &mut Lexer<'a, R>,
1178) -> Result<Option<StateFn<'a, R>>> {
1179    let (value, _) = read_value(lexer.reader)?;
1180
1181    // These fields are transimitted in pairs
1182    // z=<adjustment time> <offset> <adjustment time> <offset> ....
1183    // so we are making sure that there are actually multiple of 2 total.
1184    let fields: Vec<&str> = value.split_whitespace().collect();
1185    if !fields.len().is_multiple_of(2) {
1186        return Err(Error::SdpInvalidSyntax(format!("`t={value}`")));
1187    }
1188
1189    for i in (0..fields.len()).step_by(2) {
1190        let adjustment_time = fields[i].parse::<u64>()?;
1191        let offset = parse_time_units(fields[i + 1])?;
1192
1193        lexer.desc.time_zones.push(TimeZone {
1194            adjustment_time,
1195            offset,
1196        });
1197    }
1198
1199    Ok(Some(StateFn { f: s13 }))
1200}
1201
1202fn unmarshal_session_encryption_key<'a, R: io::BufRead + io::Seek>(
1203    lexer: &mut Lexer<'a, R>,
1204) -> Result<Option<StateFn<'a, R>>> {
1205    let (value, _) = read_value(lexer.reader)?;
1206    lexer.desc.encryption_key = Some(value);
1207    Ok(Some(StateFn { f: s11 }))
1208}
1209
1210fn unmarshal_session_attribute<'a, R: io::BufRead + io::Seek>(
1211    lexer: &mut Lexer<'a, R>,
1212) -> Result<Option<StateFn<'a, R>>> {
1213    let (value, _) = read_value(lexer.reader)?;
1214
1215    let fields: Vec<&str> = value.splitn(2, ':').collect();
1216    let attribute = if fields.len() == 2 {
1217        Attribute {
1218            key: fields[0].to_owned(),
1219            value: Some(fields[1].to_owned()),
1220        }
1221    } else {
1222        Attribute {
1223            key: fields[0].to_owned(),
1224            value: None,
1225        }
1226    };
1227    lexer.desc.attributes.push(attribute);
1228
1229    Ok(Some(StateFn { f: s11 }))
1230}
1231
1232fn unmarshal_media_description<'a, R: io::BufRead + io::Seek>(
1233    lexer: &mut Lexer<'a, R>,
1234) -> Result<Option<StateFn<'a, R>>> {
1235    let (value, _) = read_value(lexer.reader)?;
1236
1237    let fields: Vec<&str> = value.split_whitespace().collect();
1238    if fields.len() < 4 {
1239        return Err(Error::SdpInvalidSyntax(format!("`m={value}`")));
1240    }
1241
1242    // <media>
1243    // Set according to currently registered with IANA
1244    // https://tools.ietf.org/html/rfc4566#section-5.14
1245    // including "image", registered here:
1246    // https://datatracker.ietf.org/doc/html/rfc6466
1247    let i = index_of(
1248        fields[0],
1249        &["audio", "video", "text", "application", "message", "image"],
1250    );
1251    if i == -1 {
1252        return Err(Error::SdpInvalidValue(fields[0].to_owned()));
1253    }
1254
1255    // <port>
1256    let parts: Vec<&str> = fields[1].split('/').collect();
1257    let port_value = parts[0].parse::<u16>()? as isize;
1258    let port_range = if parts.len() > 1 {
1259        Some(parts[1].parse::<i32>()? as isize)
1260    } else {
1261        None
1262    };
1263
1264    // <proto>
1265    // RFC 8866 section 5.14: <proto> is the media transport protocol and is
1266    // extensible -- "New transport protocols MAY be defined, and MUST be
1267    // registered with IANA." Accept any "/"-separated proto token instead of
1268    // validating against a fixed list, so IANA-registered transports beyond the
1269    // RTP/SAVPF set interoperate, e.g. UDP/BFCP (RFC 8856), TCP/DTLS/SCTP
1270    // (RFC 8841) or TCP/MRCPv2. A genuinely malformed proto (an empty
1271    // "/"-separated token) is still rejected as SdpInvalidValue.
1272    let mut protos = vec![];
1273    for proto in fields[2].split('/').collect::<Vec<&str>>() {
1274        if proto.is_empty() {
1275            return Err(Error::SdpInvalidValue(fields[2].to_owned()));
1276        }
1277        protos.push(proto.to_owned());
1278    }
1279
1280    // <fmt>...
1281    let mut formats = vec![];
1282    for field in fields.iter().skip(3) {
1283        formats.push(field.to_string());
1284    }
1285
1286    lexer.desc.media_descriptions.push(MediaDescription {
1287        media_name: MediaName {
1288            media: fields[0].to_owned(),
1289            port: RangedPort {
1290                value: port_value,
1291                range: port_range,
1292            },
1293            protos,
1294            formats,
1295        },
1296        media_title: None,
1297        connection_information: None,
1298        bandwidth: vec![],
1299        encryption_key: None,
1300        attributes: vec![],
1301    });
1302
1303    Ok(Some(StateFn { f: s12 }))
1304}
1305
1306fn unmarshal_media_title<'a, R: io::BufRead + io::Seek>(
1307    lexer: &mut Lexer<'a, R>,
1308) -> Result<Option<StateFn<'a, R>>> {
1309    let (value, _) = read_value(lexer.reader)?;
1310
1311    if let Some(latest_media_desc) = lexer.desc.media_descriptions.last_mut() {
1312        latest_media_desc.media_title = Some(value);
1313        Ok(Some(StateFn { f: s16 }))
1314    } else {
1315        Err(Error::SdpEmptyTimeDescription)
1316    }
1317}
1318
1319fn unmarshal_media_connection_information<'a, R: io::BufRead + io::Seek>(
1320    lexer: &mut Lexer<'a, R>,
1321) -> Result<Option<StateFn<'a, R>>> {
1322    let (value, _) = read_value(lexer.reader)?;
1323
1324    if let Some(latest_media_desc) = lexer.desc.media_descriptions.last_mut() {
1325        latest_media_desc.connection_information = unmarshal_connection_information(&value)?;
1326        Ok(Some(StateFn { f: s15 }))
1327    } else {
1328        Err(Error::SdpEmptyTimeDescription)
1329    }
1330}
1331
1332fn unmarshal_media_bandwidth<'a, R: io::BufRead + io::Seek>(
1333    lexer: &mut Lexer<'a, R>,
1334) -> Result<Option<StateFn<'a, R>>> {
1335    let (value, _) = read_value(lexer.reader)?;
1336
1337    if let Some(latest_media_desc) = lexer.desc.media_descriptions.last_mut() {
1338        let bandwidth = unmarshal_bandwidth(&value)?;
1339        latest_media_desc.bandwidth.push(bandwidth);
1340        Ok(Some(StateFn { f: s15 }))
1341    } else {
1342        Err(Error::SdpEmptyTimeDescription)
1343    }
1344}
1345
1346fn unmarshal_media_encryption_key<'a, R: io::BufRead + io::Seek>(
1347    lexer: &mut Lexer<'a, R>,
1348) -> Result<Option<StateFn<'a, R>>> {
1349    let (value, _) = read_value(lexer.reader)?;
1350
1351    if let Some(latest_media_desc) = lexer.desc.media_descriptions.last_mut() {
1352        latest_media_desc.encryption_key = Some(value);
1353        Ok(Some(StateFn { f: s14 }))
1354    } else {
1355        Err(Error::SdpEmptyTimeDescription)
1356    }
1357}
1358
1359fn unmarshal_media_attribute<'a, R: io::BufRead + io::Seek>(
1360    lexer: &mut Lexer<'a, R>,
1361) -> Result<Option<StateFn<'a, R>>> {
1362    let (value, _) = read_value(lexer.reader)?;
1363
1364    let fields: Vec<&str> = value.splitn(2, ':').collect();
1365    let attribute = if fields.len() == 2 {
1366        Attribute {
1367            key: fields[0].to_owned(),
1368            value: Some(fields[1].to_owned()),
1369        }
1370    } else {
1371        Attribute {
1372            key: fields[0].to_owned(),
1373            value: None,
1374        }
1375    };
1376
1377    if let Some(latest_media_desc) = lexer.desc.media_descriptions.last_mut() {
1378        latest_media_desc.attributes.push(attribute);
1379        Ok(Some(StateFn { f: s14 }))
1380    } else {
1381        Err(Error::SdpEmptyTimeDescription)
1382    }
1383}
1384
1385fn parse_time_units(value: &str) -> Result<i64> {
1386    // Some time offsets in the protocol can be provided with a shorthand
1387    // notation. This code ensures to convert it to NTP timestamp format.
1388    let val = value.as_bytes();
1389    let len = val.len();
1390    let (num, factor) = match val.last() {
1391        Some(b'd') => (&value[..len - 1], 86400), // days
1392        Some(b'h') => (&value[..len - 1], 3600),  // hours
1393        Some(b'm') => (&value[..len - 1], 60),    // minutes
1394        Some(b's') => (&value[..len - 1], 1),     // seconds (allowed for completeness)
1395        _ => (value, 1),
1396    };
1397    num.parse::<i64>()?
1398        .checked_mul(factor)
1399        .ok_or_else(|| Error::SdpInvalidValue(value.to_owned()))
1400}