#![cfg_attr(feature = "clippy", feature(plugin))]
#[macro_use]
extern crate log;
#[cfg(feature = "serialize")]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "serialize")]
extern crate serde;
use std::net::IpAddr;
use std::str::FromStr;
#[macro_use]
pub mod attribute_type;
pub mod error;
pub mod media_type;
pub mod network;
pub mod unsupported_types;
use attribute_type::{
addr_to_string, parse_attribute, SdpAttribute, SdpAttributeRid, SdpAttributeSimulcastVersion,
SdpAttributeType, SdpSingleDirection,
};
use error::{SdpParserError, SdpParserInternalError};
use media_type::{
parse_media, parse_media_vector, SdpFormatList, SdpMedia, SdpMediaLine, SdpMediaValue,
SdpProtocolValue,
};
use network::{parse_addrtype, parse_nettype, parse_unicast_addr};
use unsupported_types::{
parse_email, parse_information, parse_key, parse_phone, parse_repeat, parse_uri, parse_zone,
};
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum SdpBandwidth {
As(u32),
Ct(u32),
Tias(u32),
Unknown(String, u32),
}
impl ToString for SdpBandwidth {
fn to_string(&self) -> String {
match *self {
SdpBandwidth::As(ref x) => format!("AS:{}", x.to_string()),
SdpBandwidth::Ct(ref x) => format!("CT:{}", x.to_string()),
SdpBandwidth::Tias(ref x) => format!("TIAS:{}", x.to_string()),
SdpBandwidth::Unknown(ref tp, ref x) => format!("{}:{}", tp.to_string(), x.to_string()),
}
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SdpConnection {
pub addr: IpAddr,
pub ttl: Option<u8>,
pub amount: Option<u32>,
}
impl ToString for SdpConnection {
fn to_string(&self) -> String {
format!(
"IN {addr}{ttl}{amount}",
addr = match self.addr {
IpAddr::V4(ipv4) => format!("IP4 {}", ipv4.to_string()),
IpAddr::V6(ipv6) => format!("IP6 {}", ipv6.to_string()),
},
ttl = option_to_string!("/{}", self.ttl),
amount = option_to_string!("/{}", self.amount)
)
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SdpOrigin {
pub username: String,
pub session_id: u64,
pub session_version: u64,
pub unicast_addr: IpAddr,
}
impl ToString for SdpOrigin {
fn to_string(&self) -> String {
format!(
"{username} {sess_id} {sess_vers} {unicast_addr}",
username = self.username.clone(),
sess_id = self.session_id.to_string(),
sess_vers = self.session_version.to_string(),
unicast_addr = addr_to_string(self.unicast_addr)
)
}
}
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SdpTiming {
pub start: u64,
pub stop: u64,
}
impl ToString for SdpTiming {
fn to_string(&self) -> String {
format!("{} {}", self.start.to_string(), self.stop.to_string())
}
}
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub enum SdpType {
Attribute(SdpAttribute),
Bandwidth(SdpBandwidth),
Connection(SdpConnection),
Email(String),
Information(String),
Key(String),
Media(SdpMediaLine),
Phone(String),
Origin(SdpOrigin),
Repeat(String),
Session(String),
Timing(SdpTiming),
Uri(String),
Version(u64),
Zone(String),
}
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SdpLine {
pub line_number: usize,
pub sdp_type: SdpType,
}
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct SdpSession {
pub version: u64,
pub origin: SdpOrigin,
pub session: String,
pub connection: Option<SdpConnection>,
pub bandwidth: Vec<SdpBandwidth>,
pub timing: Option<SdpTiming>,
pub attribute: Vec<SdpAttribute>,
pub media: Vec<SdpMedia>,
pub warnings: Vec<SdpParserError>,
}
impl SdpSession {
pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession {
SdpSession {
version,
origin,
session,
connection: None,
bandwidth: Vec::new(),
timing: None,
attribute: Vec::new(),
media: Vec::new(),
warnings: Vec::new(),
}
}
pub fn get_version(&self) -> u64 {
self.version
}
pub fn get_origin(&self) -> &SdpOrigin {
&self.origin
}
pub fn get_session(&self) -> &str {
&self.session
}
pub fn get_connection(&self) -> &Option<SdpConnection> {
&self.connection
}
pub fn set_connection(&mut self, c: &SdpConnection) {
self.connection = Some(c.clone())
}
pub fn add_bandwidth(&mut self, b: &SdpBandwidth) {
self.bandwidth.push(b.clone())
}
pub fn set_timing(&mut self, t: &SdpTiming) {
self.timing = Some(t.clone())
}
pub fn add_attribute(&mut self, a: &SdpAttribute) -> Result<(), SdpParserInternalError> {
if !a.allowed_at_session_level() {
return Err(SdpParserInternalError::Generic(format!(
"{} not allowed at session level",
SdpAttributeType::from(a).to_string()
)));
};
self.attribute.push(a.clone());
Ok(())
}
pub fn extend_media(&mut self, v: Vec<SdpMedia>) {
self.media.extend(v)
}
pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> {
self.attribute
.iter()
.filter(|a| SdpAttributeType::from(*a) == t)
.next()
}
pub fn add_media(
&mut self,
media_type: SdpMediaValue,
direction: SdpAttribute,
port: u32,
protocol: SdpProtocolValue,
addr: String,
) -> Result<(), SdpParserInternalError> {
let mut media = SdpMedia::new(SdpMediaLine {
media: media_type,
port,
port_count: 1,
proto: protocol,
formats: SdpFormatList::Integers(Vec::new()),
});
media.add_attribute(&direction)?;
media.set_connection(&SdpConnection {
addr: IpAddr::from_str(addr.as_str())?,
ttl: None,
amount: None,
})?;
self.media.push(media);
Ok(())
}
}
impl ToString for SdpSession {
fn to_string(&self) -> String {
format!(
"v={version}\r\n\
o={origin}\r\n\
s={sess}\r\n\
{timing}\
{bandwidth}\
{connection}\
{sess_attributes}\
{media_sections}",
version = self.version.to_string(),
origin = self.origin.to_string(),
sess = self.session.clone(),
timing = option_to_string!("t={}\r\n", self.timing),
bandwidth = maybe_vector_to_string!("b={}\r\n", self.bandwidth, "\r\nb="),
connection = option_to_string!("c={}\r\n", self.connection),
sess_attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na="),
media_sections = maybe_vector_to_string!("{}", self.media, "\r\n")
)
}
}
fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> {
trace!("session: {}", value);
Ok(SdpType::Session(String::from(value)))
}
#[test]
fn test_session_works() {
assert!(parse_session("topic").is_ok());
}
fn parse_version(value: &str) -> Result<SdpType, SdpParserInternalError> {
let ver = value.parse::<u64>()?;
if ver != 0 {
return Err(SdpParserInternalError::Generic(format!(
"version type contains unsupported value {}",
ver
)));
};
trace!("version: {}", ver);
Ok(SdpType::Version(ver))
}
#[test]
fn test_version_works() {
assert!(parse_version("0").is_ok());
}
#[test]
fn test_version_unsupported_input() {
assert!(parse_version("1").is_err());
assert!(parse_version("11").is_err());
assert!(parse_version("a").is_err());
}
fn parse_origin(value: &str) -> Result<SdpType, SdpParserInternalError> {
let mut tokens = value.split_whitespace();
let username = match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing username token".to_string(),
));
}
Some(x) => x,
};
let session_id = match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing session ID token".to_string(),
));
}
Some(x) => x.parse::<u64>()?,
};
let session_version = match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing session version token".to_string(),
));
}
Some(x) => x.parse::<u64>()?,
};
match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing session version token".to_string(),
));
}
Some(x) => parse_nettype(x)?,
};
let addrtype = match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing address type token".to_string(),
));
}
Some(x) => parse_addrtype(x)?,
};
let unicast_addr = match tokens.next() {
None => {
return Err(SdpParserInternalError::Generic(
"Origin type is missing IP address token".to_string(),
));
}
Some(x) => parse_unicast_addr(x)?,
};
if !addrtype.same_protocol(&unicast_addr) {
return Err(SdpParserInternalError::Generic(
"Origin addrtype does not match address.".to_string(),
));
}
let o = SdpOrigin {
username: String::from(username),
session_id,
session_version,
unicast_addr,
};
trace!("origin: {}", o.to_string());
Ok(SdpType::Origin(o))
}
#[test]
fn test_origin_works() {
assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0").is_ok());
assert!(parse_origin("mozilla 506705521068071134 0 IN IP6 ::1").is_ok());
}
#[test]
fn test_origin_wrong_amount_of_tokens() {
assert!(parse_origin("a b c d e").is_err());
assert!(parse_origin("a b c d e f g").is_err());
}
#[test]
fn test_origin_unsupported_nettype() {
assert!(parse_origin("mozilla 506705521068071134 0 UNSUPPORTED IP4 0.0.0.0").is_err());
}
#[test]
fn test_origin_unsupported_addrtpe() {
assert!(parse_origin("mozilla 506705521068071134 0 IN IP1 0.0.0.0").is_err());
}
#[test]
fn test_origin_broken_ip_addr() {
assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 1.1.1.256").is_err());
assert!(parse_origin("mozilla 506705521068071134 0 IN IP6 ::g").is_err());
}
#[test]
fn test_origin_addr_type_mismatch() {
assert!(parse_origin("mozilla 506705521068071134 0 IN IP4 ::1").is_err());
}
fn parse_connection(value: &str) -> Result<SdpType, SdpParserInternalError> {
let cv: Vec<&str> = value.split_whitespace().collect();
if cv.len() != 3 {
return Err(SdpParserInternalError::Generic(
"connection attribute must have three tokens".to_string(),
));
}
parse_nettype(cv[0])?;
let addrtype = parse_addrtype(cv[1])?;
let mut ttl = None;
let mut amount = None;
let mut addr_token = cv[2];
if addr_token.find('/') != None {
let addr_tokens: Vec<&str> = addr_token.split('/').collect();
if addr_tokens.len() >= 3 {
amount = Some(addr_tokens[2].parse::<u32>()?);
}
ttl = Some(addr_tokens[1].parse::<u8>()?);
addr_token = addr_tokens[0];
}
let addr = parse_unicast_addr(addr_token)?;
if !addrtype.same_protocol(&addr) {
return Err(SdpParserInternalError::Generic(
"connection addrtype does not match address.".to_string(),
));
}
let c = SdpConnection { addr, ttl, amount };
trace!("connection: {}", c.addr);
Ok(SdpType::Connection(c))
}
#[test]
fn connection_works() {
assert!(parse_connection("IN IP4 127.0.0.1").is_ok());
assert!(parse_connection("IN IP4 127.0.0.1/10/10").is_ok());
}
#[test]
fn connection_lots_of_whitespace() {
assert!(parse_connection("IN IP4 127.0.0.1").is_ok());
}
#[test]
fn connection_wrong_amount_of_tokens() {
assert!(parse_connection("IN IP4").is_err());
assert!(parse_connection("IN IP4 0.0.0.0 foobar").is_err());
}
#[test]
fn connection_unsupported_nettype() {
assert!(parse_connection("UNSUPPORTED IP4 0.0.0.0").is_err());
}
#[test]
fn connection_unsupported_addrtpe() {
assert!(parse_connection("IN IP1 0.0.0.0").is_err());
}
#[test]
fn connection_broken_ip_addr() {
assert!(parse_connection("IN IP4 1.1.1.256").is_err());
assert!(parse_connection("IN IP6 ::g").is_err());
}
#[test]
fn connection_addr_type_mismatch() {
assert!(parse_connection("IN IP4 ::1").is_err());
}
fn parse_bandwidth(value: &str) -> Result<SdpType, SdpParserInternalError> {
let bv: Vec<&str> = value.split(':').collect();
if bv.len() != 2 {
return Err(SdpParserInternalError::Generic(
"bandwidth attribute must have two tokens".to_string(),
));
}
let bandwidth = bv[1].parse::<u32>()?;
let bw = match bv[0].to_uppercase().as_ref() {
"AS" => SdpBandwidth::As(bandwidth),
"CT" => SdpBandwidth::Ct(bandwidth),
"TIAS" => SdpBandwidth::Tias(bandwidth),
_ => SdpBandwidth::Unknown(String::from(bv[0]), bandwidth),
};
trace!("bandwidth: {}, {}", bv[0], bandwidth);
Ok(SdpType::Bandwidth(bw))
}
#[test]
fn bandwidth_works() {
assert!(parse_bandwidth("AS:1").is_ok());
assert!(parse_bandwidth("CT:123").is_ok());
assert!(parse_bandwidth("TIAS:12345").is_ok());
}
#[test]
fn bandwidth_wrong_amount_of_tokens() {
assert!(parse_bandwidth("TIAS").is_err());
assert!(parse_bandwidth("TIAS:12345:xyz").is_err());
}
#[test]
fn bandwidth_unsupported_type() {
assert!(parse_bandwidth("UNSUPPORTED:12345").is_ok());
}
fn parse_timing(value: &str) -> Result<SdpType, SdpParserInternalError> {
let tv: Vec<&str> = value.split_whitespace().collect();
if tv.len() != 2 {
return Err(SdpParserInternalError::Generic(
"timing attribute must have two tokens".to_string(),
));
}
let start = tv[0].parse::<u64>()?;
let stop = tv[1].parse::<u64>()?;
let t = SdpTiming { start, stop };
trace!("timing: {}, {}", t.start, t.stop);
Ok(SdpType::Timing(t))
}
#[test]
fn test_timing_works() {
assert!(parse_timing("0 0").is_ok());
}
#[test]
fn test_timing_non_numeric_tokens() {
assert!(parse_timing("a 0").is_err());
assert!(parse_timing("0 a").is_err());
}
#[test]
fn test_timing_wrong_amount_of_tokens() {
assert!(parse_timing("0").is_err());
assert!(parse_timing("0 0 0").is_err());
}
fn parse_sdp_line(line: &str, line_number: usize) -> Result<SdpLine, SdpParserError> {
if line.find('=') == None {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("missing = character in line".to_string()),
line: line.to_string(),
line_number,
});
}
let mut splitted_line = line.splitn(2, '=');
let line_type = match splitted_line.next() {
None => {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("missing type".to_string()),
line: line.to_string(),
line_number,
});
}
Some(t) => {
let trimmed = t.trim();
if trimmed.len() > 1 {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("type too long".to_string()),
line: line.to_string(),
line_number,
});
}
if trimmed.is_empty() {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("type is empty".to_string()),
line: line.to_string(),
line_number,
});
}
trimmed
}
};
let line_value = match splitted_line.next() {
None => {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("missing value".to_string()),
line: line.to_string(),
line_number,
});
}
Some(v) => {
let trimmed = v.trim();
if trimmed.is_empty() {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("value is empty".to_string()),
line: line.to_string(),
line_number,
});
}
trimmed
}
};
match line_type.to_lowercase().as_ref() {
"a" => parse_attribute(line_value),
"b" => parse_bandwidth(line_value),
"c" => parse_connection(line_value),
"e" => parse_email(line_value),
"i" => parse_information(line_value),
"k" => parse_key(line_value),
"m" => parse_media(line_value),
"o" => parse_origin(line_value),
"p" => parse_phone(line_value),
"r" => parse_repeat(line_value),
"s" => parse_session(line_value),
"t" => parse_timing(line_value),
"u" => parse_uri(line_value),
"v" => parse_version(line_value),
"z" => parse_zone(line_value),
_ => Err(SdpParserInternalError::Generic(
"unknown sdp type".to_string(),
)),
}
.map(|sdp_type| SdpLine {
line_number,
sdp_type,
})
.map_err(|e| match e {
SdpParserInternalError::Generic(..)
| SdpParserInternalError::Integer(..)
| SdpParserInternalError::Float(..)
| SdpParserInternalError::Address(..) => SdpParserError::Line {
error: e,
line: line.to_string(),
line_number,
},
SdpParserInternalError::Unsupported(..) => SdpParserError::Unsupported {
error: e,
line: line.to_string(),
line_number,
},
})
}
#[test]
fn test_parse_sdp_line_works() {
assert!(parse_sdp_line("v=0", 0).is_ok());
assert!(parse_sdp_line("s=somesession", 0).is_ok());
}
#[test]
fn test_parse_sdp_line_empty_line() {
assert!(parse_sdp_line("", 0).is_err());
}
#[test]
fn test_parse_sdp_line_unknown_key() {
assert!(parse_sdp_line("y=foobar", 0).is_err());
}
#[test]
fn test_parse_sdp_line_too_long_type() {
assert!(parse_sdp_line("ab=foobar", 0).is_err());
}
#[test]
fn test_parse_sdp_line_without_equal() {
assert!(parse_sdp_line("abcd", 0).is_err());
assert!(parse_sdp_line("ab cd", 0).is_err());
}
#[test]
fn test_parse_sdp_line_empty_value() {
assert!(parse_sdp_line("v=", 0).is_err());
assert!(parse_sdp_line("o=", 0).is_err());
assert!(parse_sdp_line("s=", 0).is_err());
}
#[test]
fn test_parse_sdp_line_empty_name() {
assert!(parse_sdp_line("=abc", 0).is_err());
}
#[test]
fn test_parse_sdp_line_valid_a_line() {
assert!(parse_sdp_line("a=rtpmap:8 PCMA/8000", 0).is_ok());
}
#[test]
fn test_parse_sdp_line_invalid_a_line() {
assert!(parse_sdp_line("a=rtpmap:200 PCMA/8000", 0).is_err());
}
fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> {
let make_error = |x: &str| SdpParserError::Sequence {
message: x.to_string(),
line_number: 0,
};
if session.timing.is_none() {
return Err(SdpParserError::Sequence {
message: "Missing timing type".to_string(),
line_number: 0,
});
}
if session.media.is_empty() {
return Err(SdpParserError::Sequence {
message: "Missing media section".to_string(),
line_number: 0,
});
}
if session.get_connection().is_none() {
for msection in &session.media {
if msection.get_connection().is_none() {
return Err(SdpParserError::Sequence {
message: "Each media section must define a connection
if it is not defined on session level"
.to_string(),
line_number: 0,
});
}
}
}
if session.get_attribute(SdpAttributeType::Extmap).is_some() {
for msection in &session.media {
if msection.get_attribute(SdpAttributeType::Extmap).is_some() {
return Err(SdpParserError::Sequence {
message: "Extmap can't be define at session and media level".to_string(),
line_number: 0,
});
}
}
}
for msection in &session.media {
if msection.get_attribute(SdpAttributeType::Sendonly).is_some() {
if let Some(&SdpAttribute::Simulcast(ref x)) =
msection.get_attribute(SdpAttributeType::Simulcast)
{
if !x.receive.is_empty() {
return Err(SdpParserError::Sequence {
message: "Simulcast can't define receive parameters for sendonly"
.to_string(),
line_number: 0,
});
}
}
}
if msection.get_attribute(SdpAttributeType::Recvonly).is_some() {
if let Some(&SdpAttribute::Simulcast(ref x)) =
msection.get_attribute(SdpAttributeType::Simulcast)
{
if !x.send.is_empty() {
return Err(SdpParserError::Sequence {
message: "Simulcast can't define send parameters for recvonly".to_string(),
line_number: 0,
});
}
}
}
let rids: Vec<&SdpAttributeRid> = msection
.get_attributes()
.iter()
.filter_map(|attr| match *attr {
SdpAttribute::Rid(ref rid) => Some(rid),
_ => None,
})
.collect();
let recv_rids: Vec<&str> = rids
.iter()
.filter_map(|rid| match rid.direction {
SdpSingleDirection::Recv => Some(rid.id.as_str()),
_ => None,
})
.collect();
let send_rids: Vec<&str> = rids
.iter()
.filter_map(|rid| match rid.direction {
SdpSingleDirection::Send => Some(rid.id.as_str()),
_ => None,
})
.collect();
for rid_format in rids.iter().flat_map(|rid| &rid.formats) {
match *msection.get_formats() {
SdpFormatList::Integers(ref int_fmt) => {
if !int_fmt.contains(&(u32::from(*rid_format))) {
return Err(make_error("Rid pts must be declared in the media section"));
}
}
SdpFormatList::Strings(ref str_fmt) => {
if !str_fmt.contains(&rid_format.to_string()) {
return Err(make_error("Rid pts must be declared in the media section"));
}
}
}
}
if let Some(&SdpAttribute::Simulcast(ref simulcast)) =
msection.get_attribute(SdpAttributeType::Simulcast)
{
let check_defined_rids =
|simulcast_version_list: &Vec<SdpAttributeSimulcastVersion>,
rid_ids: &[&str]|
-> Result<(), SdpParserError> {
for simulcast_rid in simulcast_version_list.iter().flat_map(|x| &x.ids) {
if !rid_ids.contains(&simulcast_rid.id.as_str()) {
return Err(make_error(
"Simulcast RIDs must be defined in any rid attribute",
));
}
}
Ok(())
};
check_defined_rids(&simulcast.receive, &recv_rids)?;
check_defined_rids(&simulcast.send, &send_rids)?;
}
}
Ok(())
}
#[cfg(test)]
fn create_dummy_sdp_session() -> SdpSession {
let origin = parse_origin("mozilla 506705521068071134 0 IN IP4 0.0.0.0");
assert!(origin.is_ok());
let connection = parse_connection("IN IP4 198.51.100.7");
assert!(connection.is_ok());
let mut sdp_session;
if let SdpType::Origin(o) = origin.unwrap() {
sdp_session = SdpSession::new(0, o, "-".to_string());
if let Ok(SdpType::Connection(c)) = connection {
sdp_session.connection = Some(c);
} else {
panic!("Sdp type is not Connection")
}
} else {
panic!("SdpType is not Origin");
}
sdp_session
}
#[cfg(test)]
use media_type::create_dummy_media_section;
#[test]
fn test_sanity_check_sdp_session_timing() {
let mut sdp_session = create_dummy_sdp_session();
sdp_session.extend_media(vec![create_dummy_media_section()]);
assert!(sanity_check_sdp_session(&sdp_session).is_err());
let t = SdpTiming { start: 0, stop: 0 };
sdp_session.set_timing(&t);
assert!(sanity_check_sdp_session(&sdp_session).is_ok());
}
#[test]
fn test_sanity_check_sdp_session_media() {
let mut sdp_session = create_dummy_sdp_session();
let t = SdpTiming { start: 0, stop: 0 };
sdp_session.set_timing(&t);
assert!(sanity_check_sdp_session(&sdp_session).is_err());
sdp_session.extend_media(vec![create_dummy_media_section()]);
assert!(sanity_check_sdp_session(&sdp_session).is_ok());
}
#[test]
fn test_sanity_check_sdp_session_extmap() {
let mut sdp_session = create_dummy_sdp_session();
let t = SdpTiming { start: 0, stop: 0 };
sdp_session.set_timing(&t);
sdp_session.extend_media(vec![create_dummy_media_section()]);
let attribute =
parse_attribute("extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time");
assert!(attribute.is_ok());
let extmap;
if let SdpType::Attribute(a) = attribute.unwrap() {
extmap = a;
} else {
panic!("SdpType is not Attribute");
}
let ret = sdp_session.add_attribute(&extmap);
assert!(ret.is_ok());
assert!(sdp_session
.get_attribute(SdpAttributeType::Extmap)
.is_some());
assert!(sanity_check_sdp_session(&sdp_session).is_ok());
let mattribute =
parse_attribute("extmap:1/sendonly urn:ietf:params:rtp-hdrext:ssrc-audio-level");
assert!(mattribute.is_ok());
let mextmap;
if let SdpType::Attribute(ma) = mattribute.unwrap() {
mextmap = ma;
} else {
panic!("SdpType is not Attribute");
}
let mut second_media = create_dummy_media_section();
assert!(second_media.add_attribute(&mextmap).is_ok());
assert!(second_media
.get_attribute(SdpAttributeType::Extmap)
.is_some());
sdp_session.extend_media(vec![second_media]);
assert!(sdp_session.media.len() == 2);
assert!(sanity_check_sdp_session(&sdp_session).is_err());
sdp_session.attribute = Vec::new();
assert!(sanity_check_sdp_session(&sdp_session).is_ok());
}
#[test]
fn test_sanity_check_sdp_session_simulcast() {
let mut sdp_session = create_dummy_sdp_session();
let t = SdpTiming { start: 0, stop: 0 };
sdp_session.set_timing(&t);
sdp_session.extend_media(vec![create_dummy_media_section()]);
assert!(sanity_check_sdp_session(&sdp_session).is_ok());
}
fn parse_sdp_vector(lines: &[SdpLine]) -> Result<SdpSession, SdpParserError> {
if lines.len() < 5 {
return Err(SdpParserError::Sequence {
message: "SDP neeeds at least 5 lines".to_string(),
line_number: 0,
});
}
let version: u64 = match lines[0].sdp_type {
SdpType::Version(v) => v,
_ => {
return Err(SdpParserError::Sequence {
message: "first line needs to be version number".to_string(),
line_number: lines[0].line_number,
});
}
};
let origin: SdpOrigin = match lines[1].sdp_type {
SdpType::Origin(ref v) => v.clone(),
_ => {
return Err(SdpParserError::Sequence {
message: "second line needs to be origin".to_string(),
line_number: lines[1].line_number,
});
}
};
let session: String = match lines[2].sdp_type {
SdpType::Session(ref v) => v.clone(),
_ => {
return Err(SdpParserError::Sequence {
message: "third line needs to be session".to_string(),
line_number: lines[2].line_number,
});
}
};
let mut sdp_session = SdpSession::new(version, origin, session);
for (index, line) in lines.iter().enumerate().skip(3) {
match line.sdp_type {
SdpType::Attribute(ref a) => {
sdp_session
.add_attribute(a)
.map_err(|e: SdpParserInternalError| SdpParserError::Sequence {
message: format!("{}", e),
line_number: line.line_number,
})?
}
SdpType::Bandwidth(ref b) => sdp_session.add_bandwidth(b),
SdpType::Timing(ref t) => sdp_session.set_timing(t),
SdpType::Connection(ref c) => sdp_session.set_connection(c),
SdpType::Media(_) => sdp_session.extend_media(parse_media_vector(&lines[index..])?),
SdpType::Origin(_) | SdpType::Session(_) | SdpType::Version(_) => {
return Err(SdpParserError::Sequence {
message: "version, origin or session at wrong level".to_string(),
line_number: line.line_number,
});
}
SdpType::Email(_)
| SdpType::Information(_)
| SdpType::Key(_)
| SdpType::Phone(_)
| SdpType::Repeat(_)
| SdpType::Uri(_)
| SdpType::Zone(_) => (),
};
if !sdp_session.media.is_empty() {
break;
};
}
sanity_check_sdp_session(&sdp_session)?;
Ok(sdp_session)
}
pub fn parse_sdp(sdp: &str, fail_on_warning: bool) -> Result<SdpSession, SdpParserError> {
if sdp.is_empty() {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("empty SDP".to_string()),
line: sdp.to_string(),
line_number: 0,
});
}
if sdp.len() < 62 {
return Err(SdpParserError::Line {
error: SdpParserInternalError::Generic("string to short to be valid SDP".to_string()),
line: sdp.to_string(),
line_number: 0,
});
}
let lines = sdp.lines();
let mut errors: Vec<SdpParserError> = Vec::new();
let mut warnings: Vec<SdpParserError> = Vec::new();
let mut sdp_lines: Vec<SdpLine> = Vec::new();
for (line_number, line) in lines.enumerate() {
let stripped_line = line.trim();
if stripped_line.is_empty() {
continue;
}
match parse_sdp_line(stripped_line, line_number) {
Ok(n) => {
sdp_lines.push(n);
}
Err(e) => {
match e {
SdpParserError::Line {
error,
line,
line_number,
} => errors.push(SdpParserError::Line {
error,
line,
line_number,
}),
SdpParserError::Unsupported {
error,
line,
line_number,
} => {
warnings.push(SdpParserError::Unsupported {
error,
line,
line_number,
});
}
SdpParserError::Sequence {
message,
line_number,
} => errors.push(SdpParserError::Sequence {
message,
line_number,
}),
}
}
};
}
if fail_on_warning && (!warnings.is_empty()) {
return Err(warnings[0].clone());
}
if let Some(e) = errors.pop() {
return Err(e);
};
let mut session = parse_sdp_vector(&sdp_lines)?;
session.warnings = warnings;
for warning in &session.warnings {
warn!("Warning: {}", &warning);
}
Ok(session)
}
#[test]
fn test_parse_sdp_zero_length_string_fails() {
assert!(parse_sdp("", true).is_err());
}
#[test]
fn test_parse_sdp_to_short_string() {
assert!(parse_sdp("fooooobarrrr", true).is_err());
}
#[test]
fn test_parse_sdp_line_error() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.0.0.0\r\n
s=-\r\n
t=0 foobar\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_unsupported_error() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.0.0.0\r\n
s=-\r\n
t=0 0\r\n
m=foobar 0 UDP/TLS/RTP/SAVPF 0\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_unsupported_warning() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.0.0.0\r\n
s=-\r\n
c=IN IP4 198.51.100.7\r\n
t=0 0\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
a=unsupported\r\n",
false
)
.is_ok());
}
#[test]
fn test_parse_sdp_sequence_error() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.0.0.0\r\n
t=0 0\r\n
s=-\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_integer_error() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.0.0.0\r\n
s=-\r\n
t=0 0\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
a=rtcp:34er21\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_ipaddr_error() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.a.b.0\r\n
s=-\r\n
t=0 0\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_invalid_session_attribute() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.a.b.0\r\n
s=-\r\n
t=0 0\r\n
a=bundle-only\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n",
true
)
.is_err());
}
#[test]
fn test_parse_sdp_invalid_media_attribute() {
assert!(parse_sdp(
"v=0\r\n
o=- 0 0 IN IP4 0.a.b.0\r\n
s=-\r\n
t=0 0\r\n
m=audio 0 UDP/TLS/RTP/SAVPF 0\r\n
a=ice-lite\r\n",
true
)
.is_err());
}