diff options
Diffstat (limited to 'third_party/rust/webrtc-sdp/src/attribute_type.rs')
-rw-r--r-- | third_party/rust/webrtc-sdp/src/attribute_type.rs | 3319 |
1 files changed, 3319 insertions, 0 deletions
diff --git a/third_party/rust/webrtc-sdp/src/attribute_type.rs b/third_party/rust/webrtc-sdp/src/attribute_type.rs new file mode 100644 index 0000000000..7c667698b5 --- /dev/null +++ b/third_party/rust/webrtc-sdp/src/attribute_type.rs @@ -0,0 +1,3319 @@ +/* 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/. */ + +extern crate url; +use std::convert::TryFrom; +use std::fmt; +use std::iter; +use std::str::FromStr; + +use error::SdpParserInternalError; +use network::{parse_network_type, parse_unicast_address}; +use SdpType; + +use address::{Address, AddressType, ExplicitlyTypedAddress}; +use anonymizer::{AnonymizingClone, StatefulSdpAnonymizer}; + +// Serialization helper marcos and functions +#[macro_export] +macro_rules! option_to_string { + ($fmt_str:expr, $opt:expr) => { + match $opt { + Some(ref x) => format!($fmt_str, x), + None => "".to_string(), + } + }; +} + +#[macro_export] +macro_rules! write_option_string { + ($f:expr, $fmt_str:expr, $opt:expr) => { + match $opt { + Some(ref x) => write!($f, $fmt_str, x), + None => Ok(()), + } + }; +} + +#[macro_export] +macro_rules! maybe_vector_to_string { + ($fmt_str:expr, $vec:expr, $sep:expr) => { + match $vec.len() { + 0 => "".to_string(), + _ => format!( + $fmt_str, + $vec.iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join($sep) + ), + } + }; +} + +#[macro_export] +macro_rules! non_empty_string_vec { + ( $( $x:expr ),* ) => { + { + let mut temp_vec = Vec::new(); + $( + if !$x.is_empty() { + temp_vec.push($x); + } + )* + temp_vec + } + }; +} + +pub fn maybe_print_param<T>(name: &str, param: T, default_value: T) -> String +where + T: PartialEq + ToString, +{ + if param != default_value { + name.to_owned() + ¶m.to_string() + } else { + "".to_string() + } +} + +pub fn maybe_print_bool_param(name: &str, param: bool, default_value: bool) -> String { + if param != default_value { + name.to_owned() + "=" + &(param as i32).to_string() + } else { + "".to_string() + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpSingleDirection { + // This is explicitly 1 and 2 to match the defines in the C++ glue code. + Send = 1, + Recv = 2, +} + +impl fmt::Display for SdpSingleDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpSingleDirection::Send => "send", + SdpSingleDirection::Recv => "recv", + } + .fmt(f) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributePayloadType { + PayloadType(u8), + Wildcard, // Wildcard means "*", +} + +impl fmt::Display for SdpAttributePayloadType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributePayloadType::PayloadType(pt) => pt.fmt(f), + SdpAttributePayloadType::Wildcard => "*".fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateTransport { + Udp, + Tcp, +} + +impl fmt::Display for SdpAttributeCandidateTransport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateTransport::Udp => "UDP", + SdpAttributeCandidateTransport::Tcp => "TCP", + } + .fmt(f) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateType { + Host, + Srflx, + Prflx, + Relay, +} + +impl fmt::Display for SdpAttributeCandidateType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateType::Host => "host", + SdpAttributeCandidateType::Srflx => "srflx", + SdpAttributeCandidateType::Prflx => "prflx", + SdpAttributeCandidateType::Relay => "relay", + } + .fmt(f) + } +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeCandidateTcpType { + Active, + Passive, + Simultaneous, +} + +impl fmt::Display for SdpAttributeCandidateTcpType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeCandidateTcpType::Active => "active", + SdpAttributeCandidateTcpType::Passive => "passive", + SdpAttributeCandidateTcpType::Simultaneous => "so", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeCandidate { + pub foundation: String, + pub component: u32, + pub transport: SdpAttributeCandidateTransport, + pub priority: u64, + pub address: Address, + pub port: u32, + pub c_type: SdpAttributeCandidateType, + pub raddr: Option<Address>, + pub rport: Option<u32>, + pub tcp_type: Option<SdpAttributeCandidateTcpType>, + pub generation: Option<u32>, + pub ufrag: Option<String>, + pub networkcost: Option<u32>, + pub unknown_extensions: Vec<(String, String)>, +} + +impl fmt::Display for SdpAttributeCandidate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{foundation} {component} {transport} {priority} \ + {address} {port} typ {ctype}\ + {raddr}{rport}{tcp_type}{generation}{ufrag}{cost}\ + {unknown}", + foundation = self.foundation, + component = self.component, + transport = self.transport, + priority = self.priority, + address = self.address, + port = self.port, + ctype = self.c_type, + raddr = option_to_string!(" raddr {}", self.raddr), + rport = option_to_string!(" rport {}", self.rport), + tcp_type = option_to_string!(" tcptype {}", self.tcp_type), + generation = option_to_string!(" generation {}", self.generation), + ufrag = option_to_string!(" ufrag {}", self.ufrag), + cost = option_to_string!(" network-cost {}", self.networkcost), + unknown = self + .unknown_extensions + .iter() + .map(|(name, value)| format!(" {name} {value}")) + .collect::<String>() + ) + } +} + +impl SdpAttributeCandidate { + pub fn new( + foundation: String, + component: u32, + transport: SdpAttributeCandidateTransport, + priority: u64, + address: Address, + port: u32, + c_type: SdpAttributeCandidateType, + ) -> SdpAttributeCandidate { + SdpAttributeCandidate { + foundation, + component, + transport, + priority, + address, + port, + c_type, + raddr: None, + rport: None, + tcp_type: None, + generation: None, + ufrag: None, + networkcost: None, + unknown_extensions: Vec::new(), + } + } + + fn set_remote_address(&mut self, addr: Address) { + self.raddr = Some(addr) + } + + fn set_remote_port(&mut self, p: u32) { + self.rport = Some(p) + } + + fn set_tcp_type(&mut self, t: SdpAttributeCandidateTcpType) { + self.tcp_type = Some(t) + } + + fn set_generation(&mut self, g: u32) { + self.generation = Some(g) + } + + fn set_ufrag(&mut self, u: String) { + self.ufrag = Some(u) + } + + fn set_network_cost(&mut self, n: u32) { + self.networkcost = Some(n) + } + + fn add_unknown_extension(&mut self, name: String, value: String) { + self.unknown_extensions.push((name, value)); + } +} + +impl AnonymizingClone for SdpAttributeCandidate { + fn masked_clone(&self, anonymizer: &mut StatefulSdpAnonymizer) -> Self { + let mut masked = self.clone(); + masked.address = anonymizer.mask_address(&self.address); + masked.port = anonymizer.mask_port(self.port); + masked.raddr = self + .raddr + .clone() + .map(|addr| anonymizer.mask_address(&addr)); + masked.rport = self.rport.map(|port| anonymizer.mask_port(port)); + masked + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeDtlsMessage { + Client(String), + Server(String), +} + +impl fmt::Display for SdpAttributeDtlsMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SdpAttributeDtlsMessage::Client(msg) => format!("client {msg}"), + SdpAttributeDtlsMessage::Server(msg) => format!("server {msg}"), + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRemoteCandidate { + pub component: u32, + pub address: Address, + pub port: u32, +} + +impl fmt::Display for SdpAttributeRemoteCandidate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{component} {addr} {port}", + component = self.component, + addr = self.address, + port = self.port + ) + } +} + +impl AnonymizingClone for SdpAttributeRemoteCandidate { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + SdpAttributeRemoteCandidate { + address: anon.mask_address(&self.address), + port: anon.mask_port(self.port), + component: self.component, + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcastId { + pub id: String, + pub paused: bool, +} + +impl SdpAttributeSimulcastId { + pub fn new(idstr: &str) -> SdpAttributeSimulcastId { + if let Some(idstr) = idstr.strip_prefix('~') { + SdpAttributeSimulcastId { + id: idstr.to_string(), + paused: true, + } + } else { + SdpAttributeSimulcastId { + id: idstr.to_string(), + paused: false, + } + } + } +} + +impl fmt::Display for SdpAttributeSimulcastId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.paused { + write!(f, "~")?; + } + self.id.fmt(f) + } +} + +#[repr(C)] +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcastVersion { + pub ids: Vec<SdpAttributeSimulcastId>, +} + +impl SdpAttributeSimulcastVersion { + pub fn new(idlist: &str) -> SdpAttributeSimulcastVersion { + SdpAttributeSimulcastVersion { + ids: idlist + .split(',') + .map(SdpAttributeSimulcastId::new) + .collect(), + } + } +} + +impl fmt::Display for SdpAttributeSimulcastVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.ids + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(",") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSimulcast { + pub send: Vec<SdpAttributeSimulcastVersion>, + pub receive: Vec<SdpAttributeSimulcastVersion>, +} + +impl fmt::Display for SdpAttributeSimulcast { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + non_empty_string_vec![ + maybe_vector_to_string!("send {}", self.send, ";"), + maybe_vector_to_string!("recv {}", self.receive, ";") + ] + .join(" ") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtcp { + pub port: u16, + pub unicast_addr: Option<ExplicitlyTypedAddress>, +} + +impl SdpAttributeRtcp { + pub fn new(port: u16) -> SdpAttributeRtcp { + SdpAttributeRtcp { + port, + unicast_addr: None, + } + } + + fn set_addr(&mut self, addr: ExplicitlyTypedAddress) { + self.unicast_addr = Some(addr) + } +} + +impl fmt::Display for SdpAttributeRtcp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.unicast_addr { + Some(ref addr) => write!(f, "{} {}", self.port, addr), + None => self.port.fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeRtcpFbType { + Ack = 0, + Ccm = 2, // This is explicitly 2 to make the conversion to the + // enum used in the glue-code possible. The glue code has "app" + // in the place of 1 + Nack, + TrrInt, + Remb, + TransCc, +} + +impl fmt::Display for SdpAttributeRtcpFbType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeRtcpFbType::Ack => "ack", + SdpAttributeRtcpFbType::Ccm => "ccm", + SdpAttributeRtcpFbType::Nack => "nack", + SdpAttributeRtcpFbType::TrrInt => "trr-int", + SdpAttributeRtcpFbType::Remb => "goog-remb", + SdpAttributeRtcpFbType::TransCc => "transport-cc", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtcpFb { + pub payload_type: SdpAttributePayloadType, + pub feedback_type: SdpAttributeRtcpFbType, + pub parameter: String, + pub extra: String, +} + +impl fmt::Display for SdpAttributeRtcpFb { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.payload_type, self.feedback_type,)?; + if !self.parameter.is_empty() { + write!( + f, + " {}{}", + self.parameter, + maybe_print_param(" ", self.extra.clone(), "".to_string()), + )?; + } + Ok(()) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeDirection { + Recvonly, + Sendonly, + Sendrecv, +} + +impl fmt::Display for SdpAttributeDirection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeDirection::Recvonly => "recvonly", + SdpAttributeDirection::Sendonly => "sendonly", + SdpAttributeDirection::Sendrecv => "sendrecv", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeExtmap { + pub id: u16, + pub direction: Option<SdpAttributeDirection>, + pub url: String, + pub extension_attributes: Option<String>, +} + +impl fmt::Display for SdpAttributeExtmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{id}{direction} {url}{ext}", + id = self.id, + direction = option_to_string!("/{}", self.direction), + url = self.url, + ext = option_to_string!(" {}", self.extension_attributes) + ) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct RtxFmtpParameters { + pub apt: u8, + pub rtx_time: Option<u32>, +} + +impl fmt::Display for RtxFmtpParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(rtx_time) = self.rtx_time { + write!(f, "apt={};rtx-time={}", self.apt, rtx_time) + } else { + write!(f, "apt={}", self.apt) + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFmtpParameters { + // H264 + // TODO(bug 1466859): Support sprop-parameter-sets + // pub static const max_sprop_len: u32 = 128, + // pub sprop_parameter_sets: [u8, max_sprop_len], + pub packetization_mode: u32, + pub level_asymmetry_allowed: bool, + pub profile_level_id: u32, + pub max_fs: u32, + pub max_cpb: u32, + pub max_dpb: u32, + pub max_br: u32, + pub max_mbps: u32, + + // VP8 and VP9 + // max_fs, already defined in H264 + pub max_fr: u32, + + // Opus https://tools.ietf.org/html/rfc7587 + pub maxplaybackrate: u32, + pub maxaveragebitrate: u32, + pub usedtx: bool, + pub stereo: bool, + pub useinbandfec: bool, + pub cbr: bool, + pub ptime: u32, + pub minptime: u32, + pub maxptime: u32, + + // Red + pub encodings: Vec<u8>, + + // telephone-event + pub dtmf_tones: String, + + // RTX + pub rtx: Option<RtxFmtpParameters>, + + // Unknown + pub unknown_tokens: Vec<String>, +} + +impl fmt::Display for SdpAttributeFmtpParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(ref rtx) = self.rtx { + // rtx + return write!(f, "{rtx}"); + } + if !self.dtmf_tones.is_empty() { + // telephone-event + return write!(f, "{}", self.dtmf_tones); + } else if !self.encodings.is_empty() { + // red encodings + return self + .encodings + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join("/") + .fmt(f); + }; + write!( + f, + "{}", + non_empty_string_vec![ + maybe_print_param( + "profile-level-id=", + format!("{:06x}", self.profile_level_id), + "420010".to_string() + ), + maybe_print_bool_param( + "level-asymmetry-allowed", + self.level_asymmetry_allowed, + false + ), + maybe_print_param("packetization-mode=", self.packetization_mode, 0), + maybe_print_param("max-fs=", self.max_fs, 0), + maybe_print_param("max-cpb=", self.max_cpb, 0), + maybe_print_param("max-dpb=", self.max_dpb, 0), + maybe_print_param("max-br=", self.max_br, 0), + maybe_print_param("max-mbps=", self.max_mbps, 0), + maybe_print_param("max-fr=", self.max_fr, 0), + maybe_print_param("maxplaybackrate=", self.maxplaybackrate, 48000), + maybe_print_param("maxaveragebitrate=", self.maxaveragebitrate, 0), + maybe_print_param("ptime=", self.ptime, 0), + maybe_print_param("minptime=", self.minptime, 0), + maybe_print_param("maxptime=", self.maxptime, 0), + maybe_print_bool_param("usedtx", self.usedtx, false), + maybe_print_bool_param("stereo", self.stereo, false), + maybe_print_bool_param("useinbandfec", self.useinbandfec, false), + maybe_print_bool_param("cbr", self.cbr, false), + maybe_vector_to_string!("{}", self.unknown_tokens, ",") + ] + .join(";") + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFmtp { + pub payload_type: u8, + pub parameters: SdpAttributeFmtpParameters, +} + +impl fmt::Display for SdpAttributeFmtp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{pt} {parameter}", + pt = self.payload_type, + parameter = self.parameters + ) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeFingerprintHashType { + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, +} + +impl SdpAttributeFingerprintHashType { + pub fn try_from_name(name: &str) -> Result<Self, SdpParserInternalError> { + match name { + "sha-1" => Ok(Self::Sha1), + "sha-224" => Ok(Self::Sha224), + "sha-256" => Ok(Self::Sha256), + "sha-384" => Ok(Self::Sha384), + "sha-512" => Ok(Self::Sha512), + unknown => Err(SdpParserInternalError::Unsupported(format!( + "fingerprint contains an unsupported hash algorithm '{unknown}'" + ))), + } + } + pub fn octet_count(&self) -> usize { + match self { + Self::Sha1 => 20, + Self::Sha224 => 28, + Self::Sha256 => 32, + Self::Sha384 => 48, + Self::Sha512 => 64, + } + } + + pub fn parse_octets(&self, octets_string: &str) -> Result<Vec<u8>, SdpParserInternalError> { + let bytes = octets_string + .split(':') + .map(|byte_token| { + if byte_token.len() != 2 { + return Err(SdpParserInternalError::Generic( + "fingerpint's byte tokens must have 2 hexdigits".to_string(), + )); + } + Ok(u8::from_str_radix(byte_token, 16)?) + }) + .collect::<Result<Vec<u8>, _>>()?; + + if bytes.len() != self.octet_count() { + return Err(SdpParserInternalError::Generic(format!( + "fingerprint has {} bytes but should have {} bytes", + bytes.len(), + self.octet_count(), + ))); + } + + Ok(bytes) + } +} + +impl fmt::Display for SdpAttributeFingerprintHashType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeFingerprintHashType::Sha1 => "sha-1", + SdpAttributeFingerprintHashType::Sha224 => "sha-224", + SdpAttributeFingerprintHashType::Sha256 => "sha-256", + SdpAttributeFingerprintHashType::Sha384 => "sha-384", + SdpAttributeFingerprintHashType::Sha512 => "sha-512", + } + .fmt(f) + } +} + +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeFingerprint { + pub hash_algorithm: SdpAttributeFingerprintHashType, + pub fingerprint: Vec<u8>, +} + +impl TryFrom<(SdpAttributeFingerprintHashType, Vec<u8>)> for SdpAttributeFingerprint { + type Error = SdpParserInternalError; + fn try_from( + parts: (SdpAttributeFingerprintHashType, Vec<u8>), + ) -> Result<Self, SdpParserInternalError> { + let (hash_algorithm, fingerprint) = parts; + match (hash_algorithm.octet_count(), fingerprint.len()) { + (a, b) if a == b => Ok(Self { + hash_algorithm, + fingerprint, + }), + (a, b) => Err(SdpParserInternalError::Generic(format!( + "Hash algoritm expects {a} fingerprint bytes not {b}", + ))), + } + } +} + +impl fmt::Display for SdpAttributeFingerprint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{hash} {fp}", + hash = self.hash_algorithm, + fp = self + .fingerprint + .iter() + .map(|byte| format!("{byte:02X}")) + .collect::<Vec<String>>() + .join(":") + ) + } +} + +impl AnonymizingClone for SdpAttributeFingerprint { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + SdpAttributeFingerprint { + hash_algorithm: self.hash_algorithm, + fingerprint: anon.mask_cert_finger_print(&self.fingerprint), + } + } +} + +fn imageattr_discrete_value_list_to_string<T>(values: &[T]) -> String +where + T: ToString, +{ + match values.len() { + 1 => values[0].to_string(), + _ => format!( + "[{}]", + values + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(",") + ), + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrXyRange { + Range(u32, u32, Option<u32>), // min, max, step + DiscreteValues(Vec<u32>), +} + +impl fmt::Display for SdpAttributeImageAttrXyRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeImageAttrXyRange::Range(ref min, ref max, ref step_opt) => { + write!(f, "[{min}:")?; + if step_opt.is_some() { + write!(f, "{}:", step_opt.unwrap())?; + } + write!(f, "{max}]") + } + SdpAttributeImageAttrXyRange::DiscreteValues(ref values) => { + write!(f, "{}", imageattr_discrete_value_list_to_string(values)) + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrSRange { + Range(f32, f32), // min, max + DiscreteValues(Vec<f32>), +} + +impl fmt::Display for SdpAttributeImageAttrSRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SdpAttributeImageAttrSRange::Range(min, max) => write!(f, "[{min}-{max}]"), + SdpAttributeImageAttrSRange::DiscreteValues(values) => { + write!(f, "{}", imageattr_discrete_value_list_to_string(values)) + } + } + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeImageAttrPRange { + pub min: f32, + pub max: f32, +} + +impl fmt::Display for SdpAttributeImageAttrPRange { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}-{}]", self.min, self.max) + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeImageAttrSet { + pub x: SdpAttributeImageAttrXyRange, + pub y: SdpAttributeImageAttrXyRange, + pub sar: Option<SdpAttributeImageAttrSRange>, + pub par: Option<SdpAttributeImageAttrPRange>, + pub q: Option<f32>, +} + +impl fmt::Display for SdpAttributeImageAttrSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[x={x},y={y}", x = self.x, y = self.y)?; + write_option_string!(f, ",sar={}", self.sar)?; + write_option_string!(f, ",par={}", self.par)?; + write_option_string!(f, ",q={}", self.q)?; + write!(f, "]") + } +} + +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub enum SdpAttributeImageAttrSetList { + Sets(Vec<SdpAttributeImageAttrSet>), + Wildcard, +} + +impl fmt::Display for SdpAttributeImageAttrSetList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeImageAttrSetList::Sets(ref sets) => sets + .iter() + .map(ToString::to_string) + .collect::<Vec<String>>() + .join(" ") + .fmt(f), + SdpAttributeImageAttrSetList::Wildcard => "*".fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeImageAttr { + pub pt: SdpAttributePayloadType, + pub send: SdpAttributeImageAttrSetList, + pub recv: SdpAttributeImageAttrSetList, +} + +impl fmt::Display for SdpAttributeImageAttr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let maybe_sets_to_string = |set_list| match set_list { + SdpAttributeImageAttrSetList::Sets(sets) => match sets.len() { + 0 => None, + _ => Some(SdpAttributeImageAttrSetList::Sets(sets)), + }, + x => Some(x), + }; + self.pt.fmt(f)?; + write_option_string!(f, " send {}", maybe_sets_to_string(self.send.clone()))?; + write_option_string!(f, " recv {}", maybe_sets_to_string(self.recv.clone())) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSctpmap { + pub port: u16, + pub channels: u32, +} + +impl fmt::Display for SdpAttributeSctpmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{port} webrtc-datachannel {channels}", + port = self.port, + channels = self.channels + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeGroupSemantic { + LipSynchronization, // RFC5888 + FlowIdentification, // RFC5888 + SingleReservationFlow, // RFC3524 + AlternateNetworkAddressType, // RFC4091 + ForwardErrorCorrection, // RFC5956 + DecodingDependency, // RFC5583 + Bundle, // draft-ietc-mmusic-bundle +} + +impl fmt::Display for SdpAttributeGroupSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeGroupSemantic::LipSynchronization => "LS", + SdpAttributeGroupSemantic::FlowIdentification => "FID", + SdpAttributeGroupSemantic::SingleReservationFlow => "SRF", + SdpAttributeGroupSemantic::AlternateNetworkAddressType => "ANAT", + SdpAttributeGroupSemantic::ForwardErrorCorrection => "FEC", + SdpAttributeGroupSemantic::DecodingDependency => "DDP", + SdpAttributeGroupSemantic::Bundle => "BUNDLE", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeGroup { + pub semantics: SdpAttributeGroupSemantic, + pub tags: Vec<String>, +} + +impl fmt::Display for SdpAttributeGroup { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}{}", + self.semantics, + maybe_vector_to_string!(" {}", self.tags, " ") + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeMsid { + pub id: String, + pub appdata: Option<String>, +} + +impl fmt::Display for SdpAttributeMsid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id.fmt(f)?; + write_option_string!(f, " {}", self.appdata) + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +pub struct SdpAttributeMsidSemantic { + pub semantic: String, + pub msids: Vec<String>, +} + +impl fmt::Display for SdpAttributeMsidSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ", self.semantic)?; + match self.msids.len() { + 0 => "*".fmt(f), + _ => self.msids.join(" ").fmt(f), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRidParameters { + pub max_width: u32, + pub max_height: u32, + pub max_fps: u32, + pub max_fs: u32, + pub max_br: u32, + pub max_pps: u32, + + pub unknown: Vec<String>, +} + +impl fmt::Display for SdpAttributeRidParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + non_empty_string_vec![ + maybe_print_param("max-width=", self.max_width, 0), + maybe_print_param("max-height=", self.max_height, 0), + maybe_print_param("max-fps=", self.max_fps, 0), + maybe_print_param("max-fs=", self.max_fs, 0), + maybe_print_param("max-br=", self.max_br, 0), + maybe_print_param("max-pps=", self.max_pps, 0), + maybe_vector_to_string!("{}", self.unknown, ";") + ] + .join(";") + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRid { + pub id: String, + pub direction: SdpSingleDirection, + pub formats: Vec<u16>, + pub params: SdpAttributeRidParameters, + pub depends: Vec<String>, +} + +impl fmt::Display for SdpAttributeRid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{id} {direction}{format}", + id = self.id, + direction = self.direction, + format = match non_empty_string_vec![ + maybe_vector_to_string!("pt={}", self.formats, ","), + self.params.to_string(), + maybe_vector_to_string!("depends={}", self.depends, ",") + ] + .join(";") + .as_str() + { + "" => "".to_string(), + x => format!(" {x}"), + } + ) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeRtpmap { + pub payload_type: u8, + pub codec_name: String, + pub frequency: u32, + pub channels: Option<u32>, +} + +impl SdpAttributeRtpmap { + pub fn new(payload_type: u8, codec_name: String, frequency: u32) -> SdpAttributeRtpmap { + SdpAttributeRtpmap { + payload_type, + codec_name, + frequency, + channels: None, + } + } + + fn set_channels(&mut self, c: u32) { + self.channels = Some(c) + } +} + +impl fmt::Display for SdpAttributeRtpmap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{pt} {name}/{freq}", + pt = self.payload_type, + name = self.codec_name, + freq = self.frequency + )?; + write_option_string!(f, "/{}", self.channels) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttributeSetup { + Active, + Actpass, + Holdconn, + Passive, +} + +impl fmt::Display for SdpAttributeSetup { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeSetup::Active => "active", + SdpAttributeSetup::Actpass => "actpass", + SdpAttributeSetup::Holdconn => "holdconn", + SdpAttributeSetup::Passive => "passive", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub struct SdpAttributeSsrc { + pub id: u32, + pub attribute: Option<String>, + pub value: Option<String>, +} + +impl SdpAttributeSsrc { + pub fn new(id: u32) -> SdpAttributeSsrc { + SdpAttributeSsrc { + id, + attribute: None, + value: None, + } + } + + fn set_attribute(&mut self, a: &str) { + if a.find(':').is_none() { + self.attribute = Some(a.to_string()); + } else { + let v: Vec<&str> = a.splitn(2, ':').collect(); + self.attribute = Some(v[0].to_string()); + self.value = Some(v[1].to_string()); + } + } +} + +impl fmt::Display for SdpAttributeSsrc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id.fmt(f)?; + write_option_string!(f, " {}", self.attribute)?; + write_option_string!(f, ":{}", self.value) + } +} + +impl AnonymizingClone for SdpAttributeSsrc { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + Self { + id: self.id, + attribute: self.attribute.clone(), + value: self.attribute.as_ref().and_then(|attribute| { + match (attribute.to_lowercase().as_str(), &self.value) { + ("cname", Some(ref cname)) => Some(anon.mask_cname(cname.as_str())), + (_, Some(_)) => self.value.clone(), + (_, None) => None, + } + }), + } + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpSsrcGroupSemantic { + Duplication, // RFC7104 + FlowIdentification, // RFC5576 + ForwardErrorCorrection, // RFC5576 + ForwardErrorCorrectionFr, // RFC5956 + Sim, // not registered with IANA, but used in hangouts +} + +impl fmt::Display for SdpSsrcGroupSemantic { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpSsrcGroupSemantic::Duplication => "DUP", + SdpSsrcGroupSemantic::FlowIdentification => "FID", + SdpSsrcGroupSemantic::ForwardErrorCorrection => "FEC", + SdpSsrcGroupSemantic::ForwardErrorCorrectionFr => "FEC-FR", + SdpSsrcGroupSemantic::Sim => "SIM", + } + .fmt(f) + } +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "enhanced_debug", derive(Debug))] +pub enum SdpAttribute { + BundleOnly, + Candidate(SdpAttributeCandidate), + DtlsMessage(SdpAttributeDtlsMessage), + EndOfCandidates, + Extmap(SdpAttributeExtmap), + ExtmapAllowMixed, + Fingerprint(SdpAttributeFingerprint), + Fmtp(SdpAttributeFmtp), + Group(SdpAttributeGroup), + IceLite, + IceMismatch, + IceOptions(Vec<String>), + IcePacing(u64), + IcePwd(String), + IceUfrag(String), + Identity(String), + ImageAttr(SdpAttributeImageAttr), + Inactive, + Label(String), + MaxMessageSize(u64), + MaxPtime(u64), + Mid(String), + Msid(SdpAttributeMsid), + MsidSemantic(SdpAttributeMsidSemantic), + Ptime(u64), + Rid(SdpAttributeRid), + Recvonly, + RemoteCandidate(SdpAttributeRemoteCandidate), + Rtpmap(SdpAttributeRtpmap), + Rtcp(SdpAttributeRtcp), + Rtcpfb(SdpAttributeRtcpFb), + RtcpMux, + RtcpMuxOnly, // RFC8858 + RtcpRsize, + Sctpmap(SdpAttributeSctpmap), + SctpPort(u64), + Sendonly, + Sendrecv, + Setup(SdpAttributeSetup), + Simulcast(SdpAttributeSimulcast), + Ssrc(SdpAttributeSsrc), + SsrcGroup(SdpSsrcGroupSemantic, Vec<SdpAttributeSsrc>), +} + +impl SdpAttribute { + pub fn allowed_at_session_level(&self) -> bool { + match *self { + SdpAttribute::BundleOnly + | SdpAttribute::Candidate(..) + | SdpAttribute::Fmtp(..) + | SdpAttribute::IceMismatch + | SdpAttribute::ImageAttr(..) + | SdpAttribute::Label(..) + | SdpAttribute::MaxMessageSize(..) + | SdpAttribute::MaxPtime(..) + | SdpAttribute::Mid(..) + | SdpAttribute::Msid(..) + | SdpAttribute::Ptime(..) + | SdpAttribute::Rid(..) + | SdpAttribute::RemoteCandidate(..) + | SdpAttribute::Rtpmap(..) + | SdpAttribute::Rtcp(..) + | SdpAttribute::Rtcpfb(..) + | SdpAttribute::RtcpMux + | SdpAttribute::RtcpMuxOnly + | SdpAttribute::RtcpRsize + | SdpAttribute::Sctpmap(..) + | SdpAttribute::SctpPort(..) + | SdpAttribute::Simulcast(..) + | SdpAttribute::Ssrc(..) + | SdpAttribute::SsrcGroup(..) => false, + + SdpAttribute::DtlsMessage { .. } + | SdpAttribute::EndOfCandidates + | SdpAttribute::Extmap(..) + | SdpAttribute::ExtmapAllowMixed + | SdpAttribute::Fingerprint(..) + | SdpAttribute::Group(..) + | SdpAttribute::IceLite + | SdpAttribute::IceOptions(..) + | SdpAttribute::IcePacing(..) + | SdpAttribute::IcePwd(..) + | SdpAttribute::IceUfrag(..) + | SdpAttribute::Identity(..) + | SdpAttribute::Inactive + | SdpAttribute::MsidSemantic(..) + | SdpAttribute::Recvonly + | SdpAttribute::Sendonly + | SdpAttribute::Sendrecv + | SdpAttribute::Setup(..) => true, + } + } + + pub fn allowed_at_media_level(&self) -> bool { + match *self { + SdpAttribute::DtlsMessage { .. } + | SdpAttribute::Group(..) + | SdpAttribute::IceLite + | SdpAttribute::IcePacing(..) + | SdpAttribute::Identity(..) + | SdpAttribute::MsidSemantic(..) => false, + + SdpAttribute::BundleOnly + | SdpAttribute::Candidate(..) + | SdpAttribute::EndOfCandidates + | SdpAttribute::Extmap(..) + | SdpAttribute::ExtmapAllowMixed + | SdpAttribute::Fingerprint(..) + | SdpAttribute::Fmtp(..) + | SdpAttribute::IceMismatch + | SdpAttribute::IceOptions(..) + | SdpAttribute::IcePwd(..) + | SdpAttribute::IceUfrag(..) + | SdpAttribute::ImageAttr(..) + | SdpAttribute::Inactive + | SdpAttribute::Label(..) + | SdpAttribute::MaxMessageSize(..) + | SdpAttribute::MaxPtime(..) + | SdpAttribute::Mid(..) + | SdpAttribute::Msid(..) + | SdpAttribute::Ptime(..) + | SdpAttribute::Rid(..) + | SdpAttribute::Recvonly + | SdpAttribute::RemoteCandidate(..) + | SdpAttribute::Rtpmap(..) + | SdpAttribute::Rtcp(..) + | SdpAttribute::Rtcpfb(..) + | SdpAttribute::RtcpMux + | SdpAttribute::RtcpMuxOnly + | SdpAttribute::RtcpRsize + | SdpAttribute::Sctpmap(..) + | SdpAttribute::SctpPort(..) + | SdpAttribute::Sendonly + | SdpAttribute::Sendrecv + | SdpAttribute::Setup(..) + | SdpAttribute::Simulcast(..) + | SdpAttribute::Ssrc(..) + | SdpAttribute::SsrcGroup(..) => true, + } + } +} + +impl FromStr for SdpAttribute { + type Err = SdpParserInternalError; + + fn from_str(line: &str) -> Result<Self, Self::Err> { + let tokens: Vec<_> = line.splitn(2, ':').collect(); + let name = tokens[0].to_lowercase(); + let val = match tokens.get(1) { + Some(x) => x.trim(), + None => "", + }; + if tokens.len() > 1 { + match name.as_str() { + "bundle-only" | "end-of-candidates" | "extmap-allow-mixed" | "ice-lite" + | "ice-mismatch" | "inactive" | "recvonly" | "rtcp-mux" | "rtcp-mux-only" + | "rtcp-rsize" | "sendonly" | "sendrecv" => { + return Err(SdpParserInternalError::Generic(format!( + "{name} attribute is not allowed to have a value", + ))); + } + _ => (), + } + } + match name.as_str() { + "bundle-only" => Ok(SdpAttribute::BundleOnly), + "dtls-message" => parse_dtls_message(val), + "end-of-candidates" => Ok(SdpAttribute::EndOfCandidates), + "ice-lite" => Ok(SdpAttribute::IceLite), + "ice-mismatch" => Ok(SdpAttribute::IceMismatch), + "extmap-allow-mixed" => Ok(SdpAttribute::ExtmapAllowMixed), + "ice-pwd" => Ok(SdpAttribute::IcePwd(string_or_empty(val)?)), + "ice-ufrag" => Ok(SdpAttribute::IceUfrag(string_or_empty(val)?)), + "identity" => Ok(SdpAttribute::Identity(string_or_empty(val)?)), + "imageattr" => parse_image_attr(val), + "inactive" => Ok(SdpAttribute::Inactive), + "label" => Ok(SdpAttribute::Label(string_or_empty(val)?)), + "max-message-size" => Ok(SdpAttribute::MaxMessageSize(val.parse()?)), + "maxptime" => Ok(SdpAttribute::MaxPtime(val.parse()?)), + "mid" => Ok(SdpAttribute::Mid(string_or_empty(val)?)), + "msid-semantic" => parse_msid_semantic(val), + "ptime" => Ok(SdpAttribute::Ptime(val.parse()?)), + "ice-pacing" => parse_ice_pacing(val), + "rid" => parse_rid(val), + "recvonly" => Ok(SdpAttribute::Recvonly), + "rtcp-mux" => Ok(SdpAttribute::RtcpMux), + "rtcp-mux-only" => Ok(SdpAttribute::RtcpMuxOnly), + "rtcp-rsize" => Ok(SdpAttribute::RtcpRsize), + "sendonly" => Ok(SdpAttribute::Sendonly), + "sendrecv" => Ok(SdpAttribute::Sendrecv), + "ssrc-group" => parse_ssrc_group(val), + "sctp-port" => parse_sctp_port(val), + "candidate" => parse_candidate(val), + "extmap" => parse_extmap(val), + "fingerprint" => parse_fingerprint(val), + "fmtp" => parse_fmtp(val), + "group" => parse_group(val), + "ice-options" => parse_ice_options(val), + "msid" => parse_msid(val), + "remote-candidates" => parse_remote_candidates(val), + "rtpmap" => parse_rtpmap(val), + "rtcp" => parse_rtcp(val), + "rtcp-fb" => parse_rtcp_fb(val), + "sctpmap" => parse_sctpmap(val), + "setup" => parse_setup(val), + "simulcast" => parse_simulcast(val), + "ssrc" => parse_ssrc(val), + _ => Err(SdpParserInternalError::Unsupported(format!( + "Unknown attribute type {name}", + ))), + } + } +} + +impl fmt::Display for SdpAttribute { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let attr_type_name = SdpAttributeType::from(self).to_string(); + let attr_to_string = |attr_str: String| attr_type_name + ":" + &attr_str; + match *self { + SdpAttribute::BundleOnly => SdpAttributeType::BundleOnly.to_string(), + SdpAttribute::Candidate(ref a) => attr_to_string(a.to_string()), + SdpAttribute::DtlsMessage(ref a) => attr_to_string(a.to_string()), + SdpAttribute::EndOfCandidates => SdpAttributeType::EndOfCandidates.to_string(), + SdpAttribute::Extmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::ExtmapAllowMixed => SdpAttributeType::ExtmapAllowMixed.to_string(), + SdpAttribute::Fingerprint(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Fmtp(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Group(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IceLite => SdpAttributeType::IceLite.to_string(), + SdpAttribute::IceMismatch => SdpAttributeType::IceMismatch.to_string(), + SdpAttribute::IceOptions(ref a) => attr_to_string(a.join(" ")), + SdpAttribute::IcePacing(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IcePwd(ref a) => attr_to_string(a.to_string()), + SdpAttribute::IceUfrag(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Identity(ref a) => attr_to_string(a.to_string()), + SdpAttribute::ImageAttr(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Inactive => SdpAttributeType::Inactive.to_string(), + SdpAttribute::Label(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MaxMessageSize(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MaxPtime(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Mid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Msid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::MsidSemantic(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Ptime(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rid(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Recvonly => SdpAttributeType::Recvonly.to_string(), + SdpAttribute::RemoteCandidate(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtpmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtcp(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Rtcpfb(ref a) => attr_to_string(a.to_string()), + SdpAttribute::RtcpMux => SdpAttributeType::RtcpMux.to_string(), + SdpAttribute::RtcpMuxOnly => SdpAttributeType::RtcpMuxOnly.to_string(), + SdpAttribute::RtcpRsize => SdpAttributeType::RtcpRsize.to_string(), + SdpAttribute::Sctpmap(ref a) => attr_to_string(a.to_string()), + SdpAttribute::SctpPort(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Sendonly => SdpAttributeType::Sendonly.to_string(), + SdpAttribute::Sendrecv => SdpAttributeType::Sendrecv.to_string(), + SdpAttribute::Setup(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Simulcast(ref a) => attr_to_string(a.to_string()), + SdpAttribute::Ssrc(ref a) => attr_to_string(a.to_string()), + SdpAttribute::SsrcGroup(ref a, ref ssrcs) => { + let stringified_ssrcs: Vec<String> = + ssrcs.iter().map(|ssrc| ssrc.to_string()).collect(); + attr_to_string(a.to_string()) + " " + &stringified_ssrcs.join(" ") + } + } + .fmt(f) + } +} + +impl AnonymizingClone for SdpAttribute { + fn masked_clone(&self, anon: &mut StatefulSdpAnonymizer) -> Self { + match self { + SdpAttribute::Candidate(i) => SdpAttribute::Candidate(i.masked_clone(anon)), + SdpAttribute::Fingerprint(i) => SdpAttribute::Fingerprint(i.masked_clone(anon)), + SdpAttribute::IcePwd(i) => SdpAttribute::IcePwd(anon.mask_ice_password(i)), + SdpAttribute::IceUfrag(i) => SdpAttribute::IceUfrag(anon.mask_ice_user(i)), + SdpAttribute::RemoteCandidate(i) => SdpAttribute::RemoteCandidate(i.masked_clone(anon)), + SdpAttribute::Ssrc(i) => SdpAttribute::Ssrc(i.masked_clone(anon)), + _ => self.clone(), + } + } +} + +#[derive(Clone, PartialEq)] +pub enum SdpAttributeType { + BundleOnly, + Candidate, + DtlsMessage, + EndOfCandidates, + Extmap, + ExtmapAllowMixed, + Fingerprint, + Fmtp, + Group, + IceLite, + IceMismatch, + IceOptions, + IcePacing, + IcePwd, + IceUfrag, + Identity, + ImageAttr, + Inactive, + Label, + MaxMessageSize, + MaxPtime, + Mid, + Msid, + MsidSemantic, + Ptime, + Rid, + Recvonly, + RemoteCandidate, + Rtpmap, + Rtcp, + Rtcpfb, + RtcpMux, + RtcpMuxOnly, + RtcpRsize, + Sctpmap, + SctpPort, + Sendonly, + Sendrecv, + Setup, + Simulcast, + Ssrc, + SsrcGroup, +} + +impl<'a> From<&'a SdpAttribute> for SdpAttributeType { + fn from(other: &SdpAttribute) -> Self { + match *other { + SdpAttribute::BundleOnly { .. } => SdpAttributeType::BundleOnly, + SdpAttribute::Candidate { .. } => SdpAttributeType::Candidate, + SdpAttribute::DtlsMessage { .. } => SdpAttributeType::DtlsMessage, + SdpAttribute::EndOfCandidates { .. } => SdpAttributeType::EndOfCandidates, + SdpAttribute::Extmap { .. } => SdpAttributeType::Extmap, + SdpAttribute::ExtmapAllowMixed { .. } => SdpAttributeType::ExtmapAllowMixed, + SdpAttribute::Fingerprint { .. } => SdpAttributeType::Fingerprint, + SdpAttribute::Fmtp { .. } => SdpAttributeType::Fmtp, + SdpAttribute::Group { .. } => SdpAttributeType::Group, + SdpAttribute::IceLite { .. } => SdpAttributeType::IceLite, + SdpAttribute::IceMismatch { .. } => SdpAttributeType::IceMismatch, + SdpAttribute::IceOptions { .. } => SdpAttributeType::IceOptions, + SdpAttribute::IcePacing { .. } => SdpAttributeType::IcePacing, + SdpAttribute::IcePwd { .. } => SdpAttributeType::IcePwd, + SdpAttribute::IceUfrag { .. } => SdpAttributeType::IceUfrag, + SdpAttribute::Identity { .. } => SdpAttributeType::Identity, + SdpAttribute::ImageAttr { .. } => SdpAttributeType::ImageAttr, + SdpAttribute::Inactive { .. } => SdpAttributeType::Inactive, + SdpAttribute::Label { .. } => SdpAttributeType::Label, + SdpAttribute::MaxMessageSize { .. } => SdpAttributeType::MaxMessageSize, + SdpAttribute::MaxPtime { .. } => SdpAttributeType::MaxPtime, + SdpAttribute::Mid { .. } => SdpAttributeType::Mid, + SdpAttribute::Msid { .. } => SdpAttributeType::Msid, + SdpAttribute::MsidSemantic { .. } => SdpAttributeType::MsidSemantic, + SdpAttribute::Ptime { .. } => SdpAttributeType::Ptime, + SdpAttribute::Rid { .. } => SdpAttributeType::Rid, + SdpAttribute::Recvonly { .. } => SdpAttributeType::Recvonly, + SdpAttribute::RemoteCandidate { .. } => SdpAttributeType::RemoteCandidate, + SdpAttribute::Rtcp { .. } => SdpAttributeType::Rtcp, + SdpAttribute::Rtcpfb { .. } => SdpAttributeType::Rtcpfb, + SdpAttribute::RtcpMux { .. } => SdpAttributeType::RtcpMux, + SdpAttribute::RtcpMuxOnly { .. } => SdpAttributeType::RtcpMuxOnly, + SdpAttribute::RtcpRsize { .. } => SdpAttributeType::RtcpRsize, + SdpAttribute::Rtpmap { .. } => SdpAttributeType::Rtpmap, + SdpAttribute::Sctpmap { .. } => SdpAttributeType::Sctpmap, + SdpAttribute::SctpPort { .. } => SdpAttributeType::SctpPort, + SdpAttribute::Sendonly { .. } => SdpAttributeType::Sendonly, + SdpAttribute::Sendrecv { .. } => SdpAttributeType::Sendrecv, + SdpAttribute::Setup { .. } => SdpAttributeType::Setup, + SdpAttribute::Simulcast { .. } => SdpAttributeType::Simulcast, + SdpAttribute::Ssrc { .. } => SdpAttributeType::Ssrc, + SdpAttribute::SsrcGroup { .. } => SdpAttributeType::SsrcGroup, + } + } +} + +impl fmt::Display for SdpAttributeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SdpAttributeType::BundleOnly => "bundle-only", + SdpAttributeType::Candidate => "candidate", + SdpAttributeType::DtlsMessage => "dtls-message", + SdpAttributeType::EndOfCandidates => "end-of-candidates", + SdpAttributeType::Extmap => "extmap", + SdpAttributeType::ExtmapAllowMixed => "extmap-allow-mixed", + SdpAttributeType::Fingerprint => "fingerprint", + SdpAttributeType::Fmtp => "fmtp", + SdpAttributeType::Group => "group", + SdpAttributeType::IceLite => "ice-lite", + SdpAttributeType::IceMismatch => "ice-mismatch", + SdpAttributeType::IceOptions => "ice-options", + SdpAttributeType::IcePacing => "ice-pacing", + SdpAttributeType::IcePwd => "ice-pwd", + SdpAttributeType::IceUfrag => "ice-ufrag", + SdpAttributeType::Identity => "identity", + SdpAttributeType::ImageAttr => "imageattr", + SdpAttributeType::Inactive => "inactive", + SdpAttributeType::Label => "label", + SdpAttributeType::MaxMessageSize => "max-message-size", + SdpAttributeType::MaxPtime => "maxptime", + SdpAttributeType::Mid => "mid", + SdpAttributeType::Msid => "msid", + SdpAttributeType::MsidSemantic => "msid-semantic", + SdpAttributeType::Ptime => "ptime", + SdpAttributeType::Rid => "rid", + SdpAttributeType::Recvonly => "recvonly", + SdpAttributeType::RemoteCandidate => "remote-candidates", + SdpAttributeType::Rtpmap => "rtpmap", + SdpAttributeType::Rtcp => "rtcp", + SdpAttributeType::Rtcpfb => "rtcp-fb", + SdpAttributeType::RtcpMux => "rtcp-mux", + SdpAttributeType::RtcpMuxOnly => "rtcp-mux-only", + SdpAttributeType::RtcpRsize => "rtcp-rsize", + SdpAttributeType::Sctpmap => "sctpmap", + SdpAttributeType::SctpPort => "sctp-port", + SdpAttributeType::Sendonly => "sendonly", + SdpAttributeType::Sendrecv => "sendrecv", + SdpAttributeType::Setup => "setup", + SdpAttributeType::Simulcast => "simulcast", + SdpAttributeType::Ssrc => "ssrc", + SdpAttributeType::SsrcGroup => "ssrc-group", + } + .fmt(f) + } +} + +fn string_or_empty(to_parse: &str) -> Result<String, SdpParserInternalError> { + if to_parse.is_empty() { + Err(SdpParserInternalError::Generic( + "This attribute is required to have a value".to_string(), + )) + } else { + Ok(to_parse.to_string()) + } +} + +fn parse_payload_type(to_parse: &str) -> Result<SdpAttributePayloadType, SdpParserInternalError> { + Ok(match to_parse { + "*" => SdpAttributePayloadType::Wildcard, + _ => SdpAttributePayloadType::PayloadType(to_parse.parse::<u8>()?), + }) +} + +fn parse_single_direction(to_parse: &str) -> Result<SdpSingleDirection, SdpParserInternalError> { + match to_parse { + "send" => Ok(SdpSingleDirection::Send), + "recv" => Ok(SdpSingleDirection::Recv), + x => Err(SdpParserInternalError::Generic(format!( + "Unknown direction description found: '{x:}'" + ))), + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc-group, RFC5576 +//------------------------------------------------------------------------- +// a=ssrc-group:<semantics> <ssrc-id> ... +fn parse_ssrc_group(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let semantics = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Ssrc group attribute is missing semantics".to_string(), + )); + } + Some(x) => match x.to_uppercase().as_ref() { + "DUP" => SdpSsrcGroupSemantic::Duplication, + "FID" => SdpSsrcGroupSemantic::FlowIdentification, + "FEC" => SdpSsrcGroupSemantic::ForwardErrorCorrection, + "FEC-FR" => SdpSsrcGroupSemantic::ForwardErrorCorrectionFr, + "SIM" => SdpSsrcGroupSemantic::Sim, + unknown => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown ssrc semantic '{unknown:?}' found" + ))); + } + }, + }; + + let mut ssrcs = Vec::new(); + for token in tokens { + match parse_ssrc(token) { + Ok(SdpAttribute::Ssrc(ssrc)) => { + ssrcs.push(ssrc); + } + Err(err) => { + return Err(err); + } + _ => unreachable!(), + } + } + + if ssrcs.is_empty() { + return Err(SdpParserInternalError::Generic( + "Ssrc group must contain at least one ssrc".to_string(), + )); + } + + Ok(SdpAttribute::SsrcGroup(semantics, ssrcs)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=sctp-port, draft-ietf-mmusic-sctp-sdp-26#section-15.2.1 +//------------------------------------------------------------------------- +// no ABNF given +fn parse_sctp_port(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let port = to_parse.parse()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic(format!( + "Sctpport port {port} can only be a bit 16bit number" + ))); + } + Ok(SdpAttribute::SctpPort(port)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=candidate, RFC5245 +//------------------------------------------------------------------------- +// +// candidate-attribute = "candidate" ":" foundation SP component-id SP +// transport SP +// priority SP +// connection-address SP ;from RFC 4566 +// port ;port from RFC 4566 +// SP cand-type +// [SP rel-addr] +// [SP rel-port] +// *(SP extension-att-name SP +// extension-att-value) +// foundation = 1*32ice-char +// component-id = 1*5DIGIT +// transport = "UDP" / transport-extension +// transport-extension = token ; from RFC 3261 +// priority = 1*10DIGIT +// cand-type = "typ" SP candidate-types +// candidate-types = "host" / "srflx" / "prflx" / "relay" / token +// rel-addr = "raddr" SP connection-address +// rel-port = "rport" SP port +// extension-att-name = byte-string ;from RFC 4566 +// extension-att-value = byte-string +// ice-char = ALPHA / DIGIT / "+" / "/" +fn parse_candidate(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() < 8 { + return Err(SdpParserInternalError::Generic( + "Candidate needs to have minimum eigth tokens".to_string(), + )); + } + let component = tokens[1].parse::<u32>()?; + let transport = match tokens[2].to_lowercase().as_ref() { + "udp" => SdpAttributeCandidateTransport::Udp, + "tcp" => SdpAttributeCandidateTransport::Tcp, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknonw candidate transport value".to_string(), + )); + } + }; + let priority = tokens[3].parse::<u64>()?; + let address = Address::from_str(tokens[4])?; + let port = tokens[5].parse::<u32>()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "ICE candidate port can only be a bit 16bit number".to_string(), + )); + } + match tokens[6].to_lowercase().as_ref() { + "typ" => (), + _ => { + return Err(SdpParserInternalError::Generic( + "Candidate attribute token must be 'typ'".to_string(), + )); + } + }; + let cand_type = match tokens[7].to_lowercase().as_ref() { + "host" => SdpAttributeCandidateType::Host, + "srflx" => SdpAttributeCandidateType::Srflx, + "prflx" => SdpAttributeCandidateType::Prflx, + "relay" => SdpAttributeCandidateType::Relay, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknow candidate type value".to_string(), + )); + } + }; + let mut cand = SdpAttributeCandidate::new( + tokens[0].to_string(), + component, + transport, + priority, + address, + port, + cand_type, + ); + if tokens.len() > 8 { + let mut index = 8; + while tokens.len() > index + 1 { + match tokens[index].to_lowercase().as_ref() { + "generation" => { + let generation = tokens[index + 1].parse::<u32>()?; + cand.set_generation(generation); + index += 2; + } + "network-cost" => { + let cost = tokens[index + 1].parse::<u32>()?; + cand.set_network_cost(cost); + index += 2; + } + "raddr" => { + let addr = parse_unicast_address(tokens[index + 1])?; + cand.set_remote_address(addr); + index += 2; + } + "rport" => { + let port = tokens[index + 1].parse::<u32>()?; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "ICE candidate rport can only be a bit 16bit number".to_string(), + )); + } + cand.set_remote_port(port); + index += 2; + } + "tcptype" => { + cand.set_tcp_type(match tokens[index + 1].to_lowercase().as_ref() { + "active" => SdpAttributeCandidateTcpType::Active, + "passive" => SdpAttributeCandidateTcpType::Passive, + "so" => SdpAttributeCandidateTcpType::Simultaneous, + _ => { + return Err(SdpParserInternalError::Generic( + "Unknown tcptype value in candidate line".to_string(), + )); + } + }); + index += 2; + } + "ufrag" => { + let ufrag = tokens[index + 1]; + cand.set_ufrag(ufrag.to_string()); + index += 2; + } + _ => { + let name = tokens[index].to_string(); + let value = tokens[index + 1].to_string(); + cand.add_unknown_extension(name, value); + index += 2; + } + }; + } + if tokens.len() > index { + return Err(SdpParserInternalError::Unsupported( + "Ice candidate extension name without value".to_string(), + )); + } + } + Ok(SdpAttribute::Candidate(cand)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=dtls-message, draft-rescorla-dtls-in-sdp +//------------------------------------------------------------------------- +// attribute =/ dtls-message-attribute +// +// dtls-message-attribute = "dtls-message" ":" role SP value +// +// role = "client" / "server" +// +// value = 1*(ALPHA / DIGIT / "+" / "/" / "=" ) +// ; base64 encoded message +fn parse_dtls_message(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split(' ').collect(); + + if tokens.len() != 2 { + return Err(SdpParserInternalError::Generic( + "dtls-message must have a role token and a value token.".to_string(), + )); + } + + Ok(SdpAttribute::DtlsMessage(match tokens[0] { + "client" => SdpAttributeDtlsMessage::Client(tokens[1].to_string()), + "server" => SdpAttributeDtlsMessage::Server(tokens[1].to_string()), + e => { + return Err(SdpParserInternalError::Generic(format!( + "dtls-message has unknown role token '{e}'" + ))); + } + })) +} + +// Returns true if valid byte-string as defined by RFC 4566 +// https://tools.ietf.org/html/rfc4566 +fn valid_byte_string(input: &str) -> bool { + !(input.contains(0x00 as char) || input.contains(0x0A as char) || input.contains(0x0D as char)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=extmap, RFC5285 +//------------------------------------------------------------------------- +// RFC5285 +// extmap = mapentry SP extensionname [SP extensionattributes] +// +// extensionname = URI +// +// direction = "sendonly" / "recvonly" / "sendrecv" / "inactive" +// +// mapentry = "extmap:" 1*5DIGIT ["/" direction] +// +// extensionattributes = byte-string +// +// URI = <Defined in RFC 3986> +// +// byte-string = <Defined in RFC 4566> +// +// SP = <Defined in RFC 5234> +// +// DIGIT = <Defined in RFC 5234> +fn parse_extmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() < 2 { + return Err(SdpParserInternalError::Generic( + "Extmap needs to have at least two tokens".to_string(), + )); + } + let id: u16; + let mut direction: Option<SdpAttributeDirection> = None; + if tokens[0].find('/').is_none() { + id = tokens[0].parse::<u16>()?; + } else { + let id_dir: Vec<&str> = tokens[0].splitn(2, '/').collect(); + id = id_dir[0].parse::<u16>()?; + direction = Some(match id_dir[1].to_lowercase().as_ref() { + "recvonly" => SdpAttributeDirection::Recvonly, + "sendonly" => SdpAttributeDirection::Sendonly, + "sendrecv" => SdpAttributeDirection::Sendrecv, + _ => { + return Err(SdpParserInternalError::Generic( + "Unsupported direction in extmap value".to_string(), + )); + } + }) + } + // Consider replacing to_parse.split_whitespace() above with splitn on space. Would we want the pattern to split on any amout of any kind of whitespace? + let extension_attributes = if tokens.len() == 2 { + None + } else { + let ext_string: String = tokens[2..].join(" "); + if !valid_byte_string(&ext_string) { + return Err(SdpParserInternalError::Generic( + "Illegal character in extmap extension attributes".to_string(), + )); + } + Some(ext_string) + }; + Ok(SdpAttribute::Extmap(SdpAttributeExtmap { + id, + direction, + url: tokens[1].to_string(), + extension_attributes, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=fingerprint, RFC4572 +//------------------------------------------------------------------------- +// fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint +// +// hash-func = "sha-1" / "sha-224" / "sha-256" / +// "sha-384" / "sha-512" / +// "md5" / "md2" / token +// ; Additional hash functions can only come +// ; from updates to RFC 3279 +// +// fingerprint = 2UHEX *(":" 2UHEX) +// ; Each byte in upper-case hex, separated +// ; by colons. +// +// UHEX = DIGIT / %x41-46 ; A-F uppercase +fn parse_fingerprint(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() != 2 { + return Err(SdpParserInternalError::Generic( + "Fingerprint needs to have two tokens".to_string(), + )); + } + + let hash_algorithm = SdpAttributeFingerprintHashType::try_from_name(tokens[0])?; + let bytes = hash_algorithm.parse_octets(tokens[1])?; + let fingerprint = SdpAttributeFingerprint::try_from((hash_algorithm, bytes))?; + Ok(SdpAttribute::Fingerprint(fingerprint)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=fmtp, RFC4566, RFC5576 +//------------------------------------------------------------------------- +// a=fmtp:<format> <format specific parameters> +fn parse_fmtp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(2, ' ').collect(); + + // Support space seperated parameter blocks + if tokens.len() < 2 { + return Err(SdpParserInternalError::Unsupported( + "Fmtp attributes require a payload type and a parameter block.".to_string(), + )); + } + + let payload_token = tokens[0]; + + // Default initiliaze SdpAttributeFmtpParameters + let mut parameters = SdpAttributeFmtpParameters { + packetization_mode: 0, + level_asymmetry_allowed: false, + profile_level_id: 0x0042_0010, + max_fs: 0, + max_cpb: 0, + max_dpb: 0, + max_br: 0, + max_mbps: 0, + usedtx: false, + stereo: false, + useinbandfec: false, + cbr: false, + max_fr: 0, + maxplaybackrate: 48000, + maxaveragebitrate: 0, + ptime: 0, + minptime: 0, + maxptime: 0, + encodings: Vec::new(), + dtmf_tones: "".to_string(), + rtx: None, + unknown_tokens: Vec::new(), + }; + + for parameter_token in tokens[1..].iter() { + if parameter_token.contains('=') { + // Permit Leading/Trailing/Inner ';' by filtering out empty splits + let parameter_tokens: Vec<&str> = parameter_token + .split(';') + .filter(|token| !token.is_empty()) + .collect(); + for parameter_token in parameter_tokens.iter() { + let name_value_pair: Vec<&str> = parameter_token.splitn(2, '=').collect(); + if name_value_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "A fmtp parameter must be either a telephone event, a parameter list or a red codec list" + .to_string(), + )); + } + + let parse_bool = + |val: &str, param_name: &str| -> Result<bool, SdpParserInternalError> { + match val.parse::<u8>()? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(SdpParserInternalError::Generic(format!( + "The fmtp parameter '{param_name:}' must be 0 or 1" + ))), + } + }; + + let parameter_name = name_value_pair[0]; + let parameter_val = name_value_pair[1]; + + match parameter_name.to_uppercase().as_str() { + // H264 + "PROFILE-LEVEL-ID" => parameters.profile_level_id = match u32::from_str_radix( + parameter_val, + 16, + )? { + x @ 0..=0x00ff_ffff => x, + _ => return Err(SdpParserInternalError::Generic( + "The fmtp parameter 'profile-level-id' must be in range [0,0xffffff]" + .to_string(), + )), + }, + "PACKETIZATION-MODE" => { + parameters.packetization_mode = match parameter_val.parse::<u32>()? { + x @ 0..=2 => x, + _ => { + return Err(SdpParserInternalError::Generic( + "The fmtp parameter 'packetization-mode' must be 0,1 or 2" + .to_string(), + )); + } + } + } + "LEVEL-ASYMMETRY-ALLOWED" => { + parameters.level_asymmetry_allowed = + parse_bool(parameter_val, "level-asymmetry-allowed")? + } + "MAX-MBPS" => parameters.max_mbps = parameter_val.parse::<u32>()?, + "MAX-FS" => parameters.max_fs = parameter_val.parse::<u32>()?, + "MAX-CPB" => parameters.max_cpb = parameter_val.parse::<u32>()?, + "MAX-DPB" => parameters.max_dpb = parameter_val.parse::<u32>()?, + "MAX-BR" => parameters.max_br = parameter_val.parse::<u32>()?, + + // VP8 and VP9 + "MAX-FR" => parameters.max_fr = parameter_val.parse::<u32>()?, + + //Opus https://tools.ietf.org/html/rfc7587 + "MAXPLAYBACKRATE" => { + parameters.maxplaybackrate = parameter_val.parse::<u32>()? + } + "MAXAVERAGEBITRATE" => { + parameters.maxaveragebitrate = parameter_val.parse::<u32>()? + } + "PTIME" => parameters.ptime = parameter_val.parse::<u32>()?, + "MAXPTIME" => parameters.maxptime = parameter_val.parse::<u32>()?, + "MINPTIME" => parameters.minptime = parameter_val.parse::<u32>()?, + "USEDTX" => parameters.usedtx = parse_bool(parameter_val, "usedtx")?, + "STEREO" => parameters.stereo = parse_bool(parameter_val, "stereo")?, + "USEINBANDFEC" => { + parameters.useinbandfec = parse_bool(parameter_val, "useinbandfec")? + } + "CBR" => parameters.cbr = parse_bool(parameter_val, "cbr")?, + "APT" => { + parameters.rtx = Some(RtxFmtpParameters { + apt: parameter_val.parse::<u8>()?, + rtx_time: None, + }) + } + "RTX-TIME" => { + if let Some(ref mut rtx) = parameters.rtx { + rtx.rtx_time = Some(parameter_val.parse::<u32>()?) + } else { + return Err(SdpParserInternalError::Generic( + "RTX codec must have an APT field".to_string(), + )); + } + } + _ => parameters + .unknown_tokens + .push((*parameter_token).to_string()), + } + } + } else if parameter_token.contains('/') { + let encodings: Vec<&str> = parameter_token.split('/').collect(); + + for encoding in encodings { + match encoding.parse::<u8>()? { + x @ 0..=128 => parameters.encodings.push(x), + _ => { + return Err(SdpParserInternalError::Generic( + "Red codec must be in range [0,128]".to_string(), + )); + } + } + } + } else { + // This is the case for the 'telephone-event' codec + let dtmf_tones: Vec<&str> = parameter_token.split(',').collect(); + let mut dtmf_tone_is_ok = true; + + // This closure verifies the output of some_number_as_string.parse::<u8>().ok() like calls + let validate_digits = |digit_option: Option<u8>| -> Option<u8> { + match digit_option { + Some(x) => match x { + 0..=100 => Some(x), + _ => None, + }, + None => None, + } + }; + + // This loop does some sanity checking on the passed dtmf tones + for dtmf_tone in dtmf_tones { + let dtmf_tone_range: Vec<&str> = dtmf_tone.splitn(2, '-').collect(); + + dtmf_tone_is_ok = match dtmf_tone_range.len() { + // In this case the dtmf tone is a range + 2 => { + match validate_digits(dtmf_tone_range[0].parse::<u8>().ok()) { + Some(l) => match validate_digits(dtmf_tone_range[1].parse::<u8>().ok()) + { + Some(u) => { + // Check that the first part of the range is smaller than the second part + l < u + } + None => false, + }, + None => false, + } + } + // In this case the dtmf tone is a single tone + 1 => validate_digits(dtmf_tone.parse::<u8>().ok()).is_some(), + _ => false, + }; + + if !dtmf_tone_is_ok { + break; + } + } + + // Set the parsed dtmf tones or in case the parsing was insuccessfull, set it to the default "0-15" + parameters.dtmf_tones = if dtmf_tone_is_ok { + (*parameter_token).to_string() + } else { + "0-15".to_string() + }; + } + } + Ok(SdpAttribute::Fmtp(SdpAttributeFmtp { + payload_type: payload_token.parse::<u8>()?, + parameters, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=group, RFC5888 +//------------------------------------------------------------------------- +// group-attribute = "a=group:" semantics +// *(SP identification-tag) +// semantics = "LS" / "FID" / semantics-extension +// semantics-extension = token +// identification-tag = token +fn parse_group(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let semantics = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Group attribute is missing semantics token".to_string(), + )); + } + Some(x) => match x.to_uppercase().as_ref() { + "LS" => SdpAttributeGroupSemantic::LipSynchronization, + "FID" => SdpAttributeGroupSemantic::FlowIdentification, + "SRF" => SdpAttributeGroupSemantic::SingleReservationFlow, + "ANAT" => SdpAttributeGroupSemantic::AlternateNetworkAddressType, + "FEC" => SdpAttributeGroupSemantic::ForwardErrorCorrection, + "DDP" => SdpAttributeGroupSemantic::DecodingDependency, + "BUNDLE" => SdpAttributeGroupSemantic::Bundle, + unknown => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown group semantic '{unknown:?}' found", + ))); + } + }, + }; + Ok(SdpAttribute::Group(SdpAttributeGroup { + semantics, + tags: tokens.map(ToString::to_string).collect(), + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ice-options, draft-ietf-mmusic-ice-sip-sdp +//------------------------------------------------------------------------- +// ice-options = "ice-options:" ice-option-tag +// 0*(SP ice-option-tag) +// ice-option-tag = 1*ice-char +fn parse_ice_options(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + if to_parse.is_empty() { + return Err(SdpParserInternalError::Generic( + "ice-options is required to have a value".to_string(), + )); + } + Ok(SdpAttribute::IceOptions( + to_parse + .split_whitespace() + .map(ToString::to_string) + .collect(), + )) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ice-pacing, draft-ietf-mmusic-ice-sip-sdp +//------------------------------------------------------------------------- +// ice-pacing-att = "ice-pacing:" pacing-value +// pacing-value = 1*10DIGIT +fn parse_ice_pacing(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let parsed = to_parse.parse::<u64>()?; + if parsed >= 1_00_00_00_00_00 { + return Err(SdpParserInternalError::Generic( + "ice-pacing value is not a 10 digit integer".to_string(), + )); + } + Ok(SdpAttribute::IcePacing(parsed)) +} + +fn parse_imageattr_tokens(to_parse: &str, separator: char) -> Vec<String> { + let mut tokens = Vec::new(); + let mut open_braces_counter = 0; + let mut current_tokens = Vec::new(); + + for token in to_parse.split(separator) { + if token.contains('[') { + open_braces_counter += 1; + } + if token.contains(']') { + open_braces_counter -= 1; + } + + current_tokens.push(token.to_string()); + + if open_braces_counter == 0 { + tokens.push(current_tokens.join(&separator.to_string())); + current_tokens = Vec::new(); + } + } + + tokens +} + +fn parse_imagettr_braced_token(to_parse: &str) -> Option<&str> { + if !to_parse.starts_with('[') { + return None; + } + + if !to_parse.ends_with(']') { + return None; + } + + Some(&to_parse[1..to_parse.len() - 1]) +} + +fn parse_image_attr_xyrange( + to_parse: &str, +) -> Result<SdpAttributeImageAttrXyRange, SdpParserInternalError> { + if to_parse.starts_with('[') { + let value_tokens = parse_imagettr_braced_token(to_parse).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's xyrange has no closing tag ']'".to_string(), + ) + })?; + + if to_parse.contains(':') { + // Range values + let range_tokens: Vec<&str> = value_tokens.split(':').collect(); + + if range_tokens.len() == 3 { + Ok(SdpAttributeImageAttrXyRange::Range( + range_tokens[0].parse::<u32>()?, + range_tokens[2].parse::<u32>()?, + Some(range_tokens[1].parse::<u32>()?), + )) + } else if range_tokens.len() == 2 { + Ok(SdpAttributeImageAttrXyRange::Range( + range_tokens[0].parse::<u32>()?, + range_tokens[1].parse::<u32>()?, + None, + )) + } else { + Err(SdpParserInternalError::Generic( + "imageattr's xyrange must contain 2 or 3 fields".to_string(), + )) + } + } else { + // Discrete values + let values = value_tokens + .split(',') + .map(str::parse::<u32>) + .collect::<Result<Vec<u32>, _>>()?; + + if values.len() < 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's discrete value list must have at least two elements".to_string(), + )); + } + + Ok(SdpAttributeImageAttrXyRange::DiscreteValues(values)) + } + } else { + Ok(SdpAttributeImageAttrXyRange::DiscreteValues(vec![ + to_parse.parse::<u32>()? + ])) + } +} + +fn parse_image_attr_set( + to_parse: &str, +) -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> { + let mut tokens = parse_imageattr_tokens(to_parse, ',').into_iter(); + + let x_token = tokens.next().ok_or_else(|| { + SdpParserInternalError::Generic("imageattr set is missing the 'x=' token".to_string()) + })?; + if !x_token.starts_with("x=") { + return Err(SdpParserInternalError::Generic( + "The first token in an imageattr set must begin with 'x='".to_string(), + )); + } + let x = parse_image_attr_xyrange(&x_token[2..])?; + + let y_token = tokens.next().ok_or_else(|| { + SdpParserInternalError::Generic("imageattr set is missing the 'y=' token".to_string()) + })?; + if !y_token.starts_with("y=") { + return Err(SdpParserInternalError::Generic( + "The second token in an imageattr set must begin with 'y='".to_string(), + )); + } + let y = parse_image_attr_xyrange(&y_token[2..])?; + + let mut sar = None; + let mut par = None; + let mut q = None; + + let parse_ps_range = |resolution_range: &str| -> Result<(f32, f32), SdpParserInternalError> { + let minmax_pair: Vec<&str> = resolution_range.split('-').collect(); + + if minmax_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's par and sar ranges must have two components".to_string(), + )); + } + + let min = minmax_pair[0].parse::<f32>()?; + let max = minmax_pair[1].parse::<f32>()?; + + if min >= max { + return Err(SdpParserInternalError::Generic( + "In imageattr's par and sar ranges, first must be < than the second".to_string(), + )); + } + + Ok((min, max)) + }; + + for current_token in tokens { + if let Some(value_token) = current_token.strip_prefix("sar=") { + if value_token.starts_with('[') { + let sar_values = parse_imagettr_braced_token(value_token).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's sar value is missing closing tag ']'".to_string(), + ) + })?; + + if value_token.contains('-') { + // Range + let range = parse_ps_range(sar_values)?; + sar = Some(SdpAttributeImageAttrSRange::Range(range.0, range.1)) + } else if value_token.contains(',') { + // Discrete values + let values = sar_values + .split(',') + .map(str::parse::<f32>) + .collect::<Result<Vec<f32>, _>>()?; + + if values.len() < 2 { + return Err(SdpParserInternalError::Generic( + "imageattr's sar discrete value list must have at least two values" + .to_string(), + )); + } + + // Check that all the values are ascending + let mut last_value = 0.0; + for value in &values { + if last_value >= *value { + return Err(SdpParserInternalError::Generic( + "imageattr's sar discrete value list must contain ascending values" + .to_string(), + )); + } + last_value = *value; + } + sar = Some(SdpAttributeImageAttrSRange::DiscreteValues(values)) + } + } else { + sar = Some(SdpAttributeImageAttrSRange::DiscreteValues(vec![ + value_token.parse::<f32>()?, + ])) + } + } else if let Some(braced_value_token) = current_token.strip_prefix("par=") { + if !braced_value_token.starts_with('[') { + return Err(SdpParserInternalError::Generic( + "imageattr's par value must start with '['".to_string(), + )); + } + + let par_values = parse_imagettr_braced_token(braced_value_token).ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's par value must be enclosed with ']'".to_string(), + ) + })?; + let range = parse_ps_range(par_values)?; + par = Some(SdpAttributeImageAttrPRange { + min: range.0, + max: range.1, + }) + } else if let Some(qval) = current_token.strip_prefix("q=") { + q = Some(qval.parse::<f32>()?); + } + } + + Ok(SdpAttributeImageAttrSet { x, y, sar, par, q }) +} + +fn parse_image_attr_set_list<I>( + tokens: &mut iter::Peekable<I>, +) -> Result<SdpAttributeImageAttrSetList, SdpParserInternalError> +where + I: Iterator<Item = String> + Clone, +{ + let parse_set = |set_token: &str| -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> { + parse_image_attr_set(parse_imagettr_braced_token(set_token).ok_or_else(|| { + SdpParserInternalError::Generic("imageattr sets must be enclosed by ']'".to_string()) + })?) + }; + + match tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr must have a parameter set after a direction token".to_string(), + ) + })? + .as_str() + { + "*" => Ok(SdpAttributeImageAttrSetList::Wildcard), + x => { + let mut sets = vec![parse_set(x)?]; + while let Some(set_str) = tokens.clone().peek() { + if set_str.starts_with('[') { + sets.push(parse_set(&tokens.next().unwrap())?); + } else { + break; + } + } + + Ok(SdpAttributeImageAttrSetList::Sets(sets)) + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=imageattr, RFC6236 +//------------------------------------------------------------------------- +// image-attr = "imageattr:" PT 1*2( 1*WSP ( "send" / "recv" ) +// 1*WSP attr-list ) +// PT = 1*DIGIT / "*" +// attr-list = ( set *(1*WSP set) ) / "*" +// ; WSP and DIGIT defined in [RFC5234] +// +// set= "[" "x=" xyrange "," "y=" xyrange *( "," key-value ) "]" +// ; x is the horizontal image size range (pixel count) +// ; y is the vertical image size range (pixel count) +// +// key-value = ( "sar=" srange ) +// / ( "par=" prange ) +// / ( "q=" qvalue ) +// ; Key-value MAY be extended with other keyword +// ; parameters. +// ; At most, one instance each of sar, par, or q +// ; is allowed in a set. +// ; +// ; sar (sample aspect ratio) is the sample aspect ratio +// ; associated with the set (optional, MAY be ignored) +// ; par (picture aspect ratio) is the allowed +// ; ratio between the display's x and y physical +// ; size (optional) +// ; q (optional, range [0.0..1.0], default value 0.5) +// ; is the preference for the given set, +// ; a higher value means a higher preference +// +// onetonine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" +// ; Digit between 1 and 9 +// xyvalue = onetonine *5DIGIT +// ; Digit between 1 and 9 that is +// ; followed by 0 to 5 other digits +// step = xyvalue +// xyrange = ( "[" xyvalue ":" [ step ":" ] xyvalue "]" ) +// ; Range between a lower and an upper value +// ; with an optional step, default step = 1 +// ; The rightmost occurrence of xyvalue MUST have a +// ; higher value than the leftmost occurrence. +// / ( "[" xyvalue 1*( "," xyvalue ) "]" ) +// ; Discrete values separated by ',' +// / ( xyvalue ) +// ; A single value +// spvalue = ( "0" "." onetonine *3DIGIT ) +// ; Values between 0.1000 and 0.9999 +// / ( onetonine "." 1*4DIGIT ) +// ; Values between 1.0000 and 9.9999 +// srange = ( "[" spvalue 1*( "," spvalue ) "]" ) +// ; Discrete values separated by ','. +// ; Each occurrence of spvalue MUST be +// ; greater than the previous occurrence. +// / ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// / ( spvalue ) +// ; A single value +// +// prange = ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// +// qvalue = ( "0" "." 1*2DIGIT ) +// / ( "1" "." 1*2("0") ) +// ; Values between 0.00 and 1.00 +fn parse_image_attr(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = parse_imageattr_tokens(to_parse, ' ').into_iter().peekable(); + + let pt = parse_payload_type( + tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic("imageattr requires a payload token".to_string()) + })? + .as_str(), + )?; + let first_direction = parse_single_direction( + tokens + .next() + .ok_or_else(|| { + SdpParserInternalError::Generic( + "imageattr's second token must be a direction token".to_string(), + ) + })? + .as_str(), + )?; + + let first_set_list = parse_image_attr_set_list(&mut tokens)?; + + let mut second_set_list = SdpAttributeImageAttrSetList::Sets(Vec::new()); + + // Check if there is a second direction defined + if let Some(direction_token) = tokens.next() { + if parse_single_direction(direction_token.as_str())? == first_direction { + return Err(SdpParserInternalError::Generic( + "imageattr's second direction token must be different from the first one" + .to_string(), + )); + } + + second_set_list = parse_image_attr_set_list(&mut tokens)?; + } + + if tokens.next().is_some() { + return Err(SdpParserInternalError::Generic( + "imageattr must not contain any token after the second set list".to_string(), + )); + } + + Ok(SdpAttribute::ImageAttr(match first_direction { + SdpSingleDirection::Send => SdpAttributeImageAttr { + pt, + send: first_set_list, + recv: second_set_list, + }, + SdpSingleDirection::Recv => SdpAttributeImageAttr { + pt, + send: second_set_list, + recv: first_set_list, + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=msid, draft-ietf-mmusic-msid +//------------------------------------------------------------------------- +// msid-attr = "msid:" identifier [ SP appdata ] +// identifier = 1*64token-char ; see RFC 4566 +// appdata = 1*64token-char ; see RFC 4566 +fn parse_msid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Msid attribute is missing msid-id token".to_string(), + )); + } + Some(x) => x.to_string(), + }; + let appdata = tokens.next().map(|x| x.to_string()); + Ok(SdpAttribute::Msid(SdpAttributeMsid { id, appdata })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=msid-semantic, draft-ietf-mmusic-msid +//------------------------------------------------------------------------- +// msid-semantic-attr = "msid-semantic:" msid-semantic msid-list +// msid-semantic = token ; see RFC 4566 +// msid-list = *(" " msid-id) / " *" +fn parse_msid_semantic(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<_> = to_parse.split_whitespace().collect(); + if tokens.is_empty() { + return Err(SdpParserInternalError::Generic( + "Msid-semantic attribute is missing msid-semantic token".to_string(), + )); + } + // TODO: Should msids be checked to ensure they are non empty? + let semantic = SdpAttributeMsidSemantic { + semantic: tokens[0].to_string(), + msids: tokens[1..].iter().map(ToString::to_string).collect(), + }; + Ok(SdpAttribute::MsidSemantic(semantic)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rid, draft-ietf-mmusic-rid +//------------------------------------------------------------------------- +// rid-syntax = %s"a=rid:" rid-id SP rid-dir +// [ rid-pt-param-list / rid-param-list ] +// rid-id = 1*(alpha-numeric / "-" / "_") +// alpha-numeric = < as defined in {{RFC4566}} > +// rid-dir = %s"send" / %s"recv" +// rid-pt-param-list = SP rid-fmt-list *(";" rid-param) +// rid-param-list = SP rid-param *(";" rid-param) +// rid-fmt-list = %s"pt=" fmt *( "," fmt ) +// fmt = < as defined in {{RFC4566}} > +// rid-param = rid-width-param +// / rid-height-param +// / rid-fps-param +// / rid-fs-param +// / rid-br-param +// / rid-pps-param +// / rid-bpp-param +// / rid-depend-param +// / rid-param-other +// rid-width-param = %s"max-width" [ "=" int-param-val ] +// rid-height-param = %s"max-height" [ "=" int-param-val ] +// rid-fps-param = %s"max-fps" [ "=" int-param-val ] +// rid-fs-param = %s"max-fs" [ "=" int-param-val ] +// rid-br-param = %s"max-br" [ "=" int-param-val ] +// rid-pps-param = %s"max-pps" [ "=" int-param-val ] +// rid-bpp-param = %s"max-bpp" [ "=" float-param-val ] +// rid-depend-param = %s"depend=" rid-list +// rid-param-other = 1*(alpha-numeric / "-") [ "=" param-val ] +// rid-list = rid-id *( "," rid-id ) +// int-param-val = 1*DIGIT +// float-param-val = 1*DIGIT "." 1*DIGIT +// param-val = *( %x20-58 / %x60-7E ) +// ; Any printable character except semicolon +fn parse_rid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(3, ' ').collect(); + + if tokens.len() < 2 { + return Err(SdpParserInternalError::Generic( + "A rid attribute must at least have an id and a direction token.".to_string(), + )); + } + + // Default initilize + let mut params = SdpAttributeRidParameters { + max_width: 0, + max_height: 0, + max_fps: 0, + max_fs: 0, + max_br: 0, + max_pps: 0, + unknown: Vec::new(), + }; + let mut formats: Vec<u16> = Vec::new(); + let mut depends: Vec<String> = Vec::new(); + + if let Some(param_token) = tokens.get(2) { + let mut parameters = param_token.split(';').peekable(); + + // The 'pt' parameter must be the first parameter if present, so it + // cannot be checked along with the other parameters below + if let Some(maybe_fmt_parameter) = parameters.clone().peek() { + if let Some(fmt_list) = maybe_fmt_parameter.strip_prefix("pt=") { + for fmt in fmt_list.split(',') { + formats.push(fmt.trim().parse::<u16>()?); + } + parameters.next(); + } + } + + for param in parameters { + // TODO: Bug 1225877. Add support for params without '=' + let param_value_pair: Vec<&str> = param.splitn(2, '=').collect(); + if param_value_pair.len() != 2 { + return Err(SdpParserInternalError::Generic( + "A rid parameter needs to be of form 'param=value'".to_string(), + )); + } + + match param_value_pair[0] { + "max-width" => params.max_width = param_value_pair[1].parse::<u32>()?, + "max-height" => params.max_height = param_value_pair[1].parse::<u32>()?, + "max-fps" => params.max_fps = param_value_pair[1].parse::<u32>()?, + "max-fs" => params.max_fs = param_value_pair[1].parse::<u32>()?, + "max-br" => params.max_br = param_value_pair[1].parse::<u32>()?, + "max-pps" => params.max_pps = param_value_pair[1].parse::<u32>()?, + "depends" => { + depends.extend(param_value_pair[1].split(',').map(ToString::to_string)); + } + _ => params.unknown.push(param.to_string()), + } + } + } + + Ok(SdpAttribute::Rid(SdpAttributeRid { + id: tokens[0].to_string(), + direction: parse_single_direction(tokens[1])?, + formats, + params, + depends, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=remote-candiate, RFC5245 +//------------------------------------------------------------------------- +// remote-candidate-att = "remote-candidates" ":" remote-candidate +// 0*(SP remote-candidate) +// remote-candidate = component-ID SP connection-address SP port +fn parse_remote_candidates(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let component = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing component ID".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let address = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing connection address".to_string(), + )); + } + Some(x) => parse_unicast_address(x)?, + }; + let port = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Remote-candidate attribute is missing port number".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + if port > 65535 { + return Err(SdpParserInternalError::Generic( + "Remote-candidate port can only be a bit 16bit number".to_string(), + )); + }; + Ok(SdpAttribute::RemoteCandidate(SdpAttributeRemoteCandidate { + component, + address, + port, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtpmap, RFC4566 +//------------------------------------------------------------------------- +// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>] +fn parse_rtpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let payload_type: u8 = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing payload type".to_string(), + )); + } + Some(x) => { + let pt = x.parse::<u8>()?; + if pt > 127 { + return Err(SdpParserInternalError::Generic( + "Rtpmap payload type must be less then 127".to_string(), + )); + }; + pt + } + }; + let mut parameters = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing payload type".to_string(), + )); + } + Some(x) => x.split('/'), + }; + let name = match parameters.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing codec name".to_string(), + )); + } + Some(x) => x.to_string(), + }; + let frequency = match parameters.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtpmap missing codec name".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let mut rtpmap = SdpAttributeRtpmap::new(payload_type, name, frequency); + if let Some(x) = parameters.next() { + rtpmap.set_channels(x.parse::<u32>()?) + }; + Ok(SdpAttribute::Rtpmap(rtpmap)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp, RFC3605 +//------------------------------------------------------------------------- +// rtcp-attribute = "a=rtcp:" port [nettype space addrtype space +// connection-address] CRLF +fn parse_rtcp(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.split_whitespace(); + let port = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing port number".to_string(), + )); + } + Some(x) => x.parse::<u16>()?, + }; + let mut rtcp = SdpAttributeRtcp::new(port); + match tokens.next() { + None => (), + Some(x) => { + parse_network_type(x)?; + match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing address type token".to_string(), + )); + } + Some(x) => { + let addrtype = AddressType::from_str(x)?; + let addr = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Rtcp attribute is missing ip address token".to_string(), + )); + } + Some(x) => match ExplicitlyTypedAddress::try_from((addrtype, x)) { + Ok(address) => address, + Err(e) => return Err(e), + }, + }; + rtcp.set_addr(addr); + } + }; + } + }; + Ok(SdpAttribute::Rtcp(rtcp)) +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp-fb, RFC4585 +//------------------------------------------------------------------------- +// rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF +// +// rtcp-fb-pt = "*" ; wildcard: applies to all formats +// / fmt ; as defined in SDP spec +// +// rtcp-fb-val = "ack" rtcp-fb-ack-param +// / "nack" rtcp-fb-nack-param +// / "trr-int" SP 1*DIGIT +// / rtcp-fb-id rtcp-fb-param +// +// rtcp-fb-id = 1*(alpha-numeric / "-" / "_") +// +// rtcp-fb-param = SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-ack-param = SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-nack-param = SP "pli" +// / SP "sli" +// / SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +fn parse_rtcp_fb(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.splitn(4, ' ').collect(); + + // Parse this in advance to use it later in the parameter switch + let feedback_type = match tokens.get(1) { + Some(x) => match *x { + "ack" => SdpAttributeRtcpFbType::Ack, + "ccm" => SdpAttributeRtcpFbType::Ccm, + "nack" => SdpAttributeRtcpFbType::Nack, + "trr-int" => SdpAttributeRtcpFbType::TrrInt, + "goog-remb" => SdpAttributeRtcpFbType::Remb, + "transport-cc" => SdpAttributeRtcpFbType::TransCc, + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb feedback type: {x:?}" + ))); + } + }, + None => { + return Err(SdpParserInternalError::Generic( + "Error parsing rtcpfb: no feedback type".to_string(), + )); + } + }; + + // Parse this in advance to make the initilization block below better readable + let parameter = match feedback_type { + SdpAttributeRtcpFbType::Ack => match tokens.get(2) { + Some(x) => match *x { + "rpsi" | "app" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb ack parameter: {x:?}" + ))); + } + }, + None => { + return Err(SdpParserInternalError::Unsupported( + "The rtcpfb ack feeback type needs a parameter:".to_string(), + )); + } + }, + SdpAttributeRtcpFbType::Ccm => match tokens.get(2) { + Some(x) => match *x { + "fir" | "tmmbr" | "tstr" | "vbcm" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb ccm parameter: {x:?}" + ))); + } + }, + None => "".to_string(), + }, + SdpAttributeRtcpFbType::Nack => match tokens.get(2) { + Some(x) => match *x { + "sli" | "pli" | "rpsi" | "app" => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb nack parameter: {x:?}" + ))); + } + }, + None => "".to_string(), + }, + SdpAttributeRtcpFbType::TrrInt => match tokens.get(2) { + Some(x) => match x { + _ if x.parse::<u32>().is_ok() => (*x).to_string(), + _ => { + return Err(SdpParserInternalError::Generic(format!( + "Unknown rtcpfb trr-int parameter: {x:?}" + ))); + } + }, + None => { + return Err(SdpParserInternalError::Generic( + "The rtcpfb trr-int feedback type needs a parameter".to_string(), + )); + } + }, + SdpAttributeRtcpFbType::Remb | SdpAttributeRtcpFbType::TransCc => match tokens.get(2) { + Some(x) => { + return Err(SdpParserInternalError::Unsupported(format!( + "Unknown rtcpfb {feedback_type} parameter: {x:?}" + ))); + } + None => "".to_string(), + }, + }; + + Ok(SdpAttribute::Rtcpfb(SdpAttributeRtcpFb { + payload_type: parse_payload_type(tokens[0])?, + feedback_type, + parameter, + extra: match tokens.get(3) { + Some(x) => (*x).to_string(), + None => "".to_string(), + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=sctpmap, draft-ietf-mmusic-sctp-sdp-05 +//------------------------------------------------------------------------- +// sctpmap-attr = "a=sctpmap:" sctpmap-number media-subtypes +// [streams] +// sctpmap-number = 1*DIGIT +// protocol = labelstring +// labelstring = text +// text = byte-string +// streams = 1*DIGIT +// +// Note: this was replace in later versions of the draft by sctp-port +fn parse_sctpmap(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let tokens: Vec<&str> = to_parse.split_whitespace().collect(); + if tokens.len() != 3 { + return Err(SdpParserInternalError::Generic( + "Sctpmap needs to have three tokens".to_string(), + )); + } + let port = tokens[0].parse::<u16>()?; + if tokens[1].to_lowercase() != "webrtc-datachannel" { + return Err(SdpParserInternalError::Generic( + "Unsupported sctpmap type token".to_string(), + )); + } + Ok(SdpAttribute::Sctpmap(SdpAttributeSctpmap { + port, + channels: tokens[2].parse::<u32>()?, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=setup, RFC4145 +//------------------------------------------------------------------------- +// setup-attr = "a=setup:" role +// role = "active" / "passive" / "actpass" / "holdconn" +fn parse_setup(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + Ok(SdpAttribute::Setup( + match to_parse.to_lowercase().as_ref() { + "active" => SdpAttributeSetup::Active, + "actpass" => SdpAttributeSetup::Actpass, + "holdconn" => SdpAttributeSetup::Holdconn, + "passive" => SdpAttributeSetup::Passive, + _ => { + return Err(SdpParserInternalError::Generic( + "Unsupported setup value".to_string(), + )); + } + }, + )) +} + +fn parse_simulcast_version_list( + to_parse: &str, +) -> Result<Vec<SdpAttributeSimulcastVersion>, SdpParserInternalError> { + let make_version_list = |to_parse: &str| { + to_parse + .split(';') + .map(SdpAttributeSimulcastVersion::new) + .collect() + }; + if to_parse.contains('=') { + let mut descriptor_versionlist_pair = to_parse.splitn(2, '='); + match descriptor_versionlist_pair.next().unwrap() { + // TODO Bug 1470568 + "rid" => Ok(make_version_list( + descriptor_versionlist_pair.next().unwrap(), + )), + descriptor => Err(SdpParserInternalError::Generic(format!( + "Simulcast attribute has unknown list descriptor '{descriptor:?}'" + ))), + } + } else { + Ok(make_version_list(to_parse)) + } +} + +/////////////////////////////////////////////////////////////////////////// +// a=simulcast, draft-ietf-mmusic-sdp-simulcast +//------------------------------------------------------------------------- +// Old draft-04 +// sc-attr = "a=simulcast:" 1*2( WSP sc-str-list ) [WSP sc-pause-list] +// sc-str-list = sc-dir WSP sc-id-type "=" sc-alt-list *( ";" sc-alt-list ) +// sc-pause-list = "paused=" sc-alt-list +// sc-dir = "send" / "recv" +// sc-id-type = "pt" / "rid" / token +// sc-alt-list = sc-id *( "," sc-id ) +// sc-id = fmt / rid-identifier / token +// ; WSP defined in [RFC5234] +// ; fmt, token defined in [RFC4566] +// ; rid-identifier defined in [I-D.pthatcher-mmusic-rid] +// +// New draft 14, need to parse this for now, will eventually emit it +// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] ) +// sc-send = %s"send" SP sc-str-list +// sc-recv = %s"recv" SP sc-str-list +// sc-str-list = sc-alt-list *( ";" sc-alt-list ) +// sc-alt-list = sc-id *( "," sc-id ) +// sc-id-paused = "~" +// sc-id = [sc-id-paused] rid-id +// ; SP defined in [RFC5234] +// ; rid-id defined in [I-D.ietf-mmusic-rid] +fn parse_simulcast(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + // TODO: Bug 1225877: Stop accepting all kinds of whitespace here, and only accept SP + let mut tokens = to_parse.split_whitespace(); + let first_direction = match tokens.next() { + Some(x) => parse_single_direction(x)?, + None => { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute is missing send/recv value".to_string(), + )); + } + }; + + let first_version_list = match tokens.next() { + Some(x) => parse_simulcast_version_list(x)?, + None => { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute must have an alternatives list after the direction token" + .to_string(), + )); + } + }; + + let mut second_version_list = Vec::new(); + if let Some(x) = tokens.next() { + if parse_single_direction(x)? == first_direction { + return Err(SdpParserInternalError::Generic( + "Simulcast attribute has defined two times the same direction".to_string(), + )); + } + + second_version_list = match tokens.next() { + Some(x) => parse_simulcast_version_list(x)?, + None => { + return Err(SdpParserInternalError::Generic(format!( + "{:?}{:?}", + "Simulcast has defined a second direction but", + "no second list of simulcast stream versions" + ))); + } + } + } + + Ok(SdpAttribute::Simulcast(match first_direction { + SdpSingleDirection::Send => SdpAttributeSimulcast { + send: first_version_list, + receive: second_version_list, + }, + SdpSingleDirection::Recv => SdpAttributeSimulcast { + send: second_version_list, + receive: first_version_list, + }, + })) +} + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc, RFC5576 +//------------------------------------------------------------------------- +// ssrc-attr = "ssrc:" ssrc-id SP attribute +// ; The base definition of "attribute" is in RFC 4566. +// ; (It is the content of "a=" lines.) +// +// ssrc-id = integer ; 0 .. 2**32 - 1 +fn parse_ssrc(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> { + let mut tokens = to_parse.splitn(2, ' '); + let ssrc_id = match tokens.next() { + None => { + return Err(SdpParserInternalError::Generic( + "Ssrc attribute is missing ssrc-id value".to_string(), + )); + } + Some(x) => x.parse::<u32>()?, + }; + let mut ssrc = SdpAttributeSsrc::new(ssrc_id); + match tokens.next() { + None => (), + Some(x) => ssrc.set_attribute(x), + }; + Ok(SdpAttribute::Ssrc(ssrc)) +} + +pub fn parse_attribute(value: &str) -> Result<SdpType, SdpParserInternalError> { + Ok(SdpType::Attribute(value.trim().parse()?)) +} + +#[cfg(test)] +#[path = "./attribute_type_tests.rs"] +mod tests; |