summaryrefslogtreecommitdiffstats
path: root/third_party/rust/webrtc-sdp/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/webrtc-sdp/src/lib.rs')
-rw-r--r--third_party/rust/webrtc-sdp/src/lib.rs907
1 files changed, 907 insertions, 0 deletions
diff --git a/third_party/rust/webrtc-sdp/src/lib.rs b/third_party/rust/webrtc-sdp/src/lib.rs
new file mode 100644
index 0000000000..0a26adc3ea
--- /dev/null
+++ b/third_party/rust/webrtc-sdp/src/lib.rs
@@ -0,0 +1,907 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![warn(clippy::all)]
+#![forbid(unsafe_code)]
+
+#[macro_use]
+extern crate log;
+#[cfg(feature = "serialize")]
+#[macro_use]
+extern crate serde_derive;
+#[cfg(feature = "serialize")]
+extern crate serde;
+use std::convert::TryFrom;
+use std::fmt;
+
+#[macro_use]
+pub mod attribute_type;
+pub mod address;
+pub mod anonymizer;
+pub mod error;
+pub mod media_type;
+pub mod network;
+
+use address::{AddressTyped, ExplicitlyTypedAddress};
+use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer};
+use attribute_type::{
+ 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_address_type, parse_network_type};
+
+/*
+ * RFC4566
+ * bandwidth-fields = *(%x62 "=" bwtype ":" bandwidth CRLF)
+ */
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub enum SdpBandwidth {
+ As(u32),
+ Ct(u32),
+ Tias(u32),
+ Unknown(String, u32),
+}
+
+impl fmt::Display for SdpBandwidth {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let (tp_string, value) = match *self {
+ SdpBandwidth::As(ref x) => ("AS", x),
+ SdpBandwidth::Ct(ref x) => ("CT", x),
+ SdpBandwidth::Tias(ref x) => ("TIAS", x),
+ SdpBandwidth::Unknown(ref tp, ref x) => (&tp[..], x),
+ };
+ write!(f, "{tp_string}:{value}")
+ }
+}
+
+/*
+ * RFC4566
+ * connection-field = [%x63 "=" nettype SP addrtype SP
+ * connection-address CRLF]
+ */
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub struct SdpConnection {
+ pub address: ExplicitlyTypedAddress,
+ pub ttl: Option<u8>,
+ pub amount: Option<u32>,
+}
+
+impl fmt::Display for SdpConnection {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.address.fmt(f)?;
+ write_option_string!(f, "/{}", self.ttl)?;
+ write_option_string!(f, "/{}", self.amount)
+ }
+}
+
+impl AnonymizingClone for SdpConnection {
+ fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
+ let mut masked = self.clone();
+ masked.address = anon.mask_typed_address(&self.address);
+ masked
+ }
+}
+
+/*
+ * RFC4566
+ * origin-field = %x6f "=" username SP sess-id SP sess-version SP
+ * nettype SP addrtype SP unicast-address CRLF
+ */
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub struct SdpOrigin {
+ pub username: String,
+ pub session_id: u64,
+ pub session_version: u64,
+ pub unicast_addr: ExplicitlyTypedAddress,
+}
+
+impl fmt::Display for SdpOrigin {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{username} {sess_id} {sess_vers} {unicast_addr}",
+ username = self.username,
+ sess_id = self.session_id,
+ sess_vers = self.session_version,
+ unicast_addr = self.unicast_addr
+ )
+ }
+}
+
+impl AnonymizingClone for SdpOrigin {
+ fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
+ let mut masked = self.clone();
+ masked.username = anon.mask_origin_user(&self.username);
+ masked.unicast_addr = anon.mask_typed_address(&masked.unicast_addr);
+ masked
+ }
+}
+
+/*
+ * RFC4566
+ * time-fields = 1*( %x74 "=" start-time SP stop-time
+ * *(CRLF repeat-fields) CRLF)
+ * [zone-adjustments CRLF]
+ */
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub struct SdpTiming {
+ pub start: u64,
+ pub stop: u64,
+}
+
+impl fmt::Display for SdpTiming {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{start} {stop}", start = self.start, stop = self.stop)
+ }
+}
+
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub enum SdpType {
+ // Note: Email, Information, Key, Phone, Repeat, Uri and Zone are left out
+ // on purposes as we don't want to support them.
+ Attribute(SdpAttribute),
+ Bandwidth(SdpBandwidth),
+ Connection(SdpConnection),
+ Media(SdpMediaLine),
+ Origin(SdpOrigin),
+ Session(String),
+ Timing(SdpTiming),
+ Version(u64),
+}
+
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub struct SdpLine {
+ pub line_number: usize,
+ pub sdp_type: SdpType,
+ pub text: String,
+}
+
+/*
+ * RFC4566
+ * ; SDP Syntax
+ * session-description = proto-version
+ * origin-field
+ * session-name-field
+ * information-field
+ * uri-field
+ * email-fields
+ * phone-fields
+ * connection-field
+ * bandwidth-fields
+ * time-fields
+ * key-field
+ * attribute-fields
+ * media-descriptions
+ */
+#[derive(Clone)]
+#[cfg_attr(feature = "serialize", derive(Serialize))]
+#[cfg_attr(feature = "enhanced_debug", derive(Debug))]
+pub struct SdpSession {
+ pub version: u64,
+ pub origin: SdpOrigin,
+ pub session: Option<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>, // unsupported values:
+ // information: Option<String>,
+ // uri: Option<String>,
+ // email: Option<String>,
+ // phone: Option<String>,
+ // repeat: Option<String>,
+ // zone: Option<String>,
+ // key: Option<String>
+}
+
+impl fmt::Display for SdpSession {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "v={version}\r\n\
+ o={origin}\r\n\
+ s={session}\r\n\
+ {timing}\
+ {bandwidth}\
+ {connection}\
+ {session_attributes}\
+ {media_sections}",
+ version = self.version,
+ origin = self.origin,
+ session = self.get_session_text(),
+ 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),
+ session_attributes = maybe_vector_to_string!("a={}\r\n", self.attribute, "\r\na="),
+ media_sections = self.media.iter().map(|s| s.to_string()).collect::<String>(),
+ )
+ }
+}
+
+impl SdpSession {
+ pub fn new(version: u64, origin: SdpOrigin, session: String) -> SdpSession {
+ let session = match session.trim() {
+ s if !s.is_empty() => Some(s.to_owned()),
+ _ => None,
+ };
+ 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) -> &Option<String> {
+ &self.session
+ }
+
+ pub fn get_session_text(&self) -> &str {
+ if let Some(text) = &self.session {
+ text.as_str()
+ } else {
+ " "
+ }
+ }
+ pub fn get_connection(&self) -> &Option<SdpConnection> {
+ &self.connection
+ }
+
+ pub fn set_connection(&mut self, c: SdpConnection) {
+ self.connection = Some(c)
+ }
+
+ pub fn add_bandwidth(&mut self, b: SdpBandwidth) {
+ self.bandwidth.push(b)
+ }
+
+ pub fn set_timing(&mut self, t: SdpTiming) {
+ self.timing = Some(t)
+ }
+
+ pub fn add_attribute(&mut self, a: SdpAttribute) -> Result<(), SdpParserInternalError> {
+ if !a.allowed_at_session_level() {
+ return Err(SdpParserInternalError::Generic(format!(
+ "{a} not allowed at session level"
+ )));
+ };
+ self.attribute.push(a);
+ Ok(())
+ }
+
+ pub fn extend_media(&mut self, v: Vec<SdpMedia>) {
+ self.media.extend(v)
+ }
+
+ pub fn parse_session_vector(&mut self, lines: &mut Vec<SdpLine>) -> Result<(), SdpParserError> {
+ while !lines.is_empty() {
+ let line = lines.remove(0);
+ match line.sdp_type {
+ SdpType::Attribute(a) => {
+ let _line_number = line.line_number;
+ self.add_attribute(a).map_err(|e: SdpParserInternalError| {
+ SdpParserError::Sequence {
+ message: format!("{e}"),
+ line_number: _line_number,
+ }
+ })?
+ }
+ SdpType::Bandwidth(b) => self.add_bandwidth(b),
+ SdpType::Timing(t) => self.set_timing(t),
+ SdpType::Connection(c) => self.set_connection(c),
+
+ 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::Media(_) => {
+ return Err(SdpParserError::Sequence {
+ message: "media line not allowed in session parser".to_string(),
+ line_number: line.line_number,
+ });
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn get_attribute(&self, t: SdpAttributeType) -> Option<&SdpAttribute> {
+ self.attribute
+ .iter()
+ .find(|a| SdpAttributeType::from(*a) == t)
+ }
+
+ pub fn add_media(
+ &mut self,
+ media_type: SdpMediaValue,
+ direction: SdpAttribute,
+ port: u32,
+ protocol: SdpProtocolValue,
+ addr: ExplicitlyTypedAddress,
+ ) -> 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 {
+ address: addr,
+ ttl: None,
+ amount: None,
+ });
+
+ self.media.push(media);
+
+ Ok(())
+ }
+}
+
+impl AnonymizingClone for SdpSession {
+ fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self {
+ let mut masked: SdpSession = SdpSession {
+ version: self.version,
+ session: self.session.clone(),
+ origin: self.origin.masked_clone(anon),
+ connection: self.connection.clone(),
+ timing: self.timing.clone(),
+ bandwidth: self.bandwidth.clone(),
+ attribute: Vec::new(),
+ media: Vec::new(),
+ warnings: Vec::new(),
+ };
+ masked.origin = self.origin.masked_clone(anon);
+ masked.connection = masked.connection.map(|con| con.masked_clone(anon));
+ for i in &self.attribute {
+ masked.attribute.push(i.masked_clone(anon));
+ }
+ masked
+ }
+}
+
+/* removing this wrap would not allow us to call this from the match statement inside
+ * parse_sdp_line() */
+#[allow(clippy::unnecessary_wraps)]
+fn parse_session(value: &str) -> Result<SdpType, SdpParserInternalError> {
+ trace!("session: {}", value);
+ Ok(SdpType::Session(String::from(value)))
+}
+
+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))
+}
+
+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 network type token".to_string(),
+ ));
+ }
+ Some(x) => parse_network_type(x)?,
+ };
+ let addrtype = match tokens.next() {
+ None => {
+ return Err(SdpParserInternalError::Generic(
+ "Origin type is missing address type token".to_string(),
+ ));
+ }
+ Some(x) => parse_address_type(x)?,
+ };
+ let unicast_addr = match tokens.next() {
+ None => {
+ return Err(SdpParserInternalError::Generic(
+ "Origin type is missing IP address token".to_string(),
+ ));
+ }
+ Some(x) => ExplicitlyTypedAddress::try_from((addrtype, x))?,
+ };
+ if addrtype != unicast_addr.address_type() {
+ 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);
+ Ok(SdpType::Origin(o))
+}
+
+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_network_type(cv[0])?;
+ let addrtype = parse_address_type(cv[1])?;
+ let mut ttl = None;
+ let mut amount = None;
+ let mut addr_token = cv[2];
+ if addr_token.find('/').is_some() {
+ 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 address = ExplicitlyTypedAddress::try_from((addrtype, addr_token))?;
+ let c = SdpConnection {
+ address,
+ ttl,
+ amount,
+ };
+ trace!("connection: {}", c);
+ Ok(SdpType::Connection(c))
+}
+
+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: {}", bw);
+ Ok(SdpType::Bandwidth(bw))
+}
+
+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);
+ Ok(SdpType::Timing(t))
+}
+
+pub fn parse_sdp_line(line: &str, line_number: usize) -> Result<SdpLine, SdpParserError> {
+ if line.find('=').is_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.to_lowercase()
+ }
+ };
+ let (line_value, untrimmed_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();
+ // For compatibility with sites that don't adhere to "s=-" for no session ID
+ if trimmed.is_empty() && line_type.as_str() != "s" {
+ return Err(SdpParserError::Line {
+ error: SdpParserInternalError::Generic("value is empty".to_string()),
+ line: line.to_string(),
+ line_number,
+ });
+ }
+ (trimmed, v)
+ }
+ };
+ match line_type.as_ref() {
+ "a" => parse_attribute(line_value),
+ "b" => parse_bandwidth(line_value),
+ "c" => parse_connection(line_value),
+ "e" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type email: {line_value}"
+ ))),
+ "i" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type information: {line_value}"
+ ))),
+ "k" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported insecure key exchange: {line_value}"
+ ))),
+ "m" => parse_media(line_value),
+ "o" => parse_origin(line_value),
+ "p" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type phone: {line_value}"
+ ))),
+ "r" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type repeat: {line_value}"
+ ))),
+ "s" => parse_session(untrimmed_line_value),
+ "t" => parse_timing(line_value),
+ "u" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type uri: {line_value}"
+ ))),
+ "v" => parse_version(line_value),
+ "z" => Err(SdpParserInternalError::Generic(format!(
+ "unsupported type zone: {line_value}"
+ ))),
+ _ => Err(SdpParserInternalError::Generic(
+ "unknown sdp type".to_string(),
+ )),
+ }
+ .map(|sdp_type| SdpLine {
+ line_number,
+ sdp_type,
+ text: line.to_owned(),
+ })
+ .map_err(|e| match e {
+ SdpParserInternalError::UnknownAddressType(..)
+ | SdpParserInternalError::AddressTypeMismatch { .. }
+ | SdpParserInternalError::Generic(..)
+ | SdpParserInternalError::Integer(..)
+ | SdpParserInternalError::Float(..)
+ | SdpParserInternalError::Domain(..)
+ | SdpParserInternalError::IpAddress(..) => SdpParserError::Line {
+ error: e,
+ line: line.to_string(),
+ line_number,
+ },
+ SdpParserInternalError::Unsupported(..) => SdpParserError::Unsupported {
+ error: e,
+ line: line.to_string(),
+ line_number,
+ },
+ })
+}
+
+fn sanity_check_sdp_session(session: &SdpSession) -> Result<(), SdpParserError> {
+ let make_seq_error = |x: &str| SdpParserError::Sequence {
+ message: x.to_string(),
+ line_number: 0,
+ };
+
+ if session.timing.is_none() {
+ return Err(make_seq_error("Missing timing type at session level"));
+ }
+ // Checks that all media have connections if there is no top level
+ // This explicitly allows for zero connection lines if there are no media
+ // sections for interoperability reasons.
+ let media_cons = &session.media.iter().all(|m| m.get_connection().is_some());
+ if !media_cons && session.get_connection().is_none() {
+ return Err(make_seq_error(
+ "Without connection type at session level all media sections must have connection types",
+ ));
+ }
+
+ // Check that extmaps are not defined on session and media level
+ if session.get_attribute(SdpAttributeType::Extmap).is_some() {
+ for msection in &session.media {
+ if msection.get_attribute(SdpAttributeType::Extmap).is_some() {
+ return Err(make_seq_error(
+ "Extmap can't be define at session and media level",
+ ));
+ }
+ }
+ }
+
+ for msection in &session.media {
+ if msection
+ .get_attribute(SdpAttributeType::RtcpMuxOnly)
+ .is_some()
+ && msection.get_attribute(SdpAttributeType::RtcpMux).is_none()
+ {
+ return Err(make_seq_error(
+ "rtcp-mux-only media sections must also contain the rtcp-mux attribute",
+ ));
+ }
+
+ 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_seq_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_seq_error(
+ "Rid pts must be declared in the media section",
+ ));
+ }
+ }
+ }
+ }
+
+ if let Some(SdpAttribute::Simulcast(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_seq_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(())
+}
+
+fn parse_sdp_vector(lines: &mut Vec<SdpLine>) -> Result<SdpSession, SdpParserError> {
+ if lines.len() < 4 {
+ return Err(SdpParserError::Sequence {
+ message: "SDP neeeds at least 4 lines".to_string(),
+ line_number: 0,
+ });
+ }
+
+ let version = match lines.remove(0).sdp_type {
+ SdpType::Version(v) => v,
+ _ => {
+ return Err(SdpParserError::Sequence {
+ message: "first line needs to be version number".to_string(),
+ line_number: 0,
+ });
+ }
+ };
+ let origin = match lines.remove(0).sdp_type {
+ SdpType::Origin(v) => v,
+ _ => {
+ return Err(SdpParserError::Sequence {
+ message: "second line needs to be origin".to_string(),
+ line_number: 1,
+ });
+ }
+ };
+ let session = match lines.remove(0).sdp_type {
+ SdpType::Session(v) => v,
+ _ => {
+ return Err(SdpParserError::Sequence {
+ message: "third line needs to be session".to_string(),
+ line_number: 2,
+ });
+ }
+ };
+ let mut sdp_session = SdpSession::new(version, origin, session);
+
+ let _media_pos = lines
+ .iter()
+ .position(|l| matches!(l.sdp_type, SdpType::Media(_)));
+
+ match _media_pos {
+ Some(p) => {
+ let mut media: Vec<_> = lines.drain(p..).collect();
+ sdp_session.parse_session_vector(lines)?;
+ sdp_session.extend_media(parse_media_vector(&mut media)?);
+ }
+ None => sdp_session.parse_session_vector(lines)?,
+ };
+
+ 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,
+ });
+ }
+ // see test_parse_sdp_minimal_sdp_successfully
+ if sdp.len() < 51 {
+ return Err(SdpParserError::Line {
+ error: SdpParserInternalError::Generic("string too 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(line, line_number) {
+ Ok(n) => {
+ sdp_lines.push(n);
+ }
+ Err(e) => {
+ match e {
+ // TODO is this really a good way to accomplish this?
+ 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.remove(0));
+ }
+
+ // We just return the last of the errors here
+ if let Some(e) = errors.pop() {
+ return Err(e);
+ };
+
+ let mut session = parse_sdp_vector(&mut sdp_lines)?;
+ session.warnings = warnings;
+
+ for warning in &session.warnings {
+ warn!("Warning: {}", &warning);
+ }
+
+ Ok(session)
+}
+
+#[cfg(test)]
+#[path = "./lib_tests.rs"]
+mod tests;