use bstr::*;
use fallible_iterator::FallibleIterator;
mod parser;
mod writer;
pub use parser::ParserError;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Origin {
pub username: Option<String>,
pub sess_id: String,
pub sess_version: u64,
pub nettype: String,
pub addrtype: String,
pub unicast_address: String,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Connection {
pub nettype: String,
pub addrtype: String,
pub connection_address: String,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Bandwidth {
pub bwtype: String,
pub bandwidth: u64,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Time {
pub start_time: u64,
pub stop_time: u64,
pub repeats: Vec<Repeat>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Repeat {
pub repeat_interval: u64,
pub active_duration: u64,
pub offsets: Vec<u64>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TimeZone {
pub adjustment_time: u64,
pub offset: i64,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Key {
pub method: String,
pub encryption_key: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Attribute {
pub attribute: String,
pub value: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Media {
pub media: String,
pub port: u16,
pub num_ports: Option<u16>,
pub proto: String,
pub fmt: String,
pub media_title: Option<String>,
pub connections: Vec<Connection>,
pub bandwidths: Vec<Bandwidth>,
pub key: Option<Key>,
pub attributes: Vec<Attribute>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Session {
pub origin: Origin,
pub session_name: String,
pub session_description: Option<String>,
pub uri: Option<String>,
pub emails: Vec<String>,
pub phones: Vec<String>,
pub connection: Option<Connection>,
pub bandwidths: Vec<Bandwidth>,
pub times: Vec<Time>,
pub time_zones: Vec<TimeZone>,
pub key: Option<Key>,
pub attributes: Vec<Attribute>,
pub medias: Vec<Media>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct AttributeNotFoundError;
impl std::error::Error for AttributeNotFoundError {}
impl std::fmt::Display for AttributeNotFoundError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "Attribute not found")
}
}
impl Media {
pub fn has_attribute(&self, name: &str) -> bool {
self.attributes.iter().any(|a| a.attribute == name)
}
pub fn get_first_attribute_value(
&self,
name: &str,
) -> Result<Option<&str>, AttributeNotFoundError> {
self.attributes
.iter()
.find(|a| a.attribute == name)
.ok_or_else(|| AttributeNotFoundError)
.map(|a| a.value.as_ref().map(String::as_str))
}
pub fn get_attribute_values<'a>(
&'a self,
name: &'a str,
) -> Result<impl Iterator<Item = Option<&'a str>> + 'a, AttributeNotFoundError> {
let mut iter = self
.attributes
.iter()
.filter(move |a| a.attribute == name)
.map(|a| a.value.as_ref().map(String::as_str))
.peekable();
if iter.peek().is_some() {
Ok(iter)
} else {
Err(AttributeNotFoundError)
}
}
}
impl Session {
pub fn has_attribute(&self, name: &str) -> bool {
self.attributes.iter().any(|a| a.attribute == name)
}
pub fn get_first_attribute_value(
&self,
name: &str,
) -> Result<Option<&str>, AttributeNotFoundError> {
self.attributes
.iter()
.find(|a| a.attribute == name)
.ok_or_else(|| AttributeNotFoundError)
.map(|a| a.value.as_ref().map(String::as_str))
}
pub fn get_attribute_values<'a>(
&'a self,
name: &'a str,
) -> Result<impl Iterator<Item = Option<&'a str>> + 'a, AttributeNotFoundError> {
let mut iter = self
.attributes
.iter()
.filter(move |a| a.attribute == name)
.map(|a| a.value.as_ref().map(String::as_str))
.peekable();
if iter.peek().is_some() {
Ok(iter)
} else {
Err(AttributeNotFoundError)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_write() {
let sdp = "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.as_bytes()).unwrap();
let mut written = vec![];
parsed.write(&mut written).unwrap();
assert_eq!(String::from_utf8_lossy(&written), &sdp[..]);
}
#[test]
fn parse_media_attributes() {
let media = Media {
media: "video".into(),
port: 51372,
num_ports: Some(2),
proto: "RTP/AVP".into(),
fmt: "99 100".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: "rtpmap".into(),
value: Some("100 h264/90000".into()),
},
Attribute {
attribute: "rtcp".into(),
value: None,
},
],
};
assert!(media.has_attribute("rtpmap"));
assert!(media.has_attribute("rtcp"));
assert!(!media.has_attribute("foo"));
assert_eq!(
media.get_first_attribute_value("rtpmap"),
Ok(Some("99 h263-1998/90000"))
);
assert_eq!(media.get_first_attribute_value("rtcp"), Ok(None));
assert_eq!(
media.get_first_attribute_value("foo"),
Err(AttributeNotFoundError)
);
assert_eq!(
media
.get_attribute_values("rtpmap")
.unwrap()
.collect::<Vec<_>>(),
&[
Some("99 h263-1998/90000".into()),
Some("100 h264/90000".into())
]
);
assert_eq!(
media
.get_attribute_values("rtcp")
.unwrap()
.collect::<Vec<_>>(),
&[None]
);
assert!(media.get_attribute_values("foo").is_err());
}
#[test]
fn parse_session_attributes() {
let session = 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: None,
uri: None,
emails: vec![],
phones: vec![],
connection: None,
bandwidths: vec![],
times: vec![Time {
start_time: 0,
stop_time: 0,
repeats: vec![],
}],
time_zones: vec![],
key: None,
attributes: vec![
Attribute {
attribute: "rtpmap".into(),
value: Some("99 h263-1998/90000".into()),
},
Attribute {
attribute: "rtpmap".into(),
value: Some("100 h264/90000".into()),
},
Attribute {
attribute: "rtcp".into(),
value: None,
},
],
medias: vec![],
};
assert!(session.has_attribute("rtpmap"));
assert!(session.has_attribute("rtcp"));
assert!(!session.has_attribute("foo"));
assert_eq!(
session.get_first_attribute_value("rtpmap"),
Ok(Some("99 h263-1998/90000"))
);
assert_eq!(session.get_first_attribute_value("rtcp"), Ok(None));
assert_eq!(
session.get_first_attribute_value("foo"),
Err(AttributeNotFoundError)
);
assert_eq!(
session
.get_attribute_values("rtpmap")
.unwrap()
.collect::<Vec<_>>(),
&[
Some("99 h263-1998/90000".into()),
Some("100 h264/90000".into())
]
);
assert_eq!(
session
.get_attribute_values("rtcp")
.unwrap()
.collect::<Vec<_>>(),
&[None]
);
assert!(session.get_attribute_values("foo").is_err());
}
}