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}
54
55impl std::error::Error for ParserError {}
56
57impl std::fmt::Display for ParserError {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
59        use std::convert::TryFrom;
60
61        match *self {
62            #[allow(deprecated)]
63            ParserError::UnexpectedLine(line, c) => {
64                if let Ok(c) = char::try_from(c as u32) {
65                    write!(f, "Unexpected line {} starting with '{}'", line, c)
66                } else {
67                    write!(f, "Unexpected line {} starting with U+{:04x}", line, c)
68                }
69            }
70            ParserError::InvalidLineFormat(line, ref s) => {
71                write!(f, "Invalid formatted line {}: \"{}\"", line, s)
72            }
73            ParserError::InvalidFieldEncoding(line, s) => {
74                write!(f, "Invalid field encoding in line {} at {}", line, s)
75            }
76            ParserError::InvalidFieldFormat(line, s) => {
77                write!(f, "Invalid field formatting in line {} at {}", line, s)
78            }
79            ParserError::MissingField(line, s) => write!(f, "Missing field {} in line {}", s, line),
80            ParserError::FieldTrailingData(line, s) => {
81                write!(f, "Field {} in line {} has trailing data", s, line)
82            }
83            ParserError::InvalidVersion(line, ref s) => write!(
84                f,
85                "Invalid version at line {}: {}",
86                line,
87                String::from_utf8_lossy(s)
88            ),
89            ParserError::MultipleVersions(line) => write!(f, "Multiple versions in line {}", line),
90            ParserError::NoVersion => write!(f, "No version line"),
91            ParserError::MultipleOrigins(line) => write!(f, "Multiple origins in line {}", line),
92            #[allow(deprecated)]
93            ParserError::NoOrigin => write!(f, "No origin line"),
94            ParserError::MultipleSessionNames(line) => {
95                write!(f, "Multiple session-names in line {}", line)
96            }
97            #[allow(deprecated)]
98            ParserError::NoSessionName => write!(f, "No session-name line"),
99            ParserError::MultipleSessionDescription(line) => {
100                write!(f, "Multiple session-information in line {}", line)
101            }
102            ParserError::MultipleUris(line) => write!(f, "Multiple URIs in line {}", line),
103            ParserError::MultipleConnections(line) => {
104                write!(f, "Multiple connections in line {}", line)
105            }
106            ParserError::MultipleTimeZones(line) => write!(f, "Multiple zones in line {}", line),
107            ParserError::MultipleMediaTitles(line) => {
108                write!(f, "Multiple media titles in line {}", line)
109            }
110            ParserError::MultipleKeys(line) => write!(f, "Multiple keys in line {}", line),
111        }
112    }
113}
114
115impl Origin {
116    fn parse(line: &Line) -> Result<Origin, ParserError> {
117        // <username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
118        let mut origin = line.value.splitn_str(6, b" ");
119        let username = parse_str(&mut origin, line.n, "Origin username")?;
120        let sess_id = parse_str(&mut origin, line.n, "Origin sess-id")?;
121        let sess_version = parse_str_u64(&mut origin, line.n, "Origin sess-version")?;
122        let nettype = parse_str(&mut origin, line.n, "Origin nettype")?;
123        let addrtype = parse_str(&mut origin, line.n, "Origin addrtype")?;
124        let unicast_address = parse_str(&mut origin, line.n, "Origin unicast-address")?;
125
126        Ok(Origin {
127            username: if username == "-" {
128                None
129            } else {
130                Some(username)
131            },
132            sess_id,
133            sess_version,
134            nettype,
135            addrtype,
136            unicast_address,
137        })
138    }
139}
140
141impl Connection {
142    fn parse(line: &Line) -> Result<Connection, ParserError> {
143        // <nettype> <addrtype> <connection-address>
144        let mut connection = line.value.splitn_str(3, b" ");
145        let nettype = parse_str(&mut connection, line.n, "Connection nettype")?;
146        let addrtype = parse_str(&mut connection, line.n, "Connection addrtype")?;
147        let connection_address =
148            parse_str(&mut connection, line.n, "Connection connection-address")?;
149
150        Ok(Connection {
151            nettype,
152            addrtype,
153            connection_address,
154        })
155    }
156}
157
158impl Bandwidth {
159    fn parse(line: &Line) -> Result<Bandwidth, ParserError> {
160        // <bwtype>:<bandwidth>
161        let mut bandwidth = line.value.split_str(b":");
162        let bwtype = parse_str(&mut bandwidth, line.n, "Bandwidth bwtype")?;
163        let bw = parse_str_u64(&mut bandwidth, line.n, "Bandwidth bandwidth")?;
164        if bandwidth.next().is_some() {
165            return Err(ParserError::FieldTrailingData(line.n, "Bandwidth"));
166        }
167
168        Ok(Bandwidth {
169            bwtype,
170            bandwidth: bw,
171        })
172    }
173}
174
175impl Time {
176    fn parse(line: &Line) -> Result<Time, ParserError> {
177        // <start-time> <stop-time>
178        let mut time = line.value.split_str(b" ").filter(|part| !part.is_empty());
179        let start_time = parse_str_u64(&mut time, line.n, "Time start-time")?;
180        let stop_time = parse_str_u64(&mut time, line.n, "Time stop-time")?;
181        if time.next().is_some() {
182            return Err(ParserError::FieldTrailingData(line.n, "Time"));
183        }
184
185        Ok(Time {
186            start_time,
187            stop_time,
188            repeats: Vec::new(),
189        })
190    }
191}
192
193fn parse_typed_time(s: &[u8], line: usize, field: &'static str) -> Result<u64, ParserError> {
194    let (num, factor) = match s
195        .split_last()
196        .ok_or(ParserError::InvalidFieldFormat(line, field))?
197    {
198        (b'd', prefix) => (prefix, 86_400),
199        (b'h', prefix) => (prefix, 3_600),
200        (b'm', prefix) => (prefix, 60),
201        (b's', prefix) => (prefix, 1),
202        (_, _) => (s, 1),
203    };
204
205    let num =
206        std::str::from_utf8(num).map_err(|_| ParserError::InvalidFieldEncoding(line, field))?;
207    let num = num
208        .parse::<u64>()
209        .map_err(|_| ParserError::InvalidFieldFormat(line, field))?;
210    num.checked_mul(factor)
211        .ok_or(ParserError::InvalidFieldFormat(line, field))
212}
213
214impl Repeat {
215    fn parse(line: &Line) -> Result<Repeat, ParserError> {
216        // <repeat interval> <active duration> <offsets from start-time>
217        let mut repeat = line.value.split_str(b" ").filter(|part| !part.is_empty());
218        let repeat_interval = repeat
219            .next()
220            .ok_or(ParserError::MissingField(line.n, "Repeat repeat-interval"))
221            .and_then(|s| parse_typed_time(s, line.n, "Repeat repeat-interval"))?;
222        let active_duration = repeat
223            .next()
224            .ok_or(ParserError::MissingField(line.n, "Repeat active-duration"))
225            .and_then(|s| parse_typed_time(s, line.n, "Repeat active-duration"))?;
226
227        let offsets = repeat
228            .map(|s| parse_typed_time(s, line.n, "Repeat active-duration"))
229            .collect::<Result<Vec<_>, _>>()?;
230
231        Ok(Repeat {
232            repeat_interval,
233            active_duration,
234            offsets,
235        })
236    }
237}
238
239impl TimeZone {
240    fn parse(line: &Line) -> Result<Vec<TimeZone>, ParserError> {
241        // <adjustment time> <offset> <adjustment time> <offset> ....
242        let mut zones = line.value.split_str(b" ").filter(|part| !part.is_empty());
243
244        let mut ret = Vec::new();
245        loop {
246            let adjustment_time = parse_str_u64(&mut zones, line.n, "TimeZone adjustment-time");
247
248            let adjustment_time = match adjustment_time {
249                Ok(adjustment_time) => adjustment_time,
250                Err(ParserError::MissingField(..)) => break,
251                Err(err) => return Err(err),
252            };
253
254            let offset = zones
255                .next()
256                .ok_or(ParserError::MissingField(line.n, "TimeZone offset"))
257                .and_then(|s| {
258                    use std::convert::TryInto;
259
260                    let (sign, s) = if s.first() == Some(&b'-') {
261                        (true, &s[1..])
262                    } else {
263                        (false, s)
264                    };
265
266                    parse_typed_time(s, line.n, "TimeZone offset")
267                        .and_then(|t| {
268                            t.try_into().map_err(|_| {
269                                ParserError::InvalidFieldFormat(line.n, "TimeZone offset")
270                            })
271                        })
272                        .map(|t: i64| if sign { -t } else { t })
273                })?;
274
275            ret.push(TimeZone {
276                adjustment_time,
277                offset,
278            });
279        }
280
281        Ok(ret)
282    }
283}
284
285impl Attribute {
286    fn parse(line: &Line) -> Result<Attribute, ParserError> {
287        // <attribute>:<value>
288        // <attribute>
289        let mut attribute = line.value.splitn_str(2, b":");
290        let name = parse_str(&mut attribute, line.n, "Attribute name")?;
291        let value = parse_str_opt(&mut attribute, line.n, "Attribute value")?;
292
293        Ok(Attribute {
294            attribute: name,
295            value,
296        })
297    }
298}
299
300impl Key {
301    fn parse(line: &Line) -> Result<Key, ParserError> {
302        // <method>:<encryption key>
303        // <method>
304        let mut key = line.value.splitn_str(2, b":");
305        let method = parse_str(&mut key, line.n, "Key method")?;
306        let encryption_key = parse_str_opt(&mut key, line.n, "Key encryption-key")?;
307
308        Ok(Key {
309            method,
310            encryption_key,
311        })
312    }
313}
314
315impl Media {
316    fn parse_m_line(line: &Line) -> Result<Media, ParserError> {
317        // <media> <port> <proto> <fmt> ...
318        let mut media = line.value.splitn_str(4, b" ");
319        let name = parse_str(&mut media, line.n, "Media name")?;
320
321        let (port, num_ports) = media
322            .next()
323            .ok_or(ParserError::MissingField(line.n, "Media port"))
324            .and_then(|s| str_from_utf8(line.n, s, "Media Port"))
325            .and_then(|port| {
326                let mut split = port.splitn(2, '/');
327                let port = split
328                    .next()
329                    .ok_or(ParserError::MissingField(line.n, "Media port"))
330                    .and_then(|port| {
331                        port.parse()
332                            .map_err(|_| ParserError::InvalidFieldFormat(line.n, "Media port"))
333                    })?;
334
335                let num_ports = split
336                    .next()
337                    .ok_or(ParserError::MissingField(line.n, "Media num-ports"))
338                    .and_then(|num_ports| {
339                        num_ports
340                            .parse()
341                            .map_err(|_| ParserError::InvalidFieldFormat(line.n, "Media num-ports"))
342                    });
343
344                match num_ports {
345                    Ok(num_ports) => Ok((port, Some(num_ports))),
346                    Err(ParserError::MissingField(..)) => Ok((port, None)),
347                    Err(err) => Err(err),
348                }
349            })?;
350
351        let proto = parse_str(&mut media, line.n, "Media proto")?;
352        let fmt = parse_str(&mut media, line.n, "Media fmt")?;
353
354        Ok(Media {
355            media: name,
356            port,
357            num_ports,
358            proto,
359            fmt,
360            media_title: None,
361            connections: Vec::new(),
362            bandwidths: Vec::new(),
363            key: None,
364            attributes: Vec::new(),
365        })
366    }
367
368    fn parse<'a, I: FallibleIterator<Item = Line<'a>, Error = ParserError>>(
369        lines: &mut fallible_iterator::Peekable<I>,
370    ) -> Result<Option<Media>, ParserError> {
371        let media = match lines.next()? {
372            None => return Ok(None),
373            Some(line) => {
374                assert_eq!(line.key, b'm');
375                Media::parse_m_line(&line)?
376            }
377        };
378
379        // As with Session::parse, be more permissive about order than RFC 8866.
380        let mut media_title = None;
381        let mut connections = vec![];
382        let mut bandwidths = vec![];
383        let mut key = None;
384        let mut attributes = vec![];
385        while matches!(lines.peek(), Ok(Some(Line { key, .. })) if *key != b'm') {
386            let line = lines.next().unwrap().unwrap();
387
388            match line.key {
389                // Parse media information line
390                // - Can exist not at all or exactly once
391                b'i' => parse_rejecting_duplicates(
392                    &mut media_title,
393                    &line,
394                    ParserError::MultipleMediaTitles,
395                    |l| str_from_utf8(l.n, l.value, "Media Title"),
396                )?,
397
398                // Parse connection lines
399                // - Can exist not at all, once or multiple times
400                b'c' => connections.push(Connection::parse(&line)?),
401
402                // Parse bandwidth lines:
403                // - Can exist not at all, once or multiple times
404                b'b' => bandwidths.push(Bandwidth::parse(&line)?),
405
406                // Parse key line
407                // - Can exist not at all or exactly once
408                b'k' => parse_rejecting_duplicates(
409                    &mut key,
410                    &line,
411                    ParserError::MultipleKeys,
412                    Key::parse,
413                )?,
414
415                // Parse attribute lines:
416                // - Can exist not at all, once or multiple times
417                b'a' => attributes.push(Attribute::parse(&line)?),
418
419                _ => (),
420            }
421        }
422
423        Ok(Some(Media {
424            media_title,
425            connections,
426            bandwidths,
427            key,
428            attributes,
429            ..media
430        }))
431    }
432}
433
434impl Session {
435    /// Parse an SDP session description from a byte slice.
436    pub fn parse(data: &[u8]) -> Result<Session, ParserError> {
437        // Create an iterator which returns for each line its human-readable
438        // (1-based) line number and contents.
439        let mut lines =
440            LineParser(data.lines().enumerate().map(|(i, bytes)| (i + 1, bytes))).peekable();
441
442        // Parses anything allowed by RFC 8866 Section 9 and more:
443        // - be more lax about order. As in the RFC, "v=" must come first and
444        //   "m=" starts the media descriptions. Other fields can come in
445        //   almost any order. "r=" refers to the most recent "t=", even if it's
446        //   not the most recent line.
447        // - allow "o=", "t=" and "s=" lines to be missing.
448
449        // Check version line, which we expect to come first.
450        match lines.next()? {
451            Some(Line {
452                n,
453                key: b'v',
454                value,
455            }) => {
456                if value != b"0" {
457                    return Err(ParserError::InvalidVersion(n, value.into()));
458                }
459            }
460            _ => return Err(ParserError::NoVersion),
461        }
462
463        let mut origin = None;
464        let mut session_name = None;
465        let mut session_description = None;
466        let mut uri = None;
467        let mut emails = vec![];
468        let mut phones = vec![];
469        let mut connection = None;
470        let mut bandwidths = vec![];
471        let mut times = vec![];
472        let mut time_zones = None;
473        let mut attributes = vec![];
474        let mut key = None;
475        while matches!(lines.peek(), Ok(Some(Line { key, .. })) if *key != b'm') {
476            let line = lines.next().unwrap().unwrap();
477            match line.key {
478                // Parse origin line:
479                // - Must only exist once or not at all
480                b'o' => parse_rejecting_duplicates(
481                    &mut origin,
482                    &line,
483                    ParserError::MultipleOrigins,
484                    Origin::parse,
485                )?,
486
487                // Parse session name line:
488                // - Must only exist once or not at all
489                b's' => parse_rejecting_duplicates(
490                    &mut session_name,
491                    &line,
492                    ParserError::MultipleSessionNames,
493                    |l| str_from_utf8(l.n, l.value, "Session Name"),
494                )?,
495
496                // Parse session information line:
497                // - Must only exist once or not at all
498                b'i' => parse_rejecting_duplicates(
499                    &mut session_description,
500                    &line,
501                    ParserError::MultipleSessionDescription,
502                    |l| str_from_utf8(l.n, l.value, "Session Description"),
503                )?,
504
505                // Parse URI line:
506                // - Must only exist once or not at all
507                b'u' => {
508                    parse_rejecting_duplicates(&mut uri, &line, ParserError::MultipleUris, |l| {
509                        str_from_utf8(l.n, l.value, "Uri")
510                    })?
511                }
512
513                // Parse E-Mail lines:
514                // - Can exist not at all, once or multiple times
515                b'e' => emails.push(str_from_utf8(line.n, line.value, "E-Mail")?),
516
517                // Parse phone number lines:
518                // - Can exist not at all, once or multiple times
519                b'p' => phones.push(str_from_utf8(line.n, line.value, "Phone")?),
520
521                // Parse connection line:
522                // - Can exist not at all or exactly once per session
523                b'c' => parse_rejecting_duplicates(
524                    &mut connection,
525                    &line,
526                    ParserError::MultipleConnections,
527                    Connection::parse,
528                )?,
529
530                // Parse bandwidth lines:
531                // - Can exist not at all, once or multiple times
532                b'b' => bandwidths.push(Bandwidth::parse(&line)?),
533
534                // Parse time lines
535                // - If followed by "r" lines then these are part of the same time field
536                b't' => times.push(Time::parse(&line)?),
537
538                // Parse repeat lines
539                // - Can exist not at all, once or multiple times
540                b'r' => {
541                    if let Some(t) = times.last_mut() {
542                        t.repeats.push(Repeat::parse(&line)?);
543                    }
544                }
545
546                // Parse zones line:
547                // - Can exist not at all or exactly once per session
548                b'z' => parse_rejecting_duplicates(
549                    &mut time_zones,
550                    &line,
551                    ParserError::MultipleTimeZones,
552                    TimeZone::parse,
553                )?,
554
555                // Parse key line
556                // - Can exist not at all or exactly once
557                b'k' => parse_rejecting_duplicates(
558                    &mut key,
559                    &line,
560                    ParserError::MultipleKeys,
561                    Key::parse,
562                )?,
563
564                // Parse attribute lines:
565                // - Can exist not at all, once or multiple times
566                b'a' => attributes.push(Attribute::parse(&line)?),
567
568                _ => (),
569            }
570        }
571
572        let origin = origin.unwrap_or_else(|| Origin {
573            username: None,
574            sess_id: String::new(),
575            sess_version: 0,
576            nettype: String::new(),
577            addrtype: String::new(),
578            unicast_address: String::new(),
579        });
580        let session_name = session_name.unwrap_or_default();
581
582        let time_zones = time_zones.unwrap_or_default();
583
584        // Parse media lines:
585        // - Can exist not at all, once or multiple times
586        let mut medias = vec![];
587        while let Some(media) = Media::parse(&mut lines)? {
588            medias.push(media);
589        }
590
591        Ok(Session {
592            origin,
593            session_name,
594            session_description,
595            uri,
596            emails,
597            phones,
598            connection,
599            bandwidths,
600            times,
601            time_zones,
602            key,
603            attributes,
604            medias,
605        })
606    }
607}
608
609fn parse_rejecting_duplicates<
610    T,
611    E: Fn(usize) -> ParserError,
612    P: Fn(&Line) -> Result<T, ParserError>,
613>(
614    value: &mut Option<T>,
615    line: &Line<'_>,
616    duplicate_error_fn: E,
617    parser: P,
618) -> Result<(), ParserError> {
619    if value.is_some() {
620        return Err(duplicate_error_fn(line.n));
621    }
622    *value = Some(parser(line)?);
623    Ok(())
624}
625
626// Field parser helpers on byte slice iterators
627fn parse_str<'a>(
628    it: &mut impl Iterator<Item = &'a [u8]>,
629    line: usize,
630    field: &'static str,
631) -> Result<String, ParserError> {
632    it.next()
633        .ok_or(ParserError::MissingField(line, field))
634        .and_then(|b| {
635            std::str::from_utf8(b)
636                .map(String::from)
637                .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
638        })
639}
640
641fn parse_str_u64<'a>(
642    it: &mut impl Iterator<Item = &'a [u8]>,
643    line: usize,
644    field: &'static str,
645) -> Result<u64, ParserError> {
646    it.next()
647        .ok_or(ParserError::MissingField(line, field))
648        .and_then(|b| {
649            std::str::from_utf8(b).map_err(|_| ParserError::InvalidFieldEncoding(line, field))
650        })
651        .and_then(|s| {
652            s.parse()
653                .map_err(|_| ParserError::InvalidFieldFormat(line, field))
654        })
655}
656
657fn parse_str_opt<'a>(
658    it: &mut impl Iterator<Item = &'a [u8]>,
659    line: usize,
660    field: &'static str,
661) -> Result<Option<String>, ParserError> {
662    it.next()
663        .map(|b| {
664            std::str::from_utf8(b)
665                .map(String::from)
666                .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
667        })
668        .transpose()
669}
670
671// Line parser helper for converting a byte slice to a string
672fn str_from_utf8(line: usize, s: &[u8], field: &'static str) -> Result<String, ParserError> {
673    std::str::from_utf8(s)
674        .map(String::from)
675        .map_err(|_| ParserError::InvalidFieldEncoding(line, field))
676}
677
678struct Line<'item> {
679    /// The 1-based line number.
680    n: usize,
681
682    key: u8,
683
684    value: &'item [u8],
685}
686
687// Parsing helper iterators below
688struct LineParser<'item, I: Iterator<Item = (usize, &'item [u8])>>(I);
689
690impl<'item, I: Iterator<Item = (usize, &'item [u8])>> FallibleIterator for LineParser<'item, I> {
691    type Item = Line<'item>;
692    type Error = ParserError;
693
694    fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
695        for (n, line) in &mut self.0 {
696            if line.is_empty() {
697                continue;
698            }
699            let equals = line.iter().position(|b| *b == b'=');
700            let key = match equals {
701                None => {
702                    return Err(ParserError::InvalidLineFormat(
703                        n,
704                        "Line not in key=value format",
705                    ))
706                }
707                Some(1) => line[0],
708                _ => {
709                    return Err(ParserError::InvalidLineFormat(
710                        n,
711                        "Line key not 1 character",
712                    ))
713                }
714            };
715            return Ok(Some(Line {
716                n,
717                key,
718                value: &line[2..],
719            }));
720        }
721        Ok(None)
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    #[test]
730    fn parse_sdp() {
731        let sdp = b"v=0\r
732o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
733s=SDP Seminar\r
734i=A Seminar on the session description protocol\r
735u=http://www.example.com/seminars/sdp.pdf\r
736e=j.doe@example.com (Jane Doe)\r
737p=+1 617 555-6011\r
738c=IN IP4 224.2.17.12/127\r
739b=AS:128\r
740t=2873397496 2873404696\r
741r=7d 1h 0 25h\r
742z=2882844526 -1h 2898848070 0\r
743k=clear:1234\r
744a=recvonly\r
745m=audio 49170 RTP/AVP 0\r
746m=video 51372/2 RTP/AVP 99\r
747a=rtpmap:99 h263-1998/90000\r
748a=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
749";
750        let parsed = Session::parse(&sdp[..]).unwrap();
751        let expected = Session {
752            origin: Origin {
753                username: Some("jdoe".into()),
754                sess_id: "2890844526".into(),
755                sess_version: 2890842807,
756                nettype: "IN".into(),
757                addrtype: "IP4".into(),
758                unicast_address: "10.47.16.5".into(),
759            },
760            session_name: "SDP Seminar".into(),
761            session_description: Some("A Seminar on the session description protocol".into()),
762            uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
763            emails: vec!["j.doe@example.com (Jane Doe)".into()],
764            phones: vec!["+1 617 555-6011".into()],
765            connection: Some(Connection {
766                nettype: "IN".into(),
767                addrtype: "IP4".into(),
768                connection_address: "224.2.17.12/127".into(),
769            }),
770            bandwidths: vec![Bandwidth {
771                bwtype: "AS".into(),
772                bandwidth: 128,
773            }],
774            times: vec![Time {
775                start_time: 2873397496,
776                stop_time: 2873404696,
777                repeats: vec![Repeat {
778                    repeat_interval: 604800,
779                    active_duration: 3600,
780                    offsets: vec![0, 90000],
781                }],
782            }],
783            time_zones: vec![
784                TimeZone {
785                    adjustment_time: 2882844526,
786                    offset: -3600,
787                },
788                TimeZone {
789                    adjustment_time: 2898848070,
790                    offset: 0,
791                },
792            ],
793            key: Some(Key {
794                method: "clear".into(),
795                encryption_key: Some("1234".into()),
796            }),
797            attributes: vec![Attribute {
798                attribute: "recvonly".into(),
799                value: None,
800            }],
801            medias: vec![
802                Media {
803                    media: "audio".into(),
804                    port: 49170,
805                    num_ports: None,
806                    proto: "RTP/AVP".into(),
807                    fmt: "0".into(),
808                    media_title: None,
809                    connections: vec![],
810                    bandwidths: vec![],
811                    key: None,
812                    attributes: vec![],
813                },
814                Media {
815                    media: "video".into(),
816                    port: 51372,
817                    num_ports: Some(2),
818                    proto: "RTP/AVP".into(),
819                    fmt: "99".into(),
820                    media_title: None,
821                    connections: vec![],
822                    bandwidths: vec![],
823                    key: None,
824                    attributes: vec![
825                        Attribute {
826                            attribute: "rtpmap".into(),
827                            value: Some("99 h263-1998/90000".into()),
828                        },
829                        Attribute {
830                            attribute: "fingerprint".into(),
831                            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()),
832                        }
833                    ],
834                },
835            ],
836        };
837
838        assert_eq!(parsed, expected);
839    }
840
841    #[test]
842    fn parse_only_key() {
843        Session::parse(b"v\n").unwrap_err();
844    }
845
846    #[test]
847    fn parse_sdp_real_camera() {
848        let sdp = b"v=0\r
849o=VSTC 3828747520 3828747520 IN IP4 192.168.1.165\r
850s=streamed by the VSTARCAM RTSP server\r
851e=NONE\r
852c=IN IP4 0.0.0.0\r
853t=0 0\r
854m=video 0 RTP/AVP 96\r
855b=AS:1024\r
856a=control:track0\r
857a=rtpmap:96 H264/90000\r
858a=fmtp:96 packetization-mode=1;profile-level-id=4d001f;sprop-parameter-sets=Z00AH52oFAFum4CAgKAAAAMAIAAAAwHwgA==,aO48gA==\r
859m=audio 0 RTP/AVP 8	 \r
860b=AS:64\r
861a=control:track1\r
862a=rtpmap:8 PCMA/8000/1\r
863
864";
865        let _parsed = Session::parse(&sdp[..]).unwrap();
866    }
867
868    /// Parses SDP from a Geovision camera which (incorrectly) omits the "t="
869    /// line.
870    #[test]
871    fn parse_sdp_geovision() {
872        let sdp = b"v=0\r
873o=- 1001 1 IN IP4 192.168.5.237\r
874s=VCP IPC Realtime stream\r
875m=video 0 RTP/AVP 105\r
876c=IN IP4 192.168.5.237\r
877a=control:rtsp://192.168.5.237/media/video1/video\r
878a=rtpmap:105 H264/90000\r
879a=fmtp:105 profile-level-id=4d4032; packetization-mode=1; sprop-parameter-sets=Z01AMpWgCoAwfiZuAgICgAAB9AAAdTBC,aO48gA==\r
880a=recvonly\r
881m=application 0 RTP/AVP 107\r
882c=IN IP4 192.168.5.237\r
883a=control:rtsp://192.168.5.237/media/video1/metadata\r
884a=rtpmap:107 vnd.onvif.metadata/90000\r
885a=fmtp:107 DecoderTag=h3c-v3 RTCP=0\r
886a=recvonly\r
887";
888        let _parsed = Session::parse(&sdp[..]).unwrap();
889    }
890
891    /// Parses SDP from an Anpviz camera which (incorrectly) places an `a=`
892    /// between the `c=` and `t=` lines of a session.
893    #[test]
894    fn parse_sdp_anpviz() {
895        let sdp = b"v=0\r
896o=- 1109162014219182 1109162014219192 IN IP4 x.y.z.w\r
897s=RTSP/RTP stream from anjvision ipcamera\r
898e=NONE\r
899c=IN IP4 0.0.0.0\r
900a=tool:LIVE555 Streaming Media v2011.05.25 CHAM.LI@ANJVISION.COM\r
901t=0 0\r
902a=range:npt=0-\r
903a=control:*\r
904m=video 0 RTP/AVP 96\r
905a=rtpmap:96 H264/90000\r
906a=control:trackID=1\r
907a=fmtp:96 profile-level-id=4D401F;packetization-mode=0;sprop-parameter-sets=Z01AH5WgLASabAQ=,aO48gA==;config=00000001674d401f95a02c049a6c040000000168ee3c800000000106f02c0445c6f5000620ebc2f3f7639e48250bfcb561bb2b85dda6fe5f06cc8b887b6a915f5aa3bebfffffffffff7380\r
908a=x-dimensions: 704, 576\r
909a=x-framerate: 12\r
910m=audio 0 RTP/AVP 0\r
911a=rtpmap:0 MPEG4-GENERIC/16000/2\r
912a=fmtp:0 config=1408\r
913a=control:trackID=2\r
914a=Media_header:MEDIAINFO=494D4B48010100000400010010710110401F000000FA000000000000000000000000000000000000;\r
915a=appversion:1.0\r
916";
917        let _parsed = Session::parse(&sdp[..]).unwrap();
918    }
919
920    #[test]
921    fn parse_overflowing_time() {
922        assert_eq!(
923            Session::parse(b"v=0\no=  0  =\x00 \ns=q\nt=0 5\nz=00 666666000079866660m "),
924            Err(ParserError::InvalidFieldFormat(5, "TimeZone offset"))
925        );
926    }
927
928    #[test]
929    fn parse_sdp_without_origin() {
930        let sdp = b"v=0\r
931s=streamed by the macro-video rtsp server\r
932t=0 0\r
933a=control:*\r
934a=range:npt=0-\r
935a=x-qt-text-nam:streamed by the macro-video rtsp server\r
936c=IN IP4 0.0.0.0\r
937m=video 0 RTP/AVP 96\r
938b=AS:500\r
939a=rtpmap:96 H264/90000\r
940a=fmtp:96 profile-level-id=TeAo;packetization-mode=1;sprop-parameter-sets=J03gKI1oBQBboQAAAwABAAADACgPFCKg,KO4BNJI=\r
941a=control:track1\r
942";
943        let _parsed = Session::parse(&sdp[..]).unwrap();
944    }
945
946    #[test]
947    fn parse_sdp_data_after_media() {
948        let sdp = b"v=0\r
949o=- 1691154453 1 IN IP4 192.168.1.100\r
950i=Pagos\r
951a=type:broadcast\r
952s=RandD2\r
953m=video 15002 RTP/AVP 97\r
954a=range:npt=0-\r
955a=rtpmap:97 H264/90000\r
956a=fmtp:97 profile-level-id=4D4029; packetization-mode=1; sprop-parameter-sets=Z01AKZZUBQHsgA==,aO44gA==\r
957a=framerate:15.000\r
958a=control:rtsp://192.168.1.20/camera1.sdp\r
959c=IN IP4 0.0.0.0\r
960t=0 0\r
961";
962        let _parsed = Session::parse(&sdp[..]).unwrap();
963    }
964
965    #[test]
966    fn parse_sdp_without_session_name() {
967        let sdp = b"v=0\r
968o=- 1109162014219182 1109162014219192 IN IP4 x.y.z.w\r
969t=0 0\r
970a=control:*\r
971a=range:npt=0-\r
972a=x-qt-text-nam:streamed by the macro-video rtsp server\r
973c=IN IP4 0.0.0.0\r
974m=video 0 RTP/AVP 96\r
975b=AS:500\r
976a=rtpmap:96 H264/90000\r
977a=fmtp:96 profile-level-id=TeAo;packetization-mode=1;sprop-parameter-sets=J03gKI1oBQBboQAAAwABAAADACgPFCKg,KO4BNJI=\r
978a=control:track1\r
979";
980        let _parsed = Session::parse(&sdp[..]).unwrap();
981    }
982
983    #[test]
984    fn parse_sdp_with_trailing_spaces() {
985        // Obtained from a camera integrated into Elegoo Saturn 4 Ultra
986        // Notice the trailing space in "t" field
987        let sdp = b"v=0\r
988o=- 4922720 1 IN IP4 10.0.0.108\r
989t=0 0 \r
990a=control:*\r
991m=video 0 RTP/AVP 96\r
992a=rtpmap:96 H264/9000\r
993a=control:track0\r
994";
995        let _parsed = Session::parse(&sdp[..]).unwrap();
996    }
997}