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}
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 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 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 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 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 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 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 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 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 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 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 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 b'c' => connections.push(Connection::parse(&line)?),
401
402 b'b' => bandwidths.push(Bandwidth::parse(&line)?),
405
406 b'k' => parse_rejecting_duplicates(
409 &mut key,
410 &line,
411 ParserError::MultipleKeys,
412 Key::parse,
413 )?,
414
415 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 pub fn parse(data: &[u8]) -> Result<Session, ParserError> {
437 let mut lines =
440 LineParser(data.lines().enumerate().map(|(i, bytes)| (i + 1, bytes))).peekable();
441
442 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 b'o' => parse_rejecting_duplicates(
481 &mut origin,
482 &line,
483 ParserError::MultipleOrigins,
484 Origin::parse,
485 )?,
486
487 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 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 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 b'e' => emails.push(str_from_utf8(line.n, line.value, "E-Mail")?),
516
517 b'p' => phones.push(str_from_utf8(line.n, line.value, "Phone")?),
520
521 b'c' => parse_rejecting_duplicates(
524 &mut connection,
525 &line,
526 ParserError::MultipleConnections,
527 Connection::parse,
528 )?,
529
530 b'b' => bandwidths.push(Bandwidth::parse(&line)?),
533
534 b't' => times.push(Time::parse(&line)?),
537
538 b'r' => {
541 if let Some(t) = times.last_mut() {
542 t.repeats.push(Repeat::parse(&line)?);
543 }
544 }
545
546 b'z' => parse_rejecting_duplicates(
549 &mut time_zones,
550 &line,
551 ParserError::MultipleTimeZones,
552 TimeZone::parse,
553 )?,
554
555 b'k' => parse_rejecting_duplicates(
558 &mut key,
559 &line,
560 ParserError::MultipleKeys,
561 Key::parse,
562 )?,
563
564 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 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
626fn 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
671fn 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 n: usize,
681
682 key: u8,
683
684 value: &'item [u8],
685}
686
687struct 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 #[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 #[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 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}