Skip to main content

sdp_types/
parser.rs

1// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5use super::*;
6
7/// Parsing errors that can be returned from [`Session::parse`].
8///
9/// [`Session::parse`]: struct.Session.html#method.parse
10#[derive(Debug, PartialEq, Eq)]
11pub enum ParserError {
12    /// The given line started with an unexpected character.
13    #[deprecated(note = "This is no longer considered an error.")]
14    UnexpectedLine(usize, u8),
15    /// The given line was not formatted correctly.
16    InvalidLineFormat(usize, &'static str),
17    /// The given line contained an invalid encoding at the specified field.
18    InvalidFieldEncoding(usize, &'static str),
19    /// The given line contained an invalid format at the specified field.
20    InvalidFieldFormat(usize, &'static str),
21    /// The given line was missing field data at the specified field.
22    MissingField(usize, &'static str),
23    /// The given line had some trailing data at the specified field.
24    FieldTrailingData(usize, &'static str),
25    /// The given version line did not contain a valid version number.
26    InvalidVersion(usize, Vec<u8>),
27    /// A second version line was found at the given line.
28    MultipleVersions(usize),
29    /// The SDP did not contain a version line.
30    NoVersion,
31    /// A second origin line was found at the given line.
32    MultipleOrigins(usize),
33    /// The SDP did not contain an origin line.
34    #[deprecated(note = "This is no longer considered an error.")]
35    NoOrigin,
36    /// A second session name line was found at the given line.
37    MultipleSessionNames(usize),
38    /// The SDP did not contain a session name line.
39    #[deprecated(note = "This is no longer considered an error.")]
40    NoSessionName,
41    /// A second session description line was found at the given line.
42    MultipleSessionDescription(usize),
43    /// A second URI line was found at the given line.
44    MultipleUris(usize),
45    /// A second connection line was found at the given line.
46    MultipleConnections(usize),
47    /// A second time zone line was found at the given line.
48    MultipleTimeZones(usize),
49    /// A second media title line was found at the given line.
50    MultipleMediaTitles(usize),
51    /// A second key line was found at the given line.
52    MultipleKeys(usize),
53    /// A repeat line was found without a preceding time line.
54    RepeatWithoutTime(usize),
55}
56
57impl std::error::Error for ParserError {}
58
59impl std::fmt::Display for ParserError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
61        use std::convert::TryFrom;
62
63        match *self {
64            #[allow(deprecated)]
65            ParserError::UnexpectedLine(line, c) => {
66                if let Ok(c) = char::try_from(c as u32) {
67                    write!(f, "Unexpected line {line} starting with '{c}'")
68                } else {
69                    write!(f, "Unexpected line {line} starting with U+{c:04x}")
70                }
71            }
72            ParserError::InvalidLineFormat(line, ref s) => {
73                write!(f, "Invalid formatted line {line}: \"{s}\"")
74            }
75            ParserError::InvalidFieldEncoding(line, s) => {
76                write!(f, "Invalid field encoding in line {line} at {s}")
77            }
78            ParserError::InvalidFieldFormat(line, s) => {
79                write!(f, "Invalid field formatting in line {line} at {s}")
80            }
81            ParserError::MissingField(line, s) => write!(f, "Missing field {s} in line {line}"),
82            ParserError::FieldTrailingData(line, s) => {
83                write!(f, "Field {s} in line {line} has trailing data")
84            }
85            ParserError::InvalidVersion(line, ref s) => write!(
86                f,
87                "Invalid version at line {}: {}",
88                line,
89                String::from_utf8_lossy(s)
90            ),
91            ParserError::MultipleVersions(line) => write!(f, "Multiple versions in line {line}"),
92            ParserError::NoVersion => write!(f, "No version line"),
93            ParserError::MultipleOrigins(line) => write!(f, "Multiple origins in line {line}"),
94            #[allow(deprecated)]
95            ParserError::NoOrigin => write!(f, "No origin line"),
96            ParserError::MultipleSessionNames(line) => {
97                write!(f, "Multiple session-names in line {line}")
98            }
99            #[allow(deprecated)]
100            ParserError::NoSessionName => write!(f, "No session-name line"),
101            ParserError::MultipleSessionDescription(line) => {
102                write!(f, "Multiple session-information in line {line}")
103            }
104            ParserError::MultipleUris(line) => write!(f, "Multiple URIs in line {line}"),
105            ParserError::MultipleConnections(line) => {
106                write!(f, "Multiple connections in line {line}")
107            }
108            ParserError::MultipleTimeZones(line) => write!(f, "Multiple zones in line {line}"),
109            ParserError::MultipleMediaTitles(line) => {
110                write!(f, "Multiple media titles in line {line}")
111            }
112            ParserError::MultipleKeys(line) => write!(f, "Multiple keys in line {line}"),
113            ParserError::RepeatWithoutTime(line) => {
114                write!(f, "Repeat line {line} without a preceding time line")
115            }
116        }
117    }
118}
119
120impl Origin {
121    fn parse(line: &Line) -> Result<Origin, ParserError> {
122        // <username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
123        //
124        // None of the fields may be empty, so treat runs of spaces as a
125        // single separator.
126        let mut origin = line.value.split_str(b" ").filter(|part| !part.is_empty());
127        let username = parse_str(&mut origin, line.n, "Origin username")?;
128        let sess_id = parse_str(&mut origin, line.n, "Origin sess-id")?;
129        let sess_version = parse_str_u64(&mut origin, line.n, "Origin sess-version")?;
130        let nettype = parse_token(&mut origin, line.n, "Origin nettype")?;
131        let addrtype = parse_token(&mut origin, line.n, "Origin addrtype")?;
132        let unicast_address = parse_str(&mut origin, line.n, "Origin unicast-address")?;
133        if origin.next().is_some() {
134            return Err(ParserError::FieldTrailingData(line.n, "Origin"));
135        }
136
137        Ok(Origin {
138            username: if username == "-" {
139                None
140            } else {
141                Some(username)
142            },
143            sess_id,
144            sess_version,
145            nettype: NetType::from_str(&nettype).expect("infallible"),
146            addrtype: AddrType::from_str(&addrtype).expect("infallible"),
147            unicast_address,
148        })
149    }
150}
151
152impl Connection {
153    fn parse(line: &Line) -> Result<Connection, ParserError> {
154        // <nettype> <addrtype> <connection-address>
155        //
156        // None of the fields may be empty, so treat runs of spaces as a
157        // single separator.
158        let mut connection = line.value.split_str(b" ").filter(|part| !part.is_empty());
159        let nettype = parse_token(&mut connection, line.n, "Connection nettype")?;
160        let addrtype = parse_token(&mut connection, line.n, "Connection addrtype")?;
161        let connection_address =
162            parse_str(&mut connection, line.n, "Connection connection-address")?;
163        if connection.next().is_some() {
164            return Err(ParserError::FieldTrailingData(line.n, "Connection"));
165        }
166
167        Ok(Connection {
168            nettype: NetType::from_str(&nettype).expect("infallible"),
169            addrtype: AddrType::from_str(&addrtype).expect("infallible"),
170            connection_address,
171        })
172    }
173}
174
175impl Bandwidth {
176    fn parse(line: &Line) -> Result<Bandwidth, ParserError> {
177        // <bwtype>:<bandwidth>
178        let mut bandwidth = line.value.split_str(b":");
179        let bwtype = parse_str(&mut bandwidth, line.n, "Bandwidth bwtype")?;
180        let bw = parse_str_u64(&mut bandwidth, line.n, "Bandwidth bandwidth")?;
181        if bandwidth.next().is_some() {
182            return Err(ParserError::FieldTrailingData(line.n, "Bandwidth"));
183        }
184
185        Ok(Bandwidth {
186            bwtype,
187            bandwidth: bw,
188        })
189    }
190}
191
192impl Time {
193    fn parse(line: &Line) -> Result<Time, ParserError> {
194        // <start-time> <stop-time>
195        let mut time = line.value.split_str(b" ").filter(|part| !part.is_empty());
196        let start_time = parse_str_u64(&mut time, line.n, "Time start-time")?;
197        let stop_time = parse_str_u64(&mut time, line.n, "Time stop-time")?;
198        if time.next().is_some() {
199            return Err(ParserError::FieldTrailingData(line.n, "Time"));
200        }
201
202        Ok(Time {
203            start_time,
204            stop_time,
205            repeats: Vec::new(),
206        })
207    }
208}
209
210fn parse_typed_time(s: &[u8], line: usize, field: &'static str) -> Result<u64, ParserError> {
211    let (num, factor) = match s
212        .split_last()
213        .ok_or(ParserError::InvalidFieldFormat(line, field))?
214    {
215        (b'd', prefix) => (prefix, 86_400),
216        (b'h', prefix) => (prefix, 3_600),
217        (b'm', prefix) => (prefix, 60),
218        (b's', prefix) => (prefix, 1),
219        (_, _) => (s, 1),
220    };
221
222    let num =
223        std::str::from_utf8(num).map_err(|_| ParserError::InvalidFieldEncoding(line, field))?;
224    let num = num
225        .parse::<u64>()
226        .map_err(|_| ParserError::InvalidFieldFormat(line, field))?;
227    num.checked_mul(factor)
228        .ok_or(ParserError::InvalidFieldFormat(line, field))
229}
230
231impl Repeat {
232    fn parse(line: &Line) -> Result<Repeat, ParserError> {
233        // <repeat interval> <active duration> <offsets from start-time>
234        let mut repeat = line.value.split_str(b" ").filter(|part| !part.is_empty());
235        let repeat_interval = repeat
236            .next()
237            .ok_or(ParserError::MissingField(line.n, "Repeat repeat-interval"))
238            .and_then(|s| parse_typed_time(s, line.n, "Repeat repeat-interval"))?;
239        let active_duration = repeat
240            .next()
241            .ok_or(ParserError::MissingField(line.n, "Repeat active-duration"))
242            .and_then(|s| parse_typed_time(s, line.n, "Repeat active-duration"))?;
243
244        let offsets = repeat
245            .map(|s| parse_typed_time(s, line.n, "Repeat offset"))
246            .collect::<Result<Vec<_>, _>>()?;
247
248        Ok(Repeat {
249            repeat_interval,
250            active_duration,
251            offsets,
252        })
253    }
254}
255
256impl TimeZone {
257    fn parse(line: &Line) -> Result<Vec<TimeZone>, ParserError> {
258        // <adjustment time> <offset> <adjustment time> <offset> ....
259        let mut zones = line.value.split_str(b" ").filter(|part| !part.is_empty());
260
261        let mut ret = Vec::new();
262        loop {
263            let adjustment_time = parse_str_u64(&mut zones, line.n, "TimeZone adjustment-time");
264
265            let adjustment_time = match adjustment_time {
266                Ok(adjustment_time) => adjustment_time,
267                Err(ParserError::MissingField(..)) => break,
268                Err(err) => return Err(err),
269            };
270
271            let offset = zones
272                .next()
273                .ok_or(ParserError::MissingField(line.n, "TimeZone offset"))
274                .and_then(|s| {
275                    use std::convert::TryInto;
276
277                    let (sign, s) = if s.first() == Some(&b'-') {
278                        (true, &s[1..])
279                    } else {
280                        (false, s)
281                    };
282
283                    parse_typed_time(s, line.n, "TimeZone offset").and_then(|t| {
284                        // The magnitude of i64::MIN does not fit into i64,
285                        // so combine sign and magnitude via i128 to allow
286                        // a successful round trip check.
287                        let offset = if sign { -(t as i128) } else { t as i128 };
288                        let offset: i64 = offset.try_into().map_err(|_| {
289                            ParserError::InvalidFieldFormat(line.n, "TimeZone offset")
290                        })?;
291                        Ok(offset)
292                    })
293                })?;
294
295            ret.push(TimeZone {
296                adjustment_time,
297                offset,
298            });
299        }
300
301        Ok(ret)
302    }
303}
304
305impl Attribute {
306    fn parse(line: &Line) -> Result<Attribute, ParserError> {
307        // <attribute>:<value>
308        // <attribute>
309        let mut attribute = line.value.splitn_str(2, b":");
310        let name = parse_str(&mut attribute, line.n, "Attribute name")?;
311        let value = parse_str_opt(&mut attribute, line.n, "Attribute value")?;
312
313        Ok(Attribute {
314            attribute: name,
315            value,
316        })
317    }
318}
319
320impl Key {
321    fn parse(line: &Line) -> Result<Key, ParserError> {
322        // <method>:<encryption key>
323        // <method>
324        let mut key = line.value.splitn_str(2, b":");
325        let method = parse_str(&mut key, line.n, "Key method")?;
326        let encryption_key = parse_str_opt(&mut key, line.n, "Key encryption-key")?;
327
328        Ok(Key {
329            method,
330            encryption_key,
331        })
332    }
333}
334
335impl Media {
336    fn parse_m_line(line: &Line) -> Result<Media, ParserError> {
337        // <media> <port> <proto> <fmt> ...
338        let mut media = line.value.splitn_str(4, b" ");
339        let name = parse_str(&mut media, line.n, "Media name")?;
340
341        let (port, num_ports) = media
342            .next()
343            .ok_or(ParserError::MissingField(line.n, "Media port"))
344            .and_then(|s| str_from_utf8(line.n, s, "Media Port"))
345            .and_then(|port| {
346                let mut split = port.splitn(2, '/');
347                let port = split
348                    .next()
349                    .ok_or(ParserError::MissingField(line.n, "Media port"))
350                    .and_then(|port| {
351                        port.parse()
352                            .map_err(|_| ParserError::InvalidFieldFormat(line.n, "Media port"))
353                    })?;
354
355                let num_ports = split
356                    .next()
357                    .ok_or(ParserError::MissingField(line.n, "Media num-ports"))
358                    .and_then(|num_ports| {
359                        num_ports
360                            .parse()
361                            .map_err(|_| ParserError::InvalidFieldFormat(line.n, "Media num-ports"))
362                    });
363
364                match num_ports {
365                    Ok(num_ports) => Ok((port, Some(num_ports))),
366                    Err(ParserError::MissingField(..)) => Ok((port, None)),
367                    Err(err) => Err(err),
368                }
369            })?;
370
371        let proto = parse_str(&mut media, line.n, "Media proto")?;
372        let fmt = parse_str(&mut media, line.n, "Media fmt")?;
373
374        Ok(Media {
375            media: name,
376            port,
377            num_ports,
378            proto,
379            fmt,
380            media_title: None,
381            connections: Vec::new(),
382            bandwidths: Vec::new(),
383            key: None,
384            attributes: Vec::new(),
385        })
386    }
387
388    fn parse<'a, I: FallibleIterator<Item = Line<'a>, Error = ParserError>>(
389        lines: &mut fallible_iterator::Peekable<I>,
390    ) -> Result<Option<Media>, ParserError> {
391        let media = match lines.next()? {
392            None => return Ok(None),
393            Some(line) => match line.key {
394                b'm' => Media::parse_m_line(&line)?,
395                _ => return Err(ParserError::InvalidLineFormat(line.n, "line key not m")),
396            },
397        };
398
399        // As with Session::parse, be more permissive about order than RFC 8866.
400        let mut media_title = None;
401        let mut connections = vec![];
402        let mut bandwidths = vec![];
403        let mut key = None;
404        let mut attributes = vec![];
405        while let Some(line_key) = peek_key(lines)? {
406            if line_key == b'm' {
407                break;
408            }
409            let line = lines.next()?.unwrap();
410
411            match line.key {
412                // Parse media information line
413                // - Can exist not at all or exactly once
414                b'i' => parse_rejecting_duplicates(
415                    &mut media_title,
416                    &line,
417                    ParserError::MultipleMediaTitles,
418                    |l| str_from_utf8(l.n, l.value, "Media Title"),
419                )?,
420
421                // Parse connection lines
422                // - Can exist not at all, once or multiple times
423                b'c' => connections.push(Connection::parse(&line)?),
424
425                // Parse bandwidth lines:
426                // - Can exist not at all, once or multiple times
427                b'b' => bandwidths.push(Bandwidth::parse(&line)?),
428
429                // Parse key line
430                // - Can exist not at all or exactly once
431                b'k' => parse_rejecting_duplicates(
432                    &mut key,
433                    &line,
434                    ParserError::MultipleKeys,
435                    Key::parse,
436                )?,
437
438                // Parse attribute lines:
439                // - Can exist not at all, once or multiple times
440                b'a' => attributes.push(Attribute::parse(&line)?),
441
442                _ => (),
443            }
444        }
445
446        Ok(Some(Media {
447            media_title,
448            connections,
449            bandwidths,
450            key,
451            attributes,
452            ..media
453        }))
454    }
455}
456
457impl Session {
458    /// Parse an SDP session description from a byte slice.
459    pub fn parse(data: &[u8]) -> Result<Session, ParserError> {
460        // Create an iterator which returns for each line its human-readable
461        // (1-based) line number and contents.
462        let mut lines =
463            LineParser(data.lines().enumerate().map(|(i, bytes)| (i + 1, bytes))).peekable();
464
465        // Parses anything allowed by RFC 8866 Section 9 and more:
466        // - be more lax about order. As in the RFC, "v=" must come first and
467        //   "m=" starts the media descriptions. Other fields can come in
468        //   almost any order. "r=" refers to the most recent "t=", even if it's
469        //   not the most recent line.
470        // - allow "o=", "t=" and "s=" lines to be missing.
471
472        // Check version line, which we expect to come first.
473        match lines.next()? {
474            Some(Line {
475                n,
476                key: b'v',
477                value,
478            }) => {
479                if value != b"0" {
480                    return Err(ParserError::InvalidVersion(n, value.into()));
481                }
482            }
483            _ => return Err(ParserError::NoVersion),
484        }
485
486        let mut origin = None;
487        let mut session_name = None;
488        let mut session_description = None;
489        let mut uri = None;
490        let mut emails = vec![];
491        let mut phones = vec![];
492        let mut connection = None;
493        let mut bandwidths = vec![];
494        let mut times = vec![];
495        let mut time_zones = None;
496        let mut attributes = vec![];
497        let mut key = None;
498        while let Some(line_key) = peek_key(&mut lines)? {
499            if line_key == b'm' {
500                break;
501            }
502            let line = lines.next()?.unwrap();
503            match line.key {
504                // Parse origin line:
505                // - Must only exist once or not at all
506                b'o' => parse_rejecting_duplicates(
507                    &mut origin,
508                    &line,
509                    ParserError::MultipleOrigins,
510                    Origin::parse,
511                )?,
512
513                // Parse session name line:
514                // - Must only exist once or not at all
515                b's' => parse_rejecting_duplicates(
516                    &mut session_name,
517                    &line,
518                    ParserError::MultipleSessionNames,
519                    |l| str_from_utf8(l.n, l.value, "Session Name"),
520                )?,
521
522                // Parse session information line:
523                // - Must only exist once or not at all
524                b'i' => parse_rejecting_duplicates(
525                    &mut session_description,
526                    &line,
527                    ParserError::MultipleSessionDescription,
528                    |l| str_from_utf8(l.n, l.value, "Session Description"),
529                )?,
530
531                // Parse URI line:
532                // - Must only exist once or not at all
533                b'u' => {
534                    parse_rejecting_duplicates(&mut uri, &line, ParserError::MultipleUris, |l| {
535                        str_from_utf8(l.n, l.value, "Uri")
536                    })?
537                }
538
539                // Parse E-Mail lines:
540                // - Can exist not at all, once or multiple times
541                b'e' => emails.push(str_from_utf8(line.n, line.value, "E-Mail")?),
542
543                // Parse phone number lines:
544                // - Can exist not at all, once or multiple times
545                b'p' => phones.push(str_from_utf8(line.n, line.value, "Phone")?),
546
547                // Parse connection line:
548                // - Can exist not at all or exactly once per session
549                b'c' => parse_rejecting_duplicates(
550                    &mut connection,
551                    &line,
552                    ParserError::MultipleConnections,
553                    Connection::parse,
554                )?,
555
556                // Parse bandwidth lines:
557                // - Can exist not at all, once or multiple times
558                b'b' => bandwidths.push(Bandwidth::parse(&line)?),
559
560                // Parse time lines
561                // - If followed by "r" lines then these are part of the same time field
562                b't' => times.push(Time::parse(&line)?),
563
564                // Parse repeat lines
565                // - Can exist not at all, once or multiple times
566                // - Must follow a time line (RFC 8866, section 4)
567                b'r' => {
568                    let Some(t) = times.last_mut() else {
569                        return Err(ParserError::RepeatWithoutTime(line.n));
570                    };
571                    t.repeats.push(Repeat::parse(&line)?);
572                }
573
574                // Parse zones line:
575                // - Can exist not at all or exactly once per session
576                b'z' => parse_rejecting_duplicates(
577                    &mut time_zones,
578                    &line,
579                    ParserError::MultipleTimeZones,
580                    TimeZone::parse,
581                )?,
582
583                // Parse key line
584                // - Can exist not at all or exactly once
585                b'k' => parse_rejecting_duplicates(
586                    &mut key,
587                    &line,
588                    ParserError::MultipleKeys,
589                    Key::parse,
590                )?,
591
592                // Parse attribute lines:
593                // - Can exist not at all, once or multiple times
594                b'a' => attributes.push(Attribute::parse(&line)?),
595
596                _ => (),
597            }
598        }
599
600        // The placeholder is written into sessions without an "o=" line, so
601        // it must consist of valid fields that parse again.
602        let origin =
603            origin.unwrap_or_else(|| Origin::with_ip_addr("0", 0, std::net::Ipv4Addr::UNSPECIFIED));
604        let session_name = session_name.unwrap_or_default();
605
606        let time_zones = time_zones.unwrap_or_default();
607
608        // Parse media lines:
609        // - Can exist not at all, once or multiple times
610        let mut medias = vec![];
611        while let Some(media) = Media::parse(&mut lines)? {
612            medias.push(media);
613        }
614
615        Ok(Session {
616            origin,
617            session_name,
618            session_description,
619            uri,
620            emails,
621            phones,
622            connection,
623            bandwidths,
624            times,
625            time_zones,
626            key,
627            attributes,
628            medias,
629        })
630    }
631}
632
633fn parse_rejecting_duplicates<
634    T,
635    E: Fn(usize) -> ParserError,
636    P: Fn(&Line) -> Result<T, ParserError>,
637>(
638    value: &mut Option<T>,
639    line: &Line<'_>,
640    duplicate_error_fn: E,
641    parser: P,
642) -> Result<(), ParserError> {
643    if value.is_some() {
644        return Err(duplicate_error_fn(line.n));
645    }
646    *value = Some(parser(line)?);
647    Ok(())
648}
649
650// Field parser helpers on byte slice iterators
651fn parse_str<'a>(
652    it: &mut impl Iterator<Item = &'a [u8]>,
653    line: usize,
654    field: &'static str,
655) -> Result<String, ParserError> {
656    it.next()
657        .ok_or(ParserError::MissingField(line, field))
658        .and_then(|b| {
659            std::str::from_utf8(b)
660                .map(String::from)
661                .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
662        })
663}
664
665fn parse_str_u64<'a>(
666    it: &mut impl Iterator<Item = &'a [u8]>,
667    line: usize,
668    field: &'static str,
669) -> Result<u64, ParserError> {
670    it.next()
671        .ok_or(ParserError::MissingField(line, field))
672        .and_then(|b| {
673            std::str::from_utf8(b).map_err(|_| ParserError::InvalidFieldEncoding(line, field))
674        })
675        .and_then(|s| {
676            s.parse()
677                .map_err(|_| ParserError::InvalidFieldFormat(line, field))
678        })
679}
680
681// RFC 8866, section 9: token = 1*(token-char), where token-char is
682// ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" /
683// "-" / "." / "^" / "_" / "`" / "{" / "|" / "}" / "~". RFC 4566,
684// section 9 gives the same set as ranges: %x21 / %x23-27 / %x2A-2B /
685// %x2D-2E / %x30-39 / %x41-5A / %x5E-7E.
686pub(crate) fn is_token(s: &[u8]) -> bool {
687    !s.is_empty()
688        && s.iter().all(|b| {
689            matches!(
690                b,
691                0x21 | 0x23..=0x27
692                    | 0x2a..=0x2b
693                    | 0x2d..=0x2e
694                    | 0x30..=0x39
695                    | 0x41..=0x5a
696                    | 0x5e..=0x7e
697            )
698        })
699}
700
701// Parses a token as defined by the RFC 8866 grammar, e.g. for the nettype
702// and addrtype fields. Anything else would corrupt the line structure when
703// the session description is written again.
704fn parse_token<'a>(
705    it: &mut impl Iterator<Item = &'a [u8]>,
706    line: usize,
707    field: &'static str,
708) -> Result<String, ParserError> {
709    it.next()
710        .ok_or(ParserError::MissingField(line, field))
711        .and_then(|b| {
712            if is_token(b) {
713                Ok(b)
714            } else {
715                Err(ParserError::InvalidFieldFormat(line, field))
716            }
717        })
718        .and_then(|b| {
719            std::str::from_utf8(b)
720                .map(String::from)
721                .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
722        })
723}
724
725fn parse_str_opt<'a>(
726    it: &mut impl Iterator<Item = &'a [u8]>,
727    line: usize,
728    field: &'static str,
729) -> Result<Option<String>, ParserError> {
730    it.next()
731        .map(|b| {
732            std::str::from_utf8(b)
733                .map(String::from)
734                .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
735        })
736        .transpose()
737}
738
739// Line parser helper for converting a byte slice to a string
740fn str_from_utf8(line: usize, s: &[u8], field: &'static str) -> Result<String, ParserError> {
741    std::str::from_utf8(s)
742        .map(String::from)
743        .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
744}
745
746struct Line<'item> {
747    /// The 1-based line number.
748    n: usize,
749
750    key: u8,
751
752    value: &'item [u8],
753}
754
755// Returns the key of the next line without consuming it, or `None` at the
756// end of input.
757//
758// `Peekable::peek()` does not cache errors: an `Err` return has already
759// advanced the underlying iterator, so the error would be lost unless it is
760// propagated here.
761fn peek_key<'item, I: FallibleIterator<Item = Line<'item>, Error = ParserError>>(
762    lines: &mut fallible_iterator::Peekable<I>,
763) -> Result<Option<u8>, ParserError> {
764    match lines.peek() {
765        Ok(Some(line)) => Ok(Some(line.key)),
766        Ok(None) => Ok(None),
767        Err(err) => Err(err),
768    }
769}
770
771// Parsing helper iterators below
772struct LineParser<'item, I: Iterator<Item = (usize, &'item [u8])>>(I);
773
774impl<'item, I: Iterator<Item = (usize, &'item [u8])>> FallibleIterator for LineParser<'item, I> {
775    type Item = Line<'item>;
776    type Error = ParserError;
777
778    fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
779        for (n, line) in &mut self.0 {
780            if line.is_empty() {
781                continue;
782            }
783            // NUL and CR can't appear inside an SDP line (RFC 8866,
784            // section 9, byte-string). A bare CR survives line splitting
785            // and corrupts the output when written again.
786            if line.iter().any(|&byte| byte == b'\0' || byte == b'\r') {
787                return Err(ParserError::InvalidLineFormat(n, "Line contains NUL or CR"));
788            }
789            let equals = line.iter().position(|b| *b == b'=');
790            let key = match equals {
791                None => {
792                    return Err(ParserError::InvalidLineFormat(
793                        n,
794                        "Line not in key=value format",
795                    ))
796                }
797                Some(1) => line[0],
798                _ => {
799                    return Err(ParserError::InvalidLineFormat(
800                        n,
801                        "Line key not 1 character",
802                    ))
803                }
804            };
805            return Ok(Some(Line {
806                n,
807                key,
808                value: &line[2..],
809            }));
810        }
811        Ok(None)
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use super::*;
818
819    #[test]
820    fn malformed_line_error_not_swallowed() {
821        // A malformed line in the session section must error, not be skipped.
822        let sdp = b"v=0\r
823s=x\r
824broken\r
825t=0 0\r
826";
827        assert!(matches!(
828            Session::parse(&sdp[..]),
829            Err(ParserError::InvalidLineFormat(
830                3,
831                "Line not in key=value format"
832            ))
833        ));
834
835        // Same for a malformed line in a media section, before the next 'm='.
836        let sdp = b"v=0\r
837m=audio 49170 RTP/AVP 0\r
838broken\r
839";
840        assert!(matches!(
841            Session::parse(&sdp[..]),
842            Err(ParserError::InvalidLineFormat(
843                3,
844                "Line not in key=value format"
845            ))
846        ));
847    }
848
849    #[test]
850    fn nettype_and_addrtype_must_be_tokens() {
851        // RFC 8866, section 9 defines nettype/addrtype as tokens. Other
852        // values would corrupt the line structure when written again.
853        let sdp = b"v=0\r
854o=- 0 0 IN:X IP4 1.2.3.4\r
855s=x\r
856t=0 0\r
857";
858        assert_eq!(
859            Session::parse(&sdp[..]),
860            Err(ParserError::InvalidFieldFormat(2, "Origin nettype"))
861        );
862
863        let sdp = b"v=0\r
864o=- 0 0 IN IP4:x 1.2.3.4\r
865s=x\r
866t=0 0\r
867";
868        assert_eq!(
869            Session::parse(&sdp[..]),
870            Err(ParserError::InvalidFieldFormat(2, "Origin addrtype"))
871        );
872
873        let sdp = b"v=0\r
874s=x\r
875t=0 0\r
876c=IN:X IP4 1.2.3.4\r
877";
878        assert_eq!(
879            Session::parse(&sdp[..]),
880            Err(ParserError::InvalidFieldFormat(4, "Connection nettype"))
881        );
882
883        // Characters outside the token set (RFC 8866, section 9), e.g.
884        // '<' or '/', are rejected as well.
885        let sdp = b"v=0\r
886o=- 0 0 a<b IP4 1.2.3.4\r
887s=x\r
888t=0 0\r
889";
890        assert_eq!(
891            Session::parse(&sdp[..]),
892            Err(ParserError::InvalidFieldFormat(2, "Origin nettype"))
893        );
894
895        let sdp = b"v=0\r
896o=- 0 0 IN IP4/x 1.2.3.4\r
897s=x\r
898t=0 0\r
899";
900        assert_eq!(
901            Session::parse(&sdp[..]),
902            Err(ParserError::InvalidFieldFormat(2, "Origin addrtype"))
903        );
904    }
905
906    #[test]
907    fn nul_and_cr_in_lines_is_error() {
908        // NUL and CR are not valid inside SDP lines (RFC 8866, section 9,
909        // byte-string). Accepting them breaks round-tripping: the writer
910        // strips them, which can leave mandatory fields empty.
911        let sdp = b"v=0\r
912o=jdoe 2e 22 2 on \x00\r
913";
914        assert_eq!(
915            Session::parse(&sdp[..]),
916            Err(ParserError::InvalidLineFormat(2, "Line contains NUL or CR"))
917        );
918
919        // A bare CR survives line splitting and would corrupt the line
920        // structure when written again.
921        let sdp = b"v=0\r
922s=x\r
923c=IN IP4 a\rb\r
924";
925        assert_eq!(
926            Session::parse(&sdp[..]),
927            Err(ParserError::InvalidLineFormat(3, "Line contains NUL or CR"))
928        );
929    }
930
931    #[test]
932    fn session_without_origin_round_trips() {
933        // A missing "o=" line is accepted as a leniency. The placeholder
934        // origin written for it must parse back, or the session does not
935        // round-trip through write()/parse().
936        let sdp = b"v=0\r
937s=x\r
938t=0 0\r
939";
940        let session = Session::parse(&sdp[..]).unwrap();
941
942        let mut written = vec![];
943        session.write(&mut written).unwrap();
944        let parsed = Session::parse(&written[..]).unwrap();
945        assert_eq!(parsed.origin, session.origin);
946    }
947
948    #[test]
949    fn repeat_line_without_time_line_is_error() {
950        // Repeat lines must follow a time line (RFC 8866, section 4).
951        let sdp = b"v=0\r
952s=x\r
953r=7d 1h 0 90000\r
954t=0 0\r
955";
956        assert_eq!(
957            Session::parse(&sdp[..]),
958            Err(ParserError::RepeatWithoutTime(3))
959        );
960
961        // Repeat lines after a time line are still fine, also with multiple
962        // time lines.
963        let sdp = b"v=0\r
964s=x\r
965t=0 0\r
966r=7d 1h 0 90000\r
967t=1 2\r
968r=1d 1h 0\r
969";
970        let session = Session::parse(&sdp[..]).unwrap();
971        assert_eq!(session.times.len(), 2);
972        assert_eq!(session.times[0].repeats.len(), 1);
973        assert_eq!(session.times[1].repeats.len(), 1);
974    }
975
976    #[test]
977    fn origin_and_connection_field_strictness() {
978        // Whitespace leniency: runs of spaces and trailing spaces must not
979        // shift or corrupt the fields.
980        let sdp = b"v=0\r
981o=-  4922720 1 IN IP4 10.0.0.108 \r
982s=x\r
983c=IN IP4  224.2.17.12/127 \r
984t=0 0\r
985";
986        let session = Session::parse(&sdp[..]).unwrap();
987        assert_eq!(session.origin.sess_id, "4922720");
988        assert_eq!(session.origin.sess_version, 1);
989        assert_eq!(session.origin.unicast_address, "10.0.0.108");
990        assert_eq!(
991            session.connection.as_ref().unwrap().connection_address,
992            "224.2.17.12/127"
993        );
994
995        // Extra fields must be rejected instead of being absorbed into the
996        // last field.
997        let sdp = b"v=0\r
998o=- 4922720 1 IN IP4 10.0.0.108 extra\r
999s=x\r
1000t=0 0\r
1001";
1002        assert!(matches!(
1003            Session::parse(&sdp[..]),
1004            Err(ParserError::FieldTrailingData(2, "Origin"))
1005        ));
1006
1007        let sdp = b"v=0\r
1008o=- 4922720 1 IN IP4 10.0.0.108\r
1009s=x\r
1010c=IN IP4 224.2.17.12/127 extra\r
1011t=0 0\r
1012";
1013        assert!(matches!(
1014            Session::parse(&sdp[..]),
1015            Err(ParserError::FieldTrailingData(4, "Connection"))
1016        ));
1017    }
1018
1019    #[test]
1020    fn parse_sdp() {
1021        let sdp = b"v=0\r
1022o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
1023s=SDP Seminar\r
1024i=A Seminar on the session description protocol\r
1025u=http://www.example.com/seminars/sdp.pdf\r
1026e=j.doe@example.com (Jane Doe)\r
1027p=+1 617 555-6011\r
1028c=IN IP4 224.2.17.12/127\r
1029b=AS:128\r
1030t=2873397496 2873404696\r
1031r=7d 1h 0 25h\r
1032z=2882844526 -1h 2898848070 0\r
1033k=clear:1234\r
1034a=recvonly\r
1035m=audio 49170 RTP/AVP 0\r
1036m=video 51372/2 RTP/AVP 99\r
1037a=rtpmap:99 h263-1998/90000\r
1038a=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
1039";
1040        let parsed = Session::parse(&sdp[..]).unwrap();
1041        let expected = Session {
1042            origin: Origin {
1043                username: Some("jdoe".into()),
1044                sess_id: "2890844526".into(),
1045                sess_version: 2890842807,
1046                nettype: "IN".into(),
1047                addrtype: "IP4".into(),
1048                unicast_address: "10.47.16.5".into(),
1049            },
1050            session_name: "SDP Seminar".into(),
1051            session_description: Some("A Seminar on the session description protocol".into()),
1052            uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
1053            emails: vec!["j.doe@example.com (Jane Doe)".into()],
1054            phones: vec!["+1 617 555-6011".into()],
1055            connection: Some(Connection {
1056                nettype: "IN".into(),
1057                addrtype: "IP4".into(),
1058                connection_address: "224.2.17.12/127".into(),
1059            }),
1060            bandwidths: vec![Bandwidth {
1061                bwtype: "AS".into(),
1062                bandwidth: 128,
1063            }],
1064            times: vec![Time {
1065                start_time: 2873397496,
1066                stop_time: 2873404696,
1067                repeats: vec![Repeat {
1068                    repeat_interval: 604800,
1069                    active_duration: 3600,
1070                    offsets: vec![0, 90000],
1071                }],
1072            }],
1073            time_zones: vec![
1074                TimeZone {
1075                    adjustment_time: 2882844526,
1076                    offset: -3600,
1077                },
1078                TimeZone {
1079                    adjustment_time: 2898848070,
1080                    offset: 0,
1081                },
1082            ],
1083            key: Some(Key {
1084                method: "clear".into(),
1085                encryption_key: Some("1234".into()),
1086            }),
1087            attributes: vec![Attribute {
1088                attribute: "recvonly".into(),
1089                value: None,
1090            }],
1091            medias: vec![
1092                Media {
1093                    media: "audio".into(),
1094                    port: 49170,
1095                    num_ports: None,
1096                    proto: "RTP/AVP".into(),
1097                    fmt: "0".into(),
1098                    media_title: None,
1099                    connections: vec![],
1100                    bandwidths: vec![],
1101                    key: None,
1102                    attributes: vec![],
1103                },
1104                Media {
1105                    media: "video".into(),
1106                    port: 51372,
1107                    num_ports: Some(2),
1108                    proto: "RTP/AVP".into(),
1109                    fmt: "99".into(),
1110                    media_title: None,
1111                    connections: vec![],
1112                    bandwidths: vec![],
1113                    key: None,
1114                    attributes: vec![
1115                        Attribute {
1116                            attribute: "rtpmap".into(),
1117                            value: Some("99 h263-1998/90000".into()),
1118                        },
1119                        Attribute {
1120                            attribute: "fingerprint".into(),
1121                            value: Some("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".into()),
1122                        }
1123                    ],
1124                },
1125            ],
1126        };
1127
1128        assert_eq!(parsed, expected);
1129    }
1130
1131    #[test]
1132    fn parse_only_key() {
1133        Session::parse(b"v\n").unwrap_err();
1134    }
1135
1136    #[test]
1137    fn parse_sdp_real_camera() {
1138        let sdp = b"v=0\r
1139o=VSTC 3828747520 3828747520 IN IP4 192.168.1.165\r
1140s=streamed by the VSTARCAM RTSP server\r
1141e=NONE\r
1142c=IN IP4 0.0.0.0\r
1143t=0 0\r
1144m=video 0 RTP/AVP 96\r
1145b=AS:1024\r
1146a=control:track0\r
1147a=rtpmap:96 H264/90000\r
1148a=fmtp:96 packetization-mode=1;profile-level-id=4d001f;sprop-parameter-sets=Z00AH52oFAFum4CAgKAAAAMAIAAAAwHwgA==,aO48gA==\r
1149m=audio 0 RTP/AVP 8	 \r
1150b=AS:64\r
1151a=control:track1\r
1152a=rtpmap:8 PCMA/8000/1\r
1153
1154";
1155        let parsed = Session::parse(&sdp[..]).unwrap();
1156        assert_eq!(parsed.origin.nettype, NetType::In);
1157    }
1158
1159    /// Parses SDP from a Geovision camera which (incorrectly) omits the "t="
1160    /// line.
1161    #[test]
1162    fn parse_sdp_geovision() {
1163        let sdp = b"v=0\r
1164o=- 1001 1 IN IP4 192.168.5.237\r
1165s=VCP IPC Realtime stream\r
1166m=video 0 RTP/AVP 105\r
1167c=IN IP4 192.168.5.237\r
1168a=control:rtsp://192.168.5.237/media/video1/video\r
1169a=rtpmap:105 H264/90000\r
1170a=fmtp:105 profile-level-id=4d4032; packetization-mode=1; sprop-parameter-sets=Z01AMpWgCoAwfiZuAgICgAAB9AAAdTBC,aO48gA==\r
1171a=recvonly\r
1172m=application 0 RTP/AVP 107\r
1173c=IN IP4 192.168.5.237\r
1174a=control:rtsp://192.168.5.237/media/video1/metadata\r
1175a=rtpmap:107 vnd.onvif.metadata/90000\r
1176a=fmtp:107 DecoderTag=h3c-v3 RTCP=0\r
1177a=recvonly\r
1178";
1179        let _parsed = Session::parse(&sdp[..]).unwrap();
1180    }
1181
1182    /// Parses SDP from an Anpviz camera which (incorrectly) places an `a=`
1183    /// between the `c=` and `t=` lines of a session.
1184    #[test]
1185    fn parse_sdp_anpviz() {
1186        let sdp = b"v=0\r
1187o=- 1109162014219182 1109162014219192 IN IP4 x.y.z.w\r
1188s=RTSP/RTP stream from anjvision ipcamera\r
1189e=NONE\r
1190c=IN IP4 0.0.0.0\r
1191a=tool:LIVE555 Streaming Media v2011.05.25 CHAM.LI@ANJVISION.COM\r
1192t=0 0\r
1193a=range:npt=0-\r
1194a=control:*\r
1195m=video 0 RTP/AVP 96\r
1196a=rtpmap:96 H264/90000\r
1197a=control:trackID=1\r
1198a=fmtp:96 profile-level-id=4D401F;packetization-mode=0;sprop-parameter-sets=Z01AH5WgLASabAQ=,aO48gA==;config=00000001674d401f95a02c049a6c040000000168ee3c800000000106f02c0445c6f5000620ebc2f3f7639e48250bfcb561bb2b85dda6fe5f06cc8b887b6a915f5aa3bebfffffffffff7380\r
1199a=x-dimensions: 704, 576\r
1200a=x-framerate: 12\r
1201m=audio 0 RTP/AVP 0\r
1202a=rtpmap:0 MPEG4-GENERIC/16000/2\r
1203a=fmtp:0 config=1408\r
1204a=control:trackID=2\r
1205a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;\r
1206a=appversion:1.0\r
1207";
1208        let _parsed = Session::parse(&sdp[..]).unwrap();
1209    }
1210
1211    #[test]
1212    fn parse_overflowing_time() {
1213        assert_eq!(
1214            Session::parse(b"v=0\no=- 0 0 IN IP4 0.0.0.0\ns=q\nt=0 5\nz=00 666666000079866660m "),
1215            Err(ParserError::InvalidFieldFormat(5, "TimeZone offset"))
1216        );
1217
1218        // The garbage in the origin line is now rejected instead of being
1219        // absorbed into its fields. The NUL is rejected at line level.
1220        assert_eq!(
1221            Session::parse(b"v=0\no=  0  =\x00 \ns=q\nt=0 5\nz=00 666666000079866660m "),
1222            Err(ParserError::InvalidLineFormat(2, "Line contains NUL or CR"))
1223        );
1224        assert_eq!(
1225            Session::parse(b"v=0\no=  0  = \ns=q\nt=0 5\nz=00 666666000079866660m "),
1226            Err(ParserError::MissingField(2, "Origin sess-version"))
1227        );
1228    }
1229
1230    #[test]
1231    fn parse_sdp_without_origin() {
1232        let sdp = b"v=0\r
1233s=streamed by the macro-video rtsp server\r
1234t=0 0\r
1235a=control:*\r
1236a=range:npt=0-\r
1237a=x-qt-text-nam:streamed by the macro-video rtsp server\r
1238c=IN IP4 0.0.0.0\r
1239m=video 0 RTP/AVP 96\r
1240b=AS:500\r
1241a=rtpmap:96 H264/90000\r
1242a=fmtp:96 profile-level-id=TeAo;packetization-mode=1;sprop-parameter-sets=J03gKI1oBQBboQAAAwABAAADACgPFCKg,KO4BNJI=\r
1243a=control:track1\r
1244";
1245        let _parsed = Session::parse(&sdp[..]).unwrap();
1246    }
1247
1248    #[test]
1249    fn parse_sdp_data_after_media() {
1250        let sdp = b"v=0\r
1251o=- 1691154453 1 IN IP4 192.168.1.100\r
1252i=Pagos\r
1253a=type:broadcast\r
1254s=RandD2\r
1255m=video 15002 RTP/AVP 97\r
1256a=range:npt=0-\r
1257a=rtpmap:97 H264/90000\r
1258a=fmtp:97 profile-level-id=4D4029; packetization-mode=1; sprop-parameter-sets=Z01AKZZUBQHsgA==,aO44gA==\r
1259a=framerate:15.000\r
1260a=control:rtsp://192.168.1.20/camera1.sdp\r
1261c=IN IP4 0.0.0.0\r
1262t=0 0\r
1263";
1264        let _parsed = Session::parse(&sdp[..]).unwrap();
1265    }
1266
1267    #[test]
1268    fn parse_sdp_without_session_name() {
1269        let sdp = b"v=0\r
1270o=- 1109162014219182 1109162014219192 IN IP4 x.y.z.w\r
1271t=0 0\r
1272a=control:*\r
1273a=range:npt=0-\r
1274a=x-qt-text-nam:streamed by the macro-video rtsp server\r
1275c=IN IP4 0.0.0.0\r
1276m=video 0 RTP/AVP 96\r
1277b=AS:500\r
1278a=rtpmap:96 H264/90000\r
1279a=fmtp:96 profile-level-id=TeAo;packetization-mode=1;sprop-parameter-sets=J03gKI1oBQBboQAAAwABAAADACgPFCKg,KO4BNJI=\r
1280a=control:track1\r
1281";
1282        let _parsed = Session::parse(&sdp[..]).unwrap();
1283    }
1284
1285    #[test]
1286    fn parse_sdp_with_trailing_spaces() {
1287        // Obtained from a camera integrated into Elegoo Saturn 4 Ultra
1288        // Notice the trailing space in "t" field
1289        let sdp = b"v=0\r
1290o=- 4922720 1 IN IP4 10.0.0.108\r
1291t=0 0 \r
1292a=control:*\r
1293m=video 0 RTP/AVP 96\r
1294a=rtpmap:96 H264/9000\r
1295a=control:track0\r
1296";
1297        let _parsed = Session::parse(&sdp[..]).unwrap();
1298    }
1299}