use super::*;
#[derive(Debug, PartialEq, Eq)]
pub enum ParserError {
UnexpectedLine(usize, u8),
InvalidLineFormat(usize, &'static str),
InvalidFieldEncoding(usize, &'static str),
InvalidFieldFormat(usize, &'static str),
MissingField(usize, &'static str),
FieldTrailingData(usize, &'static str),
InvalidVersion(usize, Vec<u8>),
MultipleVersions(usize),
NoVersion,
MultipleOrigins(usize),
NoOrigin,
MultipleSessionNames(usize),
NoSessionName,
MultipleSessionDescription(usize),
MultipleUris(usize),
MultipleConnections(usize),
MultipleTimeZones(usize),
MultipleMediaTitles(usize),
MultipleKeys(usize),
}
impl std::error::Error for ParserError {}
impl std::fmt::Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
use std::convert::TryFrom;
match *self {
ParserError::UnexpectedLine(line, c) => {
if let Ok(c) = char::try_from(c as u32) {
write!(f, "Unexpected line {} starting with '{}'", line, c)
} else {
write!(f, "Unexpected line {} starting with U+{:04x}", line, c)
}
}
ParserError::InvalidLineFormat(line, ref s) => {
write!(f, "Invalid formatted line {}: \"{}\"", line, s)
}
ParserError::InvalidFieldEncoding(line, s) => {
write!(f, "Invalid field encoding in line {} at {}", line, s)
}
ParserError::InvalidFieldFormat(line, s) => {
write!(f, "Invalid field formatting in line {} at {}", line, s)
}
ParserError::MissingField(line, s) => write!(f, "Missing field {} in line {}", s, line),
ParserError::FieldTrailingData(line, s) => {
write!(f, "Field {} in line {} has trailing data", s, line)
}
ParserError::InvalidVersion(line, ref s) => write!(
f,
"Invalid version at line {}: {}",
line,
String::from_utf8_lossy(&*s)
),
ParserError::MultipleVersions(line) => write!(f, "Multiple versions in line {}", line),
ParserError::NoVersion => write!(f, "No version line"),
ParserError::MultipleOrigins(line) => write!(f, "Multiple origins in line {}", line),
ParserError::NoOrigin => write!(f, "No origin line"),
ParserError::MultipleSessionNames(line) => {
write!(f, "Multiple session-names in line {}", line)
}
ParserError::NoSessionName => write!(f, "No session-name line"),
ParserError::MultipleSessionDescription(line) => {
write!(f, "Multiple session-information in line {}", line)
}
ParserError::MultipleUris(line) => write!(f, "Multiple URIs in line {}", line),
ParserError::MultipleConnections(line) => {
write!(f, "Multiple connections in line {}", line)
}
ParserError::MultipleTimeZones(line) => write!(f, "Multiple zones in line {}", line),
ParserError::MultipleMediaTitles(line) => {
write!(f, "Multiple media titles in line {}", line)
}
ParserError::MultipleKeys(line) => write!(f, "Multiple keys in line {}", line),
}
}
}
impl Origin {
fn parse((line, data): (usize, &[u8])) -> Result<Origin, ParserError> {
let mut origin = data.splitn_str(6, b" ");
let username = parse_str(&mut origin, line, "Origin username")?;
let sess_id = parse_str(&mut origin, line, "Origin sess-id")?;
let sess_version = parse_str_u64(&mut origin, line, "Origin sess-version")?;
let nettype = parse_str(&mut origin, line, "Origin nettype")?;
let addrtype = parse_str(&mut origin, line, "Origin addrtype")?;
let unicast_address = parse_str(&mut origin, line, "Origin unicast-address")?;
Ok(Origin {
username: if username == "-" {
None
} else {
Some(username)
},
sess_id,
sess_version,
nettype,
addrtype,
unicast_address,
})
}
}
impl Connection {
fn parse((line, data): (usize, &[u8])) -> Result<Connection, ParserError> {
let mut connection = data.splitn_str(3, b" ");
let nettype = parse_str(&mut connection, line, "Connection nettype")?;
let addrtype = parse_str(&mut connection, line, "Connection addrtype")?;
let connection_address = parse_str(&mut connection, line, "Connection connection-address")?;
Ok(Connection {
nettype,
addrtype,
connection_address,
})
}
}
impl Bandwidth {
fn parse((line, data): (usize, &[u8])) -> Result<Bandwidth, ParserError> {
let mut bandwidth = data.split_str(b":");
let bwtype = parse_str(&mut bandwidth, line, "Bandwidth bwtype")?;
let bw = parse_str_u64(&mut bandwidth, line, "Bandwidth bandwidth")?;
if bandwidth.next().is_some() {
return Err(ParserError::FieldTrailingData(line, "Bandwidth"));
}
Ok(Bandwidth {
bwtype,
bandwidth: bw,
})
}
}
impl Time {
fn parse((line, data): (usize, &[u8])) -> Result<Time, ParserError> {
let mut time = data.split_str(b" ");
let start_time = parse_str_u64(&mut time, line, "Time start-time")?;
let stop_time = parse_str_u64(&mut time, line, "Time stop-time")?;
if time.next().is_some() {
return Err(ParserError::FieldTrailingData(line, "Time"));
}
Ok(Time {
start_time,
stop_time,
repeats: Vec::new(),
})
}
}
fn parse_typed_time(s: &[u8], line: usize, field: &'static str) -> Result<u64, ParserError> {
let (num, factor) = match s
.split_last()
.ok_or_else(|| ParserError::InvalidFieldFormat(line, field))?
{
(b'd', prefix) => (prefix, 86_400),
(b'h', prefix) => (prefix, 3_600),
(b'm', prefix) => (prefix, 60),
(b's', prefix) => (prefix, 1),
(_, _) => (s, 1),
};
let num =
std::str::from_utf8(num).map_err(|_| ParserError::InvalidFieldEncoding(line, field))?;
let num = num
.parse::<u64>()
.map_err(|_| ParserError::InvalidFieldFormat(line, field))?;
Ok(factor * num)
}
impl Repeat {
fn parse((line, data): (usize, &[u8])) -> Result<Repeat, ParserError> {
let mut repeat = data.split_str(b" ");
let repeat_interval = repeat
.next()
.ok_or_else(|| ParserError::MissingField(line, "Repeat repeat-interval"))
.and_then(|s| parse_typed_time(s, line, "Repeat repeat-interval"))?;
let active_duration = repeat
.next()
.ok_or_else(|| ParserError::MissingField(line, "Repeat active-duration"))
.and_then(|s| parse_typed_time(s, line, "Repeat active-duration"))?;
let offsets = repeat
.map(|s| parse_typed_time(s, line, "Repeat active-duration"))
.collect::<Result<Vec<_>, _>>()?;
Ok(Repeat {
repeat_interval,
active_duration,
offsets,
})
}
}
impl TimeZone {
fn parse((line, data): (usize, &[u8])) -> Result<Vec<TimeZone>, ParserError> {
let mut zones = data.split_str(b" ");
let mut ret = Vec::new();
loop {
let adjustment_time = parse_str_u64(&mut zones, line, "TimeZone adjustment-time");
let adjustment_time = match adjustment_time {
Ok(adjustment_time) => adjustment_time,
Err(ParserError::MissingField(..)) => break,
Err(err) => return Err(err),
};
let offset = zones
.next()
.ok_or_else(|| ParserError::MissingField(line, "TimeZone offset"))
.and_then(|s| {
use std::convert::TryInto;
let (sign, s) = if s.get(0) == Some(&b'-') {
(true, &s[1..])
} else {
(false, s)
};
parse_typed_time(s, line, "TimeZone offset")
.and_then(|t| {
t.try_into().map_err(|_| {
ParserError::InvalidFieldFormat(line, "TimeZone offset")
})
})
.map(|t: i64| if sign { -t } else { t })
})?;
ret.push(TimeZone {
adjustment_time,
offset,
});
}
Ok(ret)
}
}
impl Attribute {
fn parse((line, data): (usize, &[u8])) -> Result<Attribute, ParserError> {
let mut attribute = data.splitn_str(2, b":");
let name = parse_str(&mut attribute, line, "Attribute name")?;
let value = parse_str_opt(&mut attribute, line, "Attribute value")?;
Ok(Attribute {
attribute: name,
value,
})
}
}
impl Key {
fn parse((line, data): (usize, &[u8])) -> Result<Key, ParserError> {
let mut key = data.splitn_str(2, b":");
let method = parse_str(&mut key, line, "Key method")?;
let encryption_key = parse_str_opt(&mut key, line, "Key encryption-key")?;
Ok(Key {
method,
encryption_key,
})
}
}
impl Media {
fn parse_m_line((line, data): (usize, &[u8])) -> Result<Media, ParserError> {
let mut media = data.splitn_str(4, b" ");
let name = parse_str(&mut media, line, "Media name")?;
let (port, num_ports) = media
.next()
.ok_or_else(|| ParserError::MissingField(line, "Media port"))
.and_then(|s| str_from_utf8((line, s), "Media Port"))
.and_then(|port| {
let mut split = port.splitn(2, '/');
let port = split
.next()
.ok_or_else(|| ParserError::MissingField(line, "Media port"))
.and_then(|port| {
port.parse()
.map_err(|_| ParserError::InvalidFieldFormat(line, "Media port"))
})?;
let num_ports = split
.next()
.ok_or_else(|| ParserError::MissingField(line, "Media num-ports"))
.and_then(|num_ports| {
num_ports
.parse()
.map_err(|_| ParserError::InvalidFieldFormat(line, "Media num-ports"))
});
match num_ports {
Ok(num_ports) => Ok((port, Some(num_ports))),
Err(ParserError::MissingField(..)) => Ok((port, None)),
Err(err) => Err(err),
}
})?;
let proto = parse_str(&mut media, line, "Media proto")?;
let fmt = parse_str(&mut media, line, "Media fmt")?;
Ok(Media {
media: name,
port,
num_ports,
proto,
fmt,
media_title: None,
connections: Vec::new(),
bandwidths: Vec::new(),
key: None,
attributes: Vec::new(),
})
}
fn parse<'a>(
lines: &mut std::iter::Peekable<impl Iterator<Item = (usize, &'a [u8])>>,
) -> Result<Option<Media>, ParserError> {
let media = match line_parser(lines, b'm', b"icbka")
.next()?
.map(Media::parse_m_line)
.transpose()?
{
None => return Ok(None),
Some(media) => media,
};
let media_title = line_parser_once(
lines,
b'i',
b"cbkam",
ParserError::MultipleMediaTitles,
|v| str_from_utf8(v, "Media Title"),
)?;
let connections = line_parser(lines, b'c', b"bkam")
.map(Connection::parse)
.collect::<Vec<_>>()?;
let bandwidths = line_parser(lines, b'b', b"kam")
.map(Bandwidth::parse)
.collect::<Vec<_>>()?;
let key = line_parser_once(lines, b'k', b"am", ParserError::MultipleKeys, Key::parse)?;
let attributes = line_parser(lines, b'a', b"m")
.map(Attribute::parse)
.collect::<Vec<_>>()?;
Ok(Some(Media {
media_title,
connections,
bandwidths,
key,
attributes,
..media
}))
}
}
impl Session {
pub fn parse(data: &[u8]) -> Result<Session, ParserError> {
let mut lines = data.lines().enumerate().peekable();
line_parser_once(
&mut lines,
b'v',
b"o",
ParserError::MultipleVersions,
|(line, version)| {
if version != b"0" {
Err(ParserError::InvalidVersion(line, version.into()))
} else {
Ok(())
}
},
)?
.ok_or_else(|| ParserError::NoVersion)?;
let origin = line_parser_once(
&mut lines,
b'o',
b"s",
ParserError::MultipleOrigins,
Origin::parse,
)?
.ok_or_else(|| ParserError::NoOrigin)?;
let session_name = line_parser_once(
&mut lines,
b's',
b"iuepcbt",
ParserError::MultipleSessionNames,
|v| str_from_utf8(v, "Session Name"),
)?
.ok_or_else(|| ParserError::NoSessionName)?;
let session_description = line_parser_once(
&mut lines,
b'i',
b"uepcbt",
ParserError::MultipleSessionDescription,
|v| str_from_utf8(v, "Session Description"),
)?;
let uri = line_parser_once(&mut lines, b'u', b"epcbt", ParserError::MultipleUris, |v| {
str_from_utf8(v, "Uri")
})?;
let emails = line_parser(&mut lines, b'e', b"pcbt")
.map(|v| str_from_utf8(v, "E-Mail"))
.collect::<Vec<_>>()?;
let phones = line_parser(&mut lines, b'p', b"cbt")
.map(|v| str_from_utf8(v, "Phone"))
.collect::<Vec<_>>()?;
let connection = line_parser_once(
&mut lines,
b'c',
b"bt",
ParserError::MultipleConnections,
Connection::parse,
)?;
let bandwidths = line_parser(&mut lines, b'b', b"t")
.map(Bandwidth::parse)
.collect::<Vec<_>>()?;
let mut times = vec![];
loop {
let time = match line_parser(&mut lines, b't', b"rzkam")
.next()?
.map(Time::parse)
.transpose()?
{
None => break,
Some(time) => time,
};
let repeats = line_parser(&mut lines, b'r', b"tzkam")
.map(Repeat::parse)
.collect::<Vec<_>>()?;
times.push(Time { repeats, ..time });
}
let time_zones = line_parser_once(
&mut lines,
b'z',
b"kam",
ParserError::MultipleTimeZones,
TimeZone::parse,
)?
.unwrap_or_else(Vec::new);
let key = line_parser_once(
&mut lines,
b'k',
b"am",
ParserError::MultipleKeys,
Key::parse,
)?;
let attributes = line_parser(&mut lines, b'a', b"m")
.map(Attribute::parse)
.collect::<Vec<_>>()?;
let mut medias = vec![];
while let Some(media) = Media::parse(&mut lines)? {
medias.push(media);
}
Ok(Session {
origin,
session_name,
session_description,
uri,
emails,
phones,
connection,
bandwidths,
times,
time_zones,
key,
attributes,
medias,
})
}
}
fn parse_str<'a>(
it: &mut impl Iterator<Item = &'a [u8]>,
line: usize,
field: &'static str,
) -> Result<String, ParserError> {
it.next()
.ok_or_else(|| ParserError::MissingField(line, field))
.and_then(|b| {
std::str::from_utf8(b)
.map(String::from)
.map_err(|_| ParserError::InvalidFieldEncoding(line, field))
})
}
fn parse_str_u64<'a>(
it: &mut impl Iterator<Item = &'a [u8]>,
line: usize,
field: &'static str,
) -> Result<u64, ParserError> {
it.next()
.ok_or_else(|| ParserError::MissingField(line, field))
.and_then(|b| {
std::str::from_utf8(b).map_err(|_| ParserError::InvalidFieldEncoding(line, field))
})
.and_then(|s| {
s.parse()
.map_err(|_| ParserError::InvalidFieldFormat(line, field))
})
}
fn parse_str_opt<'a>(
it: &mut impl Iterator<Item = &'a [u8]>,
line: usize,
field: &'static str,
) -> Result<Option<String>, ParserError> {
it.next()
.map(|b| {
std::str::from_utf8(b)
.map(String::from)
.map_err(|_| ParserError::InvalidFieldEncoding(line, field))
})
.transpose()
}
fn str_from_utf8((line, s): (usize, &[u8]), field: &'static str) -> Result<String, ParserError> {
std::str::from_utf8(s)
.map(String::from)
.map_err(|_| ParserError::InvalidFieldEncoding(line, field))
}
struct LineParser<'iter, 'item, I: Iterator<Item = (usize, &'item [u8])>> {
it: &'iter mut std::iter::Peekable<I>,
current: u8,
expected_next: &'static [u8],
}
struct LineParserOnce<
'iter,
'item,
I: Iterator<Item = (usize, &'item [u8])>,
F: Fn(usize) -> ParserError,
> {
it: LineParser<'iter, 'item, I>,
error_fn: F,
}
fn line_parser<'iter, 'item, I: Iterator<Item = (usize, &'item [u8])>>(
it: &'iter mut std::iter::Peekable<I>,
current: u8,
expected_next: &'static [u8],
) -> LineParser<'iter, 'item, I> {
LineParser {
it,
current,
expected_next,
}
}
fn line_parser_once<
'iter,
'item,
T,
I: Iterator<Item = (usize, &'item [u8])>,
F: Fn(usize) -> ParserError,
G: Fn((usize, &[u8])) -> Result<T, ParserError>,
>(
it: &'iter mut std::iter::Peekable<I>,
current: u8,
expected_next: &'static [u8],
error_fn: F,
func: G,
) -> Result<Option<T>, ParserError> {
LineParserOnce {
it: line_parser(it, current, expected_next),
error_fn,
}
.next()?
.map(func)
.transpose()
}
impl<'iter, 'item, I: Iterator<Item = (usize, &'item [u8])>> FallibleIterator
for LineParser<'iter, 'item, I>
{
type Item = (usize, &'item [u8]);
type Error = ParserError;
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
if let Some((n, line)) = self.it.peek() {
let mut key_value = line.splitn(2, |b| *b == b'=');
let key = key_value
.next()
.map(|key| {
if key.len() == 1 {
Ok(key[0])
} else {
Err(ParserError::InvalidLineFormat(
*n,
"Line key longer than 1 character",
))
}
})
.ok_or_else(|| {
ParserError::InvalidLineFormat(*n, "Line not in key=value format")
})??;
if key == self.current {
Ok(self.it.next().map(|(n, line)| (n, &line[2..])))
} else if self.expected_next.contains(&key) {
Ok(None)
} else {
Err(ParserError::UnexpectedLine(*n, key))
}
} else {
Ok(None)
}
}
}
impl<'iter, 'item, I: Iterator<Item = (usize, &'item [u8])>, F: Fn(usize) -> ParserError>
FallibleIterator for LineParserOnce<'iter, 'item, I, F>
{
type Item = (usize, &'item [u8]);
type Error = ParserError;
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
let item = self.it.next()?;
if let Some((line, _)) = self.it.next()? {
return Err((self.error_fn)(line));
}
Ok(item)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_sdp() {
let sdp = b"v=0\r
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
s=SDP Seminar\r
i=A Seminar on the session description protocol\r
u=http://www.example.com/seminars/sdp.pdf\r
e=j.doe@example.com (Jane Doe)\r
p=+1 617 555-6011\r
c=IN IP4 224.2.17.12/127\r
b=AS:128\r
t=2873397496 2873404696\r
r=7d 1h 0 25h\r
z=2882844526 -1h 2898848070 0\r
k=clear:1234\r
a=recvonly\r
m=audio 49170 RTP/AVP 0\r
m=video 51372/2 RTP/AVP 99\r
a=rtpmap:99 h263-1998/90000\r
a=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
";
let parsed = Session::parse(&sdp[..]).unwrap();
let expected = Session {
origin: Origin {
username: Some("jdoe".into()),
sess_id: "2890844526".into(),
sess_version: 2890842807,
nettype: "IN".into(),
addrtype: "IP4".into(),
unicast_address: "10.47.16.5".into(),
},
session_name: "SDP Seminar".into(),
session_description: Some("A Seminar on the session description protocol".into()),
uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
emails: vec!["j.doe@example.com (Jane Doe)".into()],
phones: vec!["+1 617 555-6011".into()],
connection: Some(Connection {
nettype: "IN".into(),
addrtype: "IP4".into(),
connection_address: "224.2.17.12/127".into(),
}),
bandwidths: vec![Bandwidth {
bwtype: "AS".into(),
bandwidth: 128,
}],
times: vec![Time {
start_time: 2873397496,
stop_time: 2873404696,
repeats: vec![Repeat {
repeat_interval: 604800,
active_duration: 3600,
offsets: vec![0, 90000],
}],
}],
time_zones: vec![
TimeZone {
adjustment_time: 2882844526,
offset: -3600,
},
TimeZone {
adjustment_time: 2898848070,
offset: 0,
},
],
key: Some(Key {
method: "clear".into(),
encryption_key: Some("1234".into()),
}),
attributes: vec![Attribute {
attribute: "recvonly".into(),
value: None,
}],
medias: vec![
Media {
media: "audio".into(),
port: 49170,
num_ports: None,
proto: "RTP/AVP".into(),
fmt: "0".into(),
media_title: None,
connections: vec![],
bandwidths: vec![],
key: None,
attributes: vec![],
},
Media {
media: "video".into(),
port: 51372,
num_ports: Some(2),
proto: "RTP/AVP".into(),
fmt: "99".into(),
media_title: None,
connections: vec![],
bandwidths: vec![],
key: None,
attributes: vec![
Attribute {
attribute: "rtpmap".into(),
value: Some("99 h263-1998/90000".into()),
},
Attribute {
attribute: "fingerprint".into(),
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()),
}
],
},
],
};
assert_eq!(parsed, expected);
}
}