1use super::*;
6
7#[derive(Debug, PartialEq, Eq)]
11pub enum ParserError {
12 #[deprecated(note = "This is no longer considered an error.")]
14 UnexpectedLine(usize, u8),
15 InvalidLineFormat(usize, &'static str),
17 InvalidFieldEncoding(usize, &'static str),
19 InvalidFieldFormat(usize, &'static str),
21 MissingField(usize, &'static str),
23 FieldTrailingData(usize, &'static str),
25 InvalidVersion(usize, Vec<u8>),
27 MultipleVersions(usize),
29 NoVersion,
31 MultipleOrigins(usize),
33 #[deprecated(note = "This is no longer considered an error.")]
35 NoOrigin,
36 MultipleSessionNames(usize),
38 #[deprecated(note = "This is no longer considered an error.")]
40 NoSessionName,
41 MultipleSessionDescription(usize),
43 MultipleUris(usize),
45 MultipleConnections(usize),
47 MultipleTimeZones(usize),
49 MultipleMediaTitles(usize),
51 MultipleKeys(usize),
53 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 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 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 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 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 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 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 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 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 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 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 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 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 b'c' => connections.push(Connection::parse(&line)?),
424
425 b'b' => bandwidths.push(Bandwidth::parse(&line)?),
428
429 b'k' => parse_rejecting_duplicates(
432 &mut key,
433 &line,
434 ParserError::MultipleKeys,
435 Key::parse,
436 )?,
437
438 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 pub fn parse(data: &[u8]) -> Result<Session, ParserError> {
460 let mut lines =
463 LineParser(data.lines().enumerate().map(|(i, bytes)| (i + 1, bytes))).peekable();
464
465 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 b'o' => parse_rejecting_duplicates(
507 &mut origin,
508 &line,
509 ParserError::MultipleOrigins,
510 Origin::parse,
511 )?,
512
513 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 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 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 b'e' => emails.push(str_from_utf8(line.n, line.value, "E-Mail")?),
542
543 b'p' => phones.push(str_from_utf8(line.n, line.value, "Phone")?),
546
547 b'c' => parse_rejecting_duplicates(
550 &mut connection,
551 &line,
552 ParserError::MultipleConnections,
553 Connection::parse,
554 )?,
555
556 b'b' => bandwidths.push(Bandwidth::parse(&line)?),
559
560 b't' => times.push(Time::parse(&line)?),
563
564 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 b'z' => parse_rejecting_duplicates(
577 &mut time_zones,
578 &line,
579 ParserError::MultipleTimeZones,
580 TimeZone::parse,
581 )?,
582
583 b'k' => parse_rejecting_duplicates(
586 &mut key,
587 &line,
588 ParserError::MultipleKeys,
589 Key::parse,
590 )?,
591
592 b'a' => attributes.push(Attribute::parse(&line)?),
595
596 _ => (),
597 }
598 }
599
600 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 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
650fn 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
681pub(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
701fn 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
739fn 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 n: usize,
749
750 key: u8,
751
752 value: &'item [u8],
753}
754
755fn 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
771struct 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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 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}