use std::{
convert::TryFrom,
fmt::{Display, Write},
net::IpAddr,
str::FromStr,
};
use crate::{
builders, enums::*, parse_connection_address, parse_connection_ip_address, parser::is_token,
Attribute, ConnectionAddress, ConnectionAddressError,
};
pub trait TypedAttribute: Display + FromStr<Err = AttributeError> {
const NAME: &'static str;
}
impl<T: TypedAttribute> From<T> for Attribute {
fn from(attr: T) -> Attribute {
let value = attr.to_string();
Attribute {
attribute: T::NAME.to_string(),
value: (!value.is_empty()).then_some(value),
}
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AttributeError {
#[error("Attribute {} not found", .0)]
NotFound(String),
#[error("Param {} not found in {}", .param, .attr)]
ParamNotFound { param: String, attr: String },
#[error("Invalid value {} for Param {} in {}", .val ,.param, .attr)]
InvalidParamValue {
param: String,
val: String,
attr: String,
},
#[error("Unsupported attribute format: {} for {}", .val, .attr)]
UnsupportedFormat { val: String, attr: String },
#[error("Unexpected trailing item {} for in {}", .val, .attr)]
UnexpectedTrailingItem { val: String, attr: String },
#[error("{}: {}", .attr, .error)]
Other { error: String, attr: String },
}
impl AttributeError {
pub fn is_attribute_not_found(&self) -> bool {
matches!(self, AttributeError::NotFound(_))
}
fn add_param_context(&mut self, param_context: &str) {
use AttributeError::*;
match self {
NotFound(_)
| Other { .. }
| UnsupportedFormat { .. }
| UnexpectedTrailingItem { .. } => (),
ParamNotFound { param, .. } | InvalidParamValue { param, .. } => {
param.push(')');
param.insert_str(0, " (");
param.insert_str(0, param_context);
}
}
}
pub(crate) fn set_attr(&mut self, new_attr: impl ToString) {
use AttributeError::*;
match self {
NotFound(attr)
| ParamNotFound { attr, .. }
| InvalidParamValue { attr, .. }
| UnsupportedFormat { attr, .. }
| UnexpectedTrailingItem { attr, .. }
| Other { attr, .. } => *attr = new_attr.to_string(),
}
}
}
pub(crate) trait ErrorContext {
fn with_param_context(self, param_context: &str) -> Self;
fn with_attr(self, new_attr: impl ToString) -> Self;
}
impl<T> ErrorContext for Result<T, AttributeError> {
fn with_param_context(self, param_context: &str) -> Self {
self.map_err(|mut err| {
err.add_param_context(param_context);
err
})
}
fn with_attr(self, new_attr: impl ToString) -> Self {
self.map_err(|mut err| {
err.set_attr(new_attr);
err
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RtpMap {
pub payload_type: u8,
pub encoding_name: String,
pub clock_rate: u32,
pub encoding_params: Option<String>,
}
impl RtpMap {
pub fn new(payload_type: u8, encoding_name: impl ToString, clock_rate: u32) -> Self {
RtpMap {
payload_type,
encoding_name: encoding_name.to_string(),
clock_rate,
encoding_params: None,
}
}
pub fn builder(
payload_type: u8,
encoding_name: impl ToString,
clock_rate: u32,
) -> builders::RtpMap {
builders::RtpMap::new(payload_type, encoding_name, clock_rate)
}
pub fn with_encoding_params(
payload_type: u8,
encoding_name: impl ToString,
clock_rate: u32,
encoding_params: impl ToString,
) -> Self {
RtpMap {
payload_type,
encoding_name: encoding_name.to_string(),
clock_rate,
encoding_params: Some(encoding_params.to_string()),
}
}
pub fn set_encoding_params(&mut self, encoding_params: impl ToString) {
self.encoding_params = Some(encoding_params.to_string());
}
}
impl FromStr for RtpMap {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((pt, rest)) = s.split_once(' ') else {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(pt) = pt.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "Payload type".to_string(),
val: pt.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if pt > 127 {
return Err(AttributeError::InvalidParamValue {
param: "Payload type".to_string(),
val: format!("{pt}(expected 0-127)"),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let mut i = rest.splitn(3, '/');
let Some(encoding) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Encoding name".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(clock_rate) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Clock rate".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(clock_rate) = clock_rate.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Clock rate".to_string(),
val: clock_rate.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let params = i.next().map(String::from);
Ok(Self {
payload_type: pt,
encoding_name: encoding.to_owned(),
clock_rate,
encoding_params: params,
})
}
}
impl Display for RtpMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {}/{}",
self.payload_type, self.encoding_name, self.clock_rate
)?;
if let Some(params) = &self.encoding_params {
f.write_char('/')?;
f.write_str(params)?;
}
Ok(())
}
}
impl TypedAttribute for RtpMap {
const NAME: &'static str = "rtpmap";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FmtpParam {
pub param: String,
pub val: Option<String>,
}
impl FmtpParam {
pub fn new(param: impl ToString) -> Self {
FmtpParam {
param: param.to_string(),
val: None,
}
}
pub fn builder(param: impl ToString) -> builders::FmtpParam {
builders::FmtpParam::new(param)
}
pub fn with_value(param: impl ToString, value: impl ToString) -> Self {
FmtpParam {
param: param.to_string(),
val: Some(value.to_string()),
}
}
pub fn set_value(&mut self, value: impl ToString) {
self.val = Some(value.to_string());
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Fmtp {
pub fmt: u8,
pub format_specific_params: Vec<FmtpParam>,
}
impl Fmtp {
pub fn new(fmt: u8) -> Self {
Fmtp {
fmt,
format_specific_params: vec![],
}
}
pub fn builder(fmt: u8) -> builders::Fmtp {
builders::Fmtp::new(fmt)
}
pub fn add_format_specific_param(&mut self, format_specific_param: FmtpParam) {
self.format_specific_params.push(format_specific_param)
}
pub fn add_format_specific_params(
&mut self,
format_specific_params: impl IntoIterator<Item = FmtpParam>,
) {
self.format_specific_params.extend(format_specific_params)
}
}
impl FromStr for Fmtp {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((fmt, rest)) = s.split_once(' ') else {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(fmt) = fmt.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "fmtp".to_string(),
val: fmt.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let mut params: Vec<FmtpParam> = Vec::new();
for param in rest.split(';') {
if let Some((key, value)) = param.split_once('=') {
params.push(FmtpParam {
param: key.to_string(),
val: Some(value.to_string()),
});
} else {
params.push(FmtpParam {
param: param.to_string(),
val: None,
});
}
}
Ok(Self {
fmt,
format_specific_params: params,
})
}
}
impl Display for Fmtp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} ", self.fmt)?;
let mut iter = self.format_specific_params.iter().peekable();
while let Some(p) = iter.next() {
write!(f, "{}", p.param)?;
if let Some(val) = &p.val {
f.write_char('=')?;
f.write_str(val.as_str())?;
}
if iter.peek().is_some() {
f.write_char(';')?;
}
}
Ok(())
}
}
impl TypedAttribute for Fmtp {
const NAME: &'static str = "fmtp";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rtcp {
pub port: u16,
pub nettype: Option<NetType>,
pub addrtype: Option<AddrType>,
pub connection_address: Option<String>,
}
impl Rtcp {
pub fn with_port(port: u16) -> Self {
Rtcp {
port,
nettype: None,
addrtype: None,
connection_address: None,
}
}
pub fn with_ip_addr(port: u16, connection_address: impl Into<IpAddr>) -> Self {
let connection_address = connection_address.into();
Rtcp {
port,
nettype: Some(NetType::In),
addrtype: Some(connection_address.into()),
connection_address: Some(connection_address.to_string()),
}
}
pub fn new(
port: u16,
nettype: NetType,
addrtype: AddrType,
connection_address: impl ToString,
) -> Self {
Rtcp {
port,
nettype: Some(nettype),
addrtype: Some(addrtype),
connection_address: Some(connection_address.to_string()),
}
}
#[deprecated(since = "0.3.1", note = "use Rtcp::try_address instead")]
pub fn try_parse_connection_ip_address(&self) -> Option<Result<IpAddr, &str>> {
self.connection_address
.as_deref()
.map(parse_connection_ip_address)
}
fn effective_addrtype(&self) -> Option<AddrType> {
self.addrtype.clone().or_else(|| {
let address = parse_connection_ip_address(self.connection_address.as_deref()?).ok()?;
Some(address.into())
})
}
pub fn try_address(&self) -> Option<Result<ConnectionAddress, ConnectionAddressError>> {
let address = self.connection_address.as_deref()?;
match self.effective_addrtype() {
Some(addrtype) => Some(parse_connection_address(address, &addrtype)),
None => Some(Err(ConnectionAddressError::InvalidAddress(
address.to_string(),
))),
}
}
pub fn is_multicast(&self) -> bool {
matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
Some(Ok(address)) if address.is_multicast())
}
pub fn try_is_multicast(&self) -> Option<Result<bool, ConnectionAddressError>> {
let address = self.connection_address.as_deref()?;
Some(
parse_connection_ip_address(address)
.map(|address| address.is_multicast())
.map_err(|_| ConnectionAddressError::InvalidAddress(address.to_string())),
)
}
pub fn is_ipv4(&self) -> bool {
matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
Some(Ok(address)) if address.is_ipv4())
}
pub fn is_ipv6(&self) -> bool {
matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
Some(Ok(address)) if address.is_ipv6())
}
pub fn set_connection_ip_address(&mut self, connection_address: impl Into<IpAddr>) {
let connection_address = connection_address.into();
self.nettype.get_or_insert(NetType::In);
self.addrtype = Some(connection_address.into());
self.connection_address = Some(connection_address.to_string());
}
}
impl FromStr for Rtcp {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ').filter(|part| !part.is_empty());
let Some(port) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Port".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(port) = port.parse::<u16>() else {
return Err(AttributeError::InvalidParamValue {
param: "Port".to_string(),
val: port.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(nettype) = i.next() else {
return Ok(Self::with_port(port));
};
if !is_token(nettype.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "Network type".to_string(),
val: nettype.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let nettype = NetType::from_str(nettype).expect("infallible");
let Some(addrtype) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Address type".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if !is_token(addrtype.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "Address type".to_string(),
val: addrtype.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let addrtype = AddrType::from_str(addrtype).expect("infallible");
let Some(connection_address) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Connection address".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if let Some(unexpected) = i.next() {
return Err(AttributeError::UnexpectedTrailingItem {
val: unexpected.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(Self {
port,
nettype: Some(nettype),
addrtype: Some(addrtype),
connection_address: Some(connection_address.to_string()),
})
}
}
impl Display for Rtcp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.port)?;
if let Some(nettype) = &self.nettype {
write!(f, " {nettype}")?;
}
if let Some(addrtype) = &self.addrtype {
write!(f, " {addrtype}")?;
}
if let Some(connection_address) = &self.connection_address {
write!(f, " {connection_address}")?;
}
Ok(())
}
}
impl TypedAttribute for Rtcp {
const NAME: &'static str = "rtcp";
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RtcpFb {
pub pt: RtcpFbPt,
pub val: RtcpFbVal,
}
impl RtcpFb {
pub fn new(pt: impl Into<RtcpFbPt>, val: impl Into<RtcpFbVal>) -> Self {
RtcpFb {
pt: pt.into(),
val: val.into(),
}
}
}
impl FromStr for RtcpFb {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ');
let Some(pt) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Payload format".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let pt = if let Ok(pt) = pt.parse::<u8>() {
RtcpFbPt::Fmt(pt)
} else if pt == "*" {
RtcpFbPt::Wildcard
} else {
return Err(AttributeError::InvalidParamValue {
param: "Payload format".to_string(),
val: pt.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(val) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Rtcp feedback value".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let rtcp_fb_val = match val {
"ack" => {
if let Some(ack_val) = i.next() {
let ack_val = match ack_val {
"rpsi" => RtcpFbAck::Rpsi,
"app" => {
if let Some(app_param) = i.next() {
RtcpFbAck::App(Some(app_param.to_string()))
} else {
RtcpFbAck::App(None)
}
}
"ccfb" => {
if let RtcpFbPt::Fmt(pt) = pt {
return Err(AttributeError::InvalidParamValue {
param: "Payload type of Congestion control feedback (ccfb)"
.to_string(),
val: format!("{pt}(expected wildcard (*))"),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
} else {
RtcpFbAck::Ccfb
}
}
other => RtcpFbAck::Other(other.to_string()),
};
RtcpFbVal::Ack(Some(ack_val))
} else {
RtcpFbVal::Ack(None)
}
}
"nack" => {
if let Some(nack_val) = i.next() {
let nack_val = match nack_val {
"pli" => RtcpFbNack::Pli,
"sli" => RtcpFbNack::Sli,
"rpsi" => RtcpFbNack::Rpsi,
"app" => {
if let Some(app_param) = i.next() {
RtcpFbNack::App(Some(app_param.to_string()))
} else {
RtcpFbNack::App(None)
}
}
"ecn" => RtcpFbNack::Ecn,
other => RtcpFbNack::Other(other.to_string()),
};
RtcpFbVal::Nack(Some(nack_val))
} else {
RtcpFbVal::Nack(None)
}
}
"trr-int" => {
if let Some(val) = i.next() {
let Ok(i) = val.parse::<u64>() else {
return Err(AttributeError::InvalidParamValue {
param: "Minimum interval between RTCP packets (trr-int)".to_string(),
val: val.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
RtcpFbVal::TrrInt(i)
} else {
return Err(AttributeError::Other {
error: "Minimum interval between RTCP packets (trr-int) not specified"
.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
}
"ccm" => {
if let Some(ccm_val) = i.next() {
let ccm_val = match ccm_val {
"fir" => RtcpFbCcm::Fir,
"tmmbr" => {
if let Some(tmmbr_val) = i.next() {
RtcpFbCcm::Tmmbr(Some(tmmbr_val.to_string()))
} else {
RtcpFbCcm::Tmmbr(None)
}
}
"tstr" => RtcpFbCcm::Tstr,
"vbcm" => {
let mut v = vec![];
for vbcm_val in i {
let Ok(p) = vbcm_val.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "Video backchannel messages (vbcm)".to_string(),
val: vbcm_val.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
v.push(p);
}
RtcpFbCcm::Vbcm(v)
}
other => RtcpFbCcm::Other(other.to_string()),
};
RtcpFbVal::Ccm(ccm_val)
} else {
return Err(AttributeError::ParamNotFound {
param: "Codec control messages (ccm)".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
}
"transport-cc" => RtcpFbVal::TransportCc,
other => RtcpFbVal::Other(other.to_string()),
};
Ok(Self {
pt,
val: rtcp_fb_val,
})
}
}
impl Display for RtcpFb {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.pt {
RtcpFbPt::Wildcard => f.write_char('*')?,
RtcpFbPt::Fmt(pt) => write!(f, "{pt}")?,
}
f.write_char(' ')?;
write!(f, "{}", self.val)
}
}
impl TypedAttribute for RtcpFb {
const NAME: &'static str = "rtcp-fb";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
#[default]
SendRecv,
SendOnly,
RecvOnly,
Inactive,
}
impl Direction {
pub fn as_str(&self) -> &'static str {
match self {
Self::SendOnly => "sendonly",
Self::RecvOnly => "recvonly",
Self::SendRecv => "sendrecv",
Self::Inactive => "inactive",
}
}
pub fn has_send(self) -> bool {
matches!(self, Self::SendRecv | Self::SendOnly)
}
pub fn has_recv(self) -> bool {
matches!(self, Self::SendRecv | Self::RecvOnly)
}
pub fn reverse(self) -> Self {
match self {
Self::SendRecv => Self::SendRecv,
Self::SendOnly => Self::RecvOnly,
Self::RecvOnly => Self::SendOnly,
Self::Inactive => Self::Inactive,
}
}
pub fn intersect_with_remote(self, remote: Self) -> Self {
match (self, remote) {
(Self::Inactive, _)
| (_, Self::Inactive)
| (Self::RecvOnly, Self::RecvOnly)
| (Self::SendOnly, Self::SendOnly) => Self::Inactive,
(Self::SendRecv, Self::SendRecv) => Self::SendRecv,
(Self::SendOnly, Self::RecvOnly | Self::SendRecv)
| (Self::SendRecv, Self::RecvOnly) => Self::SendOnly,
(Self::RecvOnly, Self::SendRecv | Self::SendOnly)
| (Self::SendRecv, Self::SendOnly) => Self::RecvOnly,
}
}
}
impl FromStr for Direction {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if "sendonly".eq_ignore_ascii_case(s) {
Ok(Direction::SendOnly)
} else if "recvonly".eq_ignore_ascii_case(s) {
Ok(Direction::RecvOnly)
} else if "sendrecv".eq_ignore_ascii_case(s) {
Ok(Direction::SendRecv)
} else if "inactive".eq_ignore_ascii_case(s) {
Ok(Direction::Inactive)
} else {
Err(ParseEnumError::Invalid(s.to_string()))
}
}
}
impl Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl From<Direction> for Attribute {
fn from(attr: Direction) -> Attribute {
Attribute {
attribute: attr.to_string(),
value: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExtMap {
pub id: u8,
pub direction: Option<Direction>,
pub uri: String,
pub attributes: Option<String>,
}
impl ExtMap {
pub fn new(id: u8, uri: impl ToString) -> Self {
ExtMap {
id,
direction: None,
uri: uri.to_string(),
attributes: None,
}
}
pub fn builder(id: u8, uri: impl ToString) -> builders::ExtMap {
builders::ExtMap::new(id, uri)
}
pub fn with_direction(id: u8, direction: Direction, uri: impl ToString) -> Self {
ExtMap {
id,
direction: Some(direction),
uri: uri.to_string(),
attributes: None,
}
}
pub fn with_direction_and_attributes(
id: u8,
direction: Direction,
uri: impl ToString,
attributes: impl ToString,
) -> Self {
ExtMap {
id,
direction: Some(direction),
uri: uri.to_string(),
attributes: Some(attributes.to_string()),
}
}
pub fn set_direction(&mut self, direction: Direction) {
self.direction = Some(direction);
}
pub fn set_attributes(&mut self, attributes: impl ToString) {
self.attributes = Some(attributes.to_string());
}
}
impl FromStr for ExtMap {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.splitn(3, ' ');
let Some(id_direction) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "id/direction".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let mut d = id_direction.split('/');
let Some(id) = d.next() else {
return Err(AttributeError::ParamNotFound {
param: "id".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let direction = if let Some(d) = d.next() {
let Ok(dir) = Direction::from_str(d) else {
return Err(AttributeError::InvalidParamValue {
param: "Direction".to_string(),
val: d.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
Some(dir)
} else {
None
};
let Ok(id) = id.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "Id".to_string(),
val: id.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if id == 0 {
return Err(AttributeError::InvalidParamValue {
param: "Id".to_string(),
val: id.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let Some(uri) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "URI".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let attributes = i.next().map(|attr| attr.to_string());
Ok(Self {
id,
direction,
uri: uri.to_string(),
attributes,
})
}
}
impl Display for ExtMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)?;
if let Some(direction) = &self.direction {
f.write_char('/')?;
f.write_str(direction.as_str())?;
}
f.write_char(' ')?;
f.write_str(&self.uri)?;
if let Some(attr) = &self.attributes {
f.write_char(' ')?;
f.write_str(attr.as_str())?;
}
Ok(())
}
}
impl TypedAttribute for ExtMap {
const NAME: &'static str = "extmap";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Fingerprint {
pub hash_func: HashFunc,
pub fingerprint: Vec<u8>,
}
impl Fingerprint {
pub fn new(hash_func: HashFunc) -> Self {
Fingerprint {
hash_func,
fingerprint: vec![],
}
}
pub fn with_fingerprint(
hash_func: HashFunc,
fingerprint: impl IntoIterator<Item = u8>,
) -> Self {
Fingerprint {
hash_func,
fingerprint: std::iter::FromIterator::from_iter(fingerprint),
}
}
pub fn set_fingerprint(&mut self, fingerprint: impl IntoIterator<Item = u8>) {
self.fingerprint.clear();
self.fingerprint.extend(fingerprint);
}
}
impl FromStr for Fingerprint {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.splitn(2, ' ');
let hash_func = if let Some(hash_func) = i.next() {
HashFunc::new(hash_func)
} else {
return Err(AttributeError::ParamNotFound {
param: "Hash function".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let mut fingerprint: Vec<u8> = vec![];
if let Some(fp) = i.next() {
for f in fp.split(':') {
let Ok(mut f) = hex::decode(f) else {
return Err(AttributeError::InvalidParamValue {
param: "Fingerprint value".to_string(),
val: f.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
fingerprint.append(&mut f);
}
} else {
return Err(AttributeError::ParamNotFound {
param: "Hash value".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
Ok(Self {
hash_func,
fingerprint,
})
}
}
impl Display for Fingerprint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.hash_func.as_str())?;
let mut first = true;
for v in &self.fingerprint {
if first {
f.write_char(' ')?;
first = false;
} else {
f.write_char(':')?;
}
write!(f, "{v:02X}")?;
}
Ok(())
}
}
impl TypedAttribute for Fingerprint {
const NAME: &'static str = "fingerprint";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Group {
pub semantics: GroupSemantics,
pub mid_tags: Vec<String>,
}
impl Group {
pub fn new(semantics: GroupSemantics) -> Self {
Group {
semantics,
mid_tags: vec![],
}
}
pub fn add_mid_tag(&mut self, mid_tag: impl ToString) {
self.mid_tags.push(mid_tag.to_string())
}
pub fn add_mid_tags(&mut self, mid_tags: impl IntoIterator<Item = impl ToString>) {
self.mid_tags
.extend(mid_tags.into_iter().map(|i| i.to_string()))
}
}
impl FromStr for Group {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ');
let Some(semantics) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Semantics".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let semantics = GroupSemantics::new(semantics);
let mut mid_tags = vec![];
for mid in i {
mid_tags.push(mid.to_string());
}
if mid_tags.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "Media identification tags".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(Self {
semantics,
mid_tags,
})
}
}
impl Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.semantics.as_str())?;
for m in &self.mid_tags {
f.write_char(' ')?;
f.write_str(m)?;
}
Ok(())
}
}
impl TypedAttribute for Group {
const NAME: &'static str = "group";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Setup {
Active,
Passive,
ActPass,
HoldConn,
}
impl FromStr for Setup {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if "active".eq_ignore_ascii_case(s) {
Ok(Setup::Active)
} else if "passive".eq_ignore_ascii_case(s) {
Ok(Setup::Passive)
} else if "actpass".eq_ignore_ascii_case(s) {
Ok(Setup::ActPass)
} else if "holdconn".eq_ignore_ascii_case(s) {
Ok(Setup::HoldConn)
} else {
Err(AttributeError::Other {
error: format!("Invalid Setup value {s}"),
attr: <Self as TypedAttribute>::NAME.to_string(),
})
}
}
}
impl Display for Setup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Setup::Active => "active",
Setup::Passive => "passive",
Setup::ActPass => "actpass",
Setup::HoldConn => "holdconn",
};
f.write_str(s)
}
}
impl TypedAttribute for Setup {
const NAME: &'static str = "setup";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ssrc {
pub ssrc_id: u32,
pub attribute: SsrcAttribute,
pub value: Option<String>,
}
impl Ssrc {
pub fn new(ssrc_id: u32, attribute: SsrcAttribute) -> Self {
Ssrc {
ssrc_id,
attribute,
value: None,
}
}
pub fn with_typed_attribute(ssrc_id: u32, attribute: impl TypedAttribute) -> Self {
let value = attribute.to_string();
Ssrc {
ssrc_id,
attribute: SsrcAttribute::from(attribute),
value: Some(value),
}
}
pub fn with_value(ssrc_id: u32, attribute: SsrcAttribute, value: impl ToString) -> Self {
Ssrc {
ssrc_id,
attribute,
value: Some(value.to_string()),
}
}
pub fn set_value(&mut self, value: impl ToString) {
self.value = Some(value.to_string());
}
pub fn get_typed<T: TypedAttribute>(&self) -> Result<T, AttributeError> {
if !self.attribute.as_str().eq_ignore_ascii_case(T::NAME) {
return Err(AttributeError::Other {
error: format!("Attribute type mismatch (requested {})", T::NAME),
attr: self.attribute.as_str().to_string(),
});
}
T::from_str(self.value.as_deref().unwrap_or_default())
}
}
impl FromStr for Ssrc {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((ssrc_id_str, rest)) = s.split_once(' ') else {
return Err(AttributeError::ParamNotFound {
param: "Ssrc id".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(ssrc_id) = ssrc_id_str.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Ssrc id".to_string(),
val: ssrc_id_str.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let (attr, value) = if let Some((attr_str, value)) = rest.split_once(':') {
(attr_str, Some(value.to_string()))
} else {
(rest, None)
};
Ok(Self {
ssrc_id,
attribute: SsrcAttribute::new(attr),
value,
})
}
}
impl Display for Ssrc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
let attr_str = match &self.attribute {
SsrcAttribute::Cname => "cname",
SsrcAttribute::PreviousSsrc => "previous-ssrc",
SsrcAttribute::Fmtp => <Fmtp as TypedAttribute>::NAME,
SsrcAttribute::Rtcp => <Rtcp as TypedAttribute>::NAME,
SsrcAttribute::ReferenceClock => <ReferenceClock as TypedAttribute>::NAME,
SsrcAttribute::MediaClockSource => <MediaClockSource as TypedAttribute>::NAME,
SsrcAttribute::Other(other) => other.as_str(),
};
write!(f, "{} {attr_str}", self.ssrc_id)?;
if let Some(value) = &self.value {
f.write_char(':')?;
f.write_str(value)?;
}
Ok(())
}
}
impl TypedAttribute for Ssrc {
const NAME: &'static str = "ssrc";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SsrcGroup {
pub semantics: GroupSemantics,
pub ssrc_ids: Vec<u32>,
}
impl SsrcGroup {
pub fn new(semantics: GroupSemantics) -> Self {
SsrcGroup {
semantics,
ssrc_ids: vec![],
}
}
pub fn add_ssrc_id(&mut self, ssrc_id: u32) {
self.ssrc_ids.push(ssrc_id)
}
pub fn add_ssrc_ids(&mut self, ssrc_ids: impl IntoIterator<Item = u32>) {
self.ssrc_ids.extend(ssrc_ids)
}
}
impl FromStr for SsrcGroup {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ');
let Some(semantics) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Semantics".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let semantics = if "FEC".eq_ignore_ascii_case(semantics) {
GroupSemantics::FEC
} else if "FID".eq_ignore_ascii_case(semantics) {
GroupSemantics::FID
} else {
GroupSemantics::Other(semantics.to_string())
};
let mut ssrc_ids = vec![];
for ssrc_id in i {
let Ok(ssrc_id) = ssrc_id.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Ssrc id".to_string(),
val: ssrc_id.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
ssrc_ids.push(ssrc_id);
}
if ssrc_ids.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "ssrc_id".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(Self {
semantics,
ssrc_ids,
})
}
}
impl Display for SsrcGroup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let sem = match &self.semantics {
GroupSemantics::FEC => "FEC",
GroupSemantics::FID => "FID",
GroupSemantics::LS => "LS",
GroupSemantics::SRF => "SRF",
GroupSemantics::ANAT => "ANAT",
GroupSemantics::DDP => "DDP",
GroupSemantics::Other(s) => s.as_str(),
};
f.write_str(sem)?;
for ssrc_id in &self.ssrc_ids {
f.write_char(' ')?;
write!(f, "{ssrc_id}")?;
}
Ok(())
}
}
impl TypedAttribute for SsrcGroup {
const NAME: &'static str = "ssrc-group";
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SrtpKeyParam {
pub key_and_salt: String,
pub lifetime: Option<u64>,
pub mki_and_length: Option<(u32, u32)>,
}
impl SrtpKeyParam {
pub fn new(key_and_salt: impl ToString) -> Self {
SrtpKeyParam {
key_and_salt: key_and_salt.to_string(),
lifetime: None,
mki_and_length: None,
}
}
pub fn with_lifetime(key_and_salt: impl ToString, lifetime: u64) -> Self {
SrtpKeyParam {
key_and_salt: key_and_salt.to_string(),
lifetime: Some(lifetime),
mki_and_length: None,
}
}
pub fn with_lifetime_and_mki_and_length(
key_and_salt: impl ToString,
lifetime: u64,
mki: u32,
length: u32,
) -> Self {
SrtpKeyParam {
key_and_salt: key_and_salt.to_string(),
lifetime: Some(lifetime),
mki_and_length: Some((mki, length)),
}
}
pub fn set_lifetime(&mut self, lifetime: u64) {
self.lifetime = Some(lifetime);
}
pub fn set_mki_and_length(&mut self, mki: u32, length: u32) {
self.mki_and_length = Some((mki, length));
}
}
fn parse_decimal_no_leading_zeroes(s: &str) -> Option<u64> {
if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
if s.len() > 1 && s.starts_with('0') {
return None;
}
s.parse().ok()
}
impl FromStr for SrtpKeyParam {
type Err = AttributeError;
fn from_str(key_param: &str) -> Result<Self, Self::Err> {
let mut k = key_param.split('|');
let Some(key_and_salt_with_method) = k.next() else {
return Err(AttributeError::ParamNotFound {
param: "Srtp Key and Salt".to_string(),
attr: Crypto::NAME.to_string(),
});
};
let key_and_salt = if key_and_salt_with_method
.get(..7)
.is_some_and(|p| p.eq_ignore_ascii_case("inline:"))
{
&key_and_salt_with_method[7..]
} else {
return Err(AttributeError::InvalidParamValue {
param: "Srtp Key and Salt".to_string(),
val: key_and_salt_with_method.to_string(),
attr: Crypto::NAME.to_string(),
});
};
let (lifetime, mki_and_length) = if let Some(next_param) = k.next() {
match next_param.split_once(':') {
Some(mki_and_length) => {
let Ok(mki) = mki_and_length.0.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "MKI".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
};
let Ok(len) = mki_and_length.1.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Length".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
};
(None, Some((mki, len)))
}
None => {
const MAX_LIFETIME: u64 = 1 << 48;
let lifetime = match next_param.strip_prefix("2^") {
Some(exp) => {
let Some(exp) = parse_decimal_no_leading_zeroes(exp)
.and_then(|exp| u32::try_from(exp).ok())
else {
return Err(AttributeError::InvalidParamValue {
param: "Lifetime".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
};
let Some(lifetime) = 1u64.checked_shl(exp) else {
return Err(AttributeError::InvalidParamValue {
param: "Lifetime".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
};
if lifetime > MAX_LIFETIME {
return Err(AttributeError::InvalidParamValue {
param: "Lifetime".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
}
Some(lifetime)
}
None => {
let Some(lifetime) = parse_decimal_no_leading_zeroes(next_param) else {
return Err(AttributeError::InvalidParamValue {
param: "Lifetime".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
};
if lifetime == 0 || lifetime > MAX_LIFETIME {
return Err(AttributeError::InvalidParamValue {
param: "Lifetime".to_string(),
val: next_param.to_string(),
attr: Crypto::NAME.to_string(),
});
}
Some(lifetime)
}
};
let mki_and_length = if let Some(m) = k.next() {
if let Some(p) = m.split_once(':') {
let Ok(mki) = p.0.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "MKI".to_string(),
val: m.to_string(),
attr: Crypto::NAME.to_string(),
});
};
let Ok(len) = p.1.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Length".to_string(),
val: m.to_string(),
attr: Crypto::NAME.to_string(),
});
};
Some((mki, len))
} else {
return Err(AttributeError::ParamNotFound {
param: "MKI and Length".to_string(),
attr: Crypto::NAME.to_string(),
});
}
} else {
None
};
(lifetime, mki_and_length)
}
}
} else {
(None, None)
};
if let Some((_, len)) = mki_and_length {
if !(1..=128).contains(&len) {
return Err(AttributeError::InvalidParamValue {
param: "MKI length".to_string(),
val: len.to_string(),
attr: Crypto::NAME.to_string(),
});
}
}
if let Some(unexpected) = k.next() {
return Err(AttributeError::UnexpectedTrailingItem {
val: unexpected.to_string(),
attr: Crypto::NAME.to_string(),
});
}
Ok(Self {
key_and_salt: key_and_salt.to_string(),
lifetime,
mki_and_length,
})
}
}
impl Display for SrtpKeyParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "inline:{}", self.key_and_salt)?;
if let Some(lifetime) = self.lifetime {
if lifetime.is_power_of_two() {
write!(f, "|2^{}", lifetime.trailing_zeros())?;
} else {
write!(f, "|{lifetime}")?;
}
}
if let Some((mki, length)) = self.mki_and_length {
write!(f, "|{mki}:{length}")?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Crypto {
pub tag: u32,
pub crypto_suite: CryptoSuite,
pub key_params: Vec<SrtpKeyParam>,
pub session_params: Vec<SrtpSessionParam>,
}
impl Crypto {
pub fn new(tag: u32, crypto_suite: CryptoSuite) -> Self {
Crypto {
tag,
crypto_suite,
key_params: vec![],
session_params: vec![],
}
}
pub fn add_key_param(&mut self, key_param: SrtpKeyParam) {
self.key_params.push(key_param)
}
pub fn add_key_params(&mut self, key_params: impl IntoIterator<Item = SrtpKeyParam>) {
self.key_params.extend(key_params)
}
pub fn add_session_param(&mut self, session_param: SrtpSessionParam) {
self.session_params.push(session_param)
}
pub fn add_session_params(
&mut self,
session_params: impl IntoIterator<Item = SrtpSessionParam>,
) {
self.session_params.extend(session_params)
}
}
impl FromStr for Crypto {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ');
let Some(tag) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Tag".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(tag) = tag.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Tag".to_string(),
val: tag.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(crypto_suite) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "CryptoSuite".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let crypto_suite = CryptoSuite::new(crypto_suite);
let Some(key_params_str) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Key params".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let mut key_params: Vec<SrtpKeyParam> = Vec::new();
for key_param in key_params_str.split(';') {
let key_param = SrtpKeyParam::from_str(key_param)?;
key_params.push(key_param);
}
let mut session_params: Vec<SrtpSessionParam> = Vec::new();
for s in &mut i {
let param = if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("KDR=")) {
let kdr_val = &s[4..];
let Ok(kdr_val) = kdr_val.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "KDR".to_string(),
val: kdr_val.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if !(0..=24).contains(&kdr_val) {
return Err(AttributeError::InvalidParamValue {
param: "KDR".to_string(),
val: format!("{kdr_val}(expected range 0..24)"),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
SrtpSessionParam::Kdr(kdr_val)
} else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTCP") {
SrtpSessionParam::UnencryptedSrtcp
} else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTP") {
SrtpSessionParam::UnencryptedSrtp
} else if s.eq_ignore_ascii_case("UNAUTHENTICATED_SRTP") {
SrtpSessionParam::UnauthenticatedSrtp
} else if s
.get(..10)
.is_some_and(|p| p.eq_ignore_ascii_case("FEC_ORDER="))
{
let fec_ord = &s[10..];
if fec_ord.eq_ignore_ascii_case("FEC_SRTP") {
SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
} else if fec_ord.eq_ignore_ascii_case("SRTP_FEC") {
SrtpSessionParam::FecOrder(FecOrder::SrtpFec)
} else {
return Err(AttributeError::InvalidParamValue {
param: "FEC order".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
} else if s
.get(..8)
.is_some_and(|p| p.eq_ignore_ascii_case("FEC_KEY="))
{
let key_params_str = &s[8..];
let mut key_params: Vec<SrtpKeyParam> = Vec::new();
for key_param in key_params_str.split(';') {
let key_param = SrtpKeyParam::from_str(key_param)?;
key_params.push(key_param);
}
SrtpSessionParam::FecKey(key_params)
} else if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("WSH=")) {
let wsh_val = &s[4..];
let Ok(wsh_val) = wsh_val.parse::<u8>() else {
return Err(AttributeError::InvalidParamValue {
param: "WSH".to_string(),
val: wsh_val.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if wsh_val < 64 {
return Err(AttributeError::InvalidParamValue {
param: "WSH".to_string(),
val: wsh_val.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
SrtpSessionParam::Wsh(wsh_val)
} else {
SrtpSessionParam::Extension(s.to_string())
};
session_params.push(param);
}
Ok(Self {
tag,
key_params,
crypto_suite,
session_params,
})
}
}
impl Display for Crypto {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.tag, self.crypto_suite.as_str())?;
for (i, key_param) in self.key_params.iter().enumerate() {
if i == 0 {
f.write_char(' ')?;
} else {
f.write_char(';')?;
}
write!(f, "{}", key_param)?;
}
for session_param in &self.session_params {
match session_param {
SrtpSessionParam::Kdr(kdr) => write!(f, " KDR={kdr}")?,
SrtpSessionParam::UnencryptedSrtp => write!(f, " UNENCRYPTED_SRTP")?,
SrtpSessionParam::UnencryptedSrtcp => write!(f, " UNENCRYPTED_SRTCP")?,
SrtpSessionParam::UnauthenticatedSrtp => write!(f, " UNAUTHENTICATED_SRTP")?,
SrtpSessionParam::FecOrder(fec_order) => {
let order = match fec_order {
FecOrder::FecSrtp => "FEC_SRTP",
FecOrder::SrtpFec => "SRTP_FEC",
};
write!(f, " FEC_ORDER={order}")?;
}
SrtpSessionParam::FecKey(srtp_key_params) => {
write!(f, " FEC_KEY")?;
for (i, key_param) in srtp_key_params.iter().enumerate() {
if i == 0 {
f.write_char('=')?;
} else {
f.write_char(';')?;
}
write!(f, "{}", key_param)?;
}
}
SrtpSessionParam::Wsh(wsh) => write!(f, " WSH={wsh}")?,
SrtpSessionParam::Extension(extn) => {
f.write_char(' ')?;
f.write_str(extn)?;
}
}
}
Ok(())
}
}
impl TypedAttribute for Crypto {
const NAME: &'static str = "crypto";
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Candidate {
pub foundation: String,
pub component_id: u32,
pub transport: String,
pub priority: u64,
pub address: CandidateAddress,
pub port: u16,
pub typ: CandidateType,
pub rel_addr: Option<IpAddr>,
pub rel_port: Option<u16>,
pub extensions: Vec<(String, String)>,
}
impl Candidate {
pub fn new(
foundation: impl ToString,
component_id: u32,
transport: impl ToString,
priority: u64,
address: CandidateAddress,
port: u16,
typ: CandidateType,
) -> Self {
Candidate {
foundation: foundation.to_string(),
component_id,
transport: transport.to_string(),
priority,
address,
port,
typ,
rel_addr: None,
rel_port: None,
extensions: vec![],
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_rel_addr(
foundation: impl ToString,
component_id: u32,
transport: impl ToString,
priority: u64,
address: CandidateAddress,
port: u16,
typ: CandidateType,
rel_addr: impl Into<IpAddr>,
) -> Self {
Candidate {
foundation: foundation.to_string(),
component_id,
transport: transport.to_string(),
priority,
address,
port,
typ,
rel_addr: Some(rel_addr.into()),
rel_port: None,
extensions: vec![],
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_rel_addr_and_port(
foundation: impl ToString,
component_id: u32,
transport: impl ToString,
priority: u64,
address: CandidateAddress,
port: u16,
typ: CandidateType,
rel_addr: impl Into<IpAddr>,
rel_port: u16,
) -> Self {
Candidate {
foundation: foundation.to_string(),
component_id,
transport: transport.to_string(),
priority,
address,
port,
typ,
rel_addr: Some(rel_addr.into()),
rel_port: Some(rel_port),
extensions: vec![],
}
}
pub fn set_rel_addr(&mut self, rel_addr: impl Into<IpAddr>) {
self.rel_addr = Some(rel_addr.into());
}
pub fn set_rel_port(&mut self, rel_port: u16) {
self.rel_port = Some(rel_port);
}
pub fn add_extension(&mut self, name: impl ToString, value: impl ToString) {
self.extensions.push((name.to_string(), value.to_string()))
}
}
impl FromStr for Candidate {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut i = s.split(' ');
let Some(foundation) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Foundation".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(comp_id) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Component id".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(comp_id) = comp_id.parse::<u32>() else {
return Err(AttributeError::InvalidParamValue {
param: "Component id".to_string(),
val: comp_id.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(transport) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Transport".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(priority) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Priority".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(priority) = priority.parse::<u64>() else {
return Err(AttributeError::InvalidParamValue {
param: "Priority".to_string(),
val: priority.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(address) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Address".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let address = match address.parse::<IpAddr>() {
Ok(a) => CandidateAddress::IpAddr(a),
Err(_) => CandidateAddress::FQDN(address.to_string()),
};
let Some(port) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Port".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Ok(port) = port.parse::<u16>() else {
return Err(AttributeError::InvalidParamValue {
param: "Port".to_string(),
val: port.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let Some(typ_str) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "'typ' string".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if !typ_str.eq_ignore_ascii_case("typ") {
return Err(AttributeError::ParamNotFound {
param: "'typ' string".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let Some(cand_type) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Candidate type".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
let cand_type = CandidateType::new(cand_type);
let mut rel_addr: Option<IpAddr> = None;
let mut rel_port: Option<u16> = None;
let mut exts: Vec<(String, String)> = Vec::new();
while let Some(key) = i.next() {
if key.eq_ignore_ascii_case("raddr") {
let Some(raddr) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Relative address".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if let Ok(raddr) = raddr.parse::<IpAddr>() {
rel_addr = Some(raddr);
} else {
return Err(AttributeError::InvalidParamValue {
param: "Relative address".to_string(),
val: raddr.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
} else if key.eq_ignore_ascii_case("rport") {
let Some(rport) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Relative port".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
if let Ok(rport) = rport.parse::<u16>() {
rel_port = Some(rport);
} else {
return Err(AttributeError::InvalidParamValue {
param: "Relative port".to_string(),
val: rport.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
} else {
let Some(val) = i.next() else {
return Err(AttributeError::Other {
error: format!("No val for the extension {key}"),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
};
exts.push((key.to_string(), val.to_string()));
}
}
Ok(Self {
foundation: foundation.to_string(),
component_id: comp_id,
transport: transport.to_string(),
priority,
address,
port,
typ: cand_type,
rel_addr,
rel_port,
extensions: exts,
})
}
}
impl Display for Candidate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let candidate_addr = match &self.address {
CandidateAddress::IpAddr(a) => a.to_string(),
CandidateAddress::FQDN(d) => d.clone(),
};
write!(
f,
"{} {} {} {} {} {} typ {}",
self.foundation,
self.component_id,
self.transport,
self.priority,
candidate_addr,
self.port,
self.typ.as_str(),
)?;
if let Some(rel_addr) = self.rel_addr {
write!(f, " raddr {rel_addr}")?;
}
if let Some(rel_port) = self.rel_port {
write!(f, " rport {rel_port}")?;
}
for (key, val) in &self.extensions {
write!(f, " {key} {val}")?;
}
Ok(())
}
}
impl TypedAttribute for Candidate {
const NAME: &'static str = "candidate";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rid {
pub id: String,
pub direction: RidDirection,
pub params: Vec<String>,
}
impl Rid {
pub fn new(id: impl ToString, direction: RidDirection) -> Self {
Rid {
id: id.to_string(),
direction,
params: Vec::new(),
}
}
pub fn add_param(&mut self, param: impl ToString) {
self.params.push(param.to_string());
}
pub fn with_params(
id: impl ToString,
direction: RidDirection,
params: impl IntoIterator<Item = impl ToString>,
) -> Self {
Rid {
id: id.to_string(),
direction,
params: params.into_iter().map(|p| p.to_string()).collect(),
}
}
}
impl FromStr for Rid {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let (id, rest) = s
.split_once(' ')
.ok_or_else(|| AttributeError::ParamNotFound {
param: "Direction".to_string(),
attr: attr.clone(),
})?;
let id = id.trim();
if id.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "ID".to_string(),
attr: attr.clone(),
});
}
if !id
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
{
return Err(AttributeError::InvalidParamValue {
param: "ID".to_string(),
val: id.to_string(),
attr,
});
}
let (direction_str, params_str) = match rest.trim().split_once(' ') {
Some((d, p)) => (d, p),
None => (rest.trim(), ""),
};
let direction = direction_str
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Direction".to_string(),
val: direction_str.to_string(),
attr: attr.clone(),
})?;
let params = if params_str.trim().is_empty() {
Vec::new()
} else {
params_str
.trim()
.split(';')
.map(|p| p.trim())
.filter(|p| !p.is_empty())
.map(|p| {
if p.contains(char::is_whitespace) {
Err(AttributeError::InvalidParamValue {
param: "Parameter".to_string(),
val: p.to_string(),
attr: attr.clone(),
})
} else {
Ok(p.to_string())
}
})
.collect::<Result<Vec<_>, _>>()?
};
Ok(Rid {
id: id.to_string(),
direction,
params,
})
}
}
impl Display for Rid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.id, self.direction)?;
if !self.params.is_empty() {
write!(f, " {}", self.params.join(";"))?;
}
Ok(())
}
}
impl TypedAttribute for Rid {
const NAME: &'static str = "rid";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum KeyMgmtMethod {
Mikey,
Other(String),
}
impl From<&str> for KeyMgmtMethod {
fn from(s: &str) -> Self {
match s {
"mikey" => KeyMgmtMethod::Mikey,
other => KeyMgmtMethod::Other(other.to_string()),
}
}
}
impl std::str::FromStr for KeyMgmtMethod {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
}
}
impl Display for KeyMgmtMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KeyMgmtMethod::Mikey => f.write_str("mikey"),
KeyMgmtMethod::Other(other) => f.write_str(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct KeyMgmt {
pub method: KeyMgmtMethod,
pub data: Option<String>,
}
impl KeyMgmt {
pub fn new(method: KeyMgmtMethod) -> Self {
KeyMgmt { method, data: None }
}
pub fn with_data(method: KeyMgmtMethod, data: impl ToString) -> Self {
KeyMgmt {
method,
data: Some(data.to_string()),
}
}
}
impl FromStr for KeyMgmt {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
if s.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "Method".to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
let (method, data) = match s.split_once(char::is_whitespace) {
Some((method, data)) => (method, Some(data.trim().to_string())),
None => (s, None),
};
if !method.bytes().all(|b| b.is_ascii_alphanumeric()) {
return Err(AttributeError::InvalidParamValue {
param: "Method".to_string(),
val: method.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(KeyMgmt {
method: method.into(),
data,
})
}
}
impl Display for KeyMgmt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.data {
Some(data) => write!(f, "{} {}", self.method, data),
None => write!(f, "{}", self.method),
}
}
}
impl TypedAttribute for KeyMgmt {
const NAME: &'static str = "key-mgmt";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemoteCandidate {
pub component_id: u32,
pub address: IpAddr,
pub port: u16,
}
impl RemoteCandidate {
pub fn new(component_id: u32, address: impl Into<IpAddr>, port: u16) -> Self {
RemoteCandidate {
component_id,
address: address.into(),
port,
}
}
}
impl FromStr for RemoteCandidate {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <RemoteCandidates as TypedAttribute>::NAME.to_string();
let mut parts = s.split_whitespace();
let component_id_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "Component ID".to_string(),
attr: attr.clone(),
})?;
let component_id =
component_id_token
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Component ID".to_string(),
val: component_id_token.to_string(),
attr: attr.clone(),
})?;
let address_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "Address".to_string(),
attr: attr.clone(),
})?;
let address = address_token
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Address".to_string(),
val: address_token.to_string(),
attr: attr.clone(),
})?;
let port_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "Port".to_string(),
attr: attr.clone(),
})?;
let port = port_token
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Port".to_string(),
val: port_token.to_string(),
attr: attr.clone(),
})?;
if let Some(val) = parts.next() {
return Err(AttributeError::UnexpectedTrailingItem {
val: val.to_string(),
attr,
});
}
Ok(RemoteCandidate {
component_id,
address,
port,
})
}
}
impl Display for RemoteCandidate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.component_id, self.address, self.port)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RemoteCandidates(pub Vec<RemoteCandidate>);
impl RemoteCandidates {
pub fn new(candidates: impl IntoIterator<Item = RemoteCandidate>) -> Self {
RemoteCandidates(candidates.into_iter().collect())
}
}
impl FromStr for RemoteCandidates {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let mut tokens = s.split_whitespace();
let mut candidates = Vec::new();
loop {
let triple: Vec<&str> = tokens.by_ref().take(3).collect();
match triple.len() {
0 => break,
1..=2 => {
return Err(AttributeError::UnexpectedTrailingItem {
val: triple.join(" "),
attr,
})
}
_ => candidates.push(triple.join(" ").parse()?),
}
}
if candidates.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "Candidate".to_string(),
attr,
});
}
Ok(RemoteCandidates(candidates))
}
}
impl Display for RemoteCandidates {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, candidate) in self.0.iter().enumerate() {
if i > 0 {
f.write_char(' ')?;
}
write!(f, "{}", candidate)?;
}
Ok(())
}
}
impl TypedAttribute for RemoteCandidates {
const NAME: &'static str = "remote-candidates";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SourceFilterMode {
Incl,
Excl,
}
impl SourceFilterMode {
pub fn as_str(&self) -> &'static str {
match self {
SourceFilterMode::Incl => "incl",
SourceFilterMode::Excl => "excl",
}
}
}
impl FromStr for SourceFilterMode {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if "incl".eq_ignore_ascii_case(s) {
Ok(SourceFilterMode::Incl)
} else if "excl".eq_ignore_ascii_case(s) {
Ok(SourceFilterMode::Excl)
} else {
Err(ParseEnumError::Invalid(s.to_string()))
}
}
}
impl Display for SourceFilterMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SourceFilterAddrType {
Any,
Ip4,
Ip6,
}
impl SourceFilterAddrType {
pub fn as_str(&self) -> &'static str {
match self {
SourceFilterAddrType::Any => "*",
SourceFilterAddrType::Ip4 => "IP4",
SourceFilterAddrType::Ip6 => "IP6",
}
}
}
impl FromStr for SourceFilterAddrType {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "*" {
Ok(SourceFilterAddrType::Any)
} else if "IP4".eq_ignore_ascii_case(s) {
Ok(SourceFilterAddrType::Ip4)
} else if "IP6".eq_ignore_ascii_case(s) {
Ok(SourceFilterAddrType::Ip6)
} else {
Err(ParseEnumError::Invalid(s.to_string()))
}
}
}
impl Display for SourceFilterAddrType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SourceFilterAddress {
IpAddr(IpAddr),
FQDN(String),
}
impl From<&str> for SourceFilterAddress {
fn from(s: &str) -> Self {
match s.parse::<IpAddr>() {
Ok(address) => SourceFilterAddress::IpAddr(address),
Err(_) => SourceFilterAddress::FQDN(s.to_string()),
}
}
}
impl FromStr for SourceFilterAddress {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.into())
}
}
impl Display for SourceFilterAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceFilterAddress::IpAddr(address) => write!(f, "{}", address),
SourceFilterAddress::FQDN(domain) => f.write_str(domain),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SourceFilterDestination {
Wildcard,
Address(SourceFilterAddress),
}
impl FromStr for SourceFilterDestination {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "*" {
Ok(SourceFilterDestination::Wildcard)
} else {
Ok(SourceFilterDestination::Address(s.into()))
}
}
}
impl Display for SourceFilterDestination {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SourceFilterDestination::Wildcard => f.write_str("*"),
SourceFilterDestination::Address(address) => write!(f, "{}", address),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SourceFilter {
pub mode: SourceFilterMode,
pub nettype: NetType,
pub addrtype: SourceFilterAddrType,
pub destination: SourceFilterDestination,
pub sources: Vec<SourceFilterAddress>,
}
impl FromStr for SourceFilter {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let mut i = s.split(' ').filter(|part| !part.is_empty());
let Some(mode) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Filter mode".to_string(),
attr,
});
};
let mode = mode
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Filter mode".to_string(),
val: mode.to_string(),
attr: attr.clone(),
})?;
let Some(nettype) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Network type".to_string(),
attr,
});
};
if !is_token(nettype.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "Network type".to_string(),
val: nettype.to_string(),
attr: attr.clone(),
});
}
let nettype = NetType::from_str(nettype).expect("infallible");
let Some(addrtype) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Address types".to_string(),
attr,
});
};
let addrtype = addrtype
.parse()
.map_err(|_| AttributeError::InvalidParamValue {
param: "Address types".to_string(),
val: addrtype.to_string(),
attr: attr.clone(),
})?;
let Some(destination) = i.next() else {
return Err(AttributeError::ParamNotFound {
param: "Destination address".to_string(),
attr,
});
};
if destination.contains('/') {
return Err(AttributeError::InvalidParamValue {
param: "Destination address".to_string(),
val: destination.to_string(),
attr,
});
}
let destination = SourceFilterDestination::from_str(destination).expect("infallible");
if addrtype == SourceFilterAddrType::Any
&& matches!(
destination,
SourceFilterDestination::Address(SourceFilterAddress::IpAddr(_))
)
{
return Err(AttributeError::InvalidParamValue {
param: "Destination address".to_string(),
val: destination.to_string(),
attr,
});
}
let sources: Vec<&str> = i.collect();
if sources.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "Source list".to_string(),
attr,
});
}
if let Some(source) = sources.iter().find(|source| source == &&"*") {
return Err(AttributeError::InvalidParamValue {
param: "Source address".to_string(),
val: source.to_string(),
attr,
});
}
let sources = sources.into_iter().map(SourceFilterAddress::from).collect();
Ok(SourceFilter {
mode,
nettype,
addrtype,
destination,
sources,
})
}
}
impl Display for SourceFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} {} {}",
self.mode, self.nettype, self.addrtype, self.destination
)?;
for source in &self.sources {
write!(f, " {}", source)?;
}
Ok(())
}
}
impl TypedAttribute for SourceFilter {
const NAME: &'static str = "source-filter";
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mid(pub String);
impl FromStr for Mid {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !is_token(s.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "Identification tag".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(Mid(s.to_string()))
}
}
impl Display for Mid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl TypedAttribute for Mid {
const NAME: &'static str = "mid";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RtcpMux;
impl FromStr for RtcpMux {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(RtcpMux)
}
}
impl Display for RtcpMux {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TypedAttribute for RtcpMux {
const NAME: &'static str = "rtcp-mux";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RtcpMuxOnly;
impl FromStr for RtcpMuxOnly {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(RtcpMuxOnly)
}
}
impl Display for RtcpMuxOnly {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TypedAttribute for RtcpMuxOnly {
const NAME: &'static str = "rtcp-mux-only";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RtcpRsize;
impl FromStr for RtcpRsize {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(RtcpRsize)
}
}
impl Display for RtcpRsize {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TypedAttribute for RtcpRsize {
const NAME: &'static str = "rtcp-rsize";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IceLite;
impl FromStr for IceLite {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(IceLite)
}
}
impl Display for IceLite {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TypedAttribute for IceLite {
const NAME: &'static str = "ice-lite";
}
fn is_ice_char(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '/')
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IceUfrag(pub String);
impl FromStr for IceUfrag {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !(4..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
return Err(AttributeError::InvalidParamValue {
param: "ICE username fragment".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(IceUfrag(s.to_string()))
}
}
impl Display for IceUfrag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl TypedAttribute for IceUfrag {
const NAME: &'static str = "ice-ufrag";
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IcePwd(pub String);
impl FromStr for IcePwd {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !(22..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
return Err(AttributeError::InvalidParamValue {
param: "ICE password".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(IcePwd(s.to_string()))
}
}
impl Display for IcePwd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl TypedAttribute for IcePwd {
const NAME: &'static str = "ice-pwd";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct IceOptions(pub Vec<String>);
impl IceOptions {
pub fn new(tags: impl IntoIterator<Item = impl ToString>) -> Self {
IceOptions(tags.into_iter().map(|tag| tag.to_string()).collect())
}
}
impl FromStr for IceOptions {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let mut tags = Vec::new();
for tag in s.split_whitespace() {
if !tag.chars().all(is_ice_char) {
return Err(AttributeError::InvalidParamValue {
param: "ICE option tag".to_string(),
val: tag.to_string(),
attr,
});
}
tags.push(tag.to_string());
}
if tags.is_empty() {
return Err(AttributeError::ParamNotFound {
param: "ICE option tags".to_string(),
attr,
});
}
Ok(IceOptions(tags))
}
}
impl Display for IceOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, tag) in self.0.iter().enumerate() {
if i > 0 {
f.write_char(' ')?;
}
f.write_str(tag)?;
}
Ok(())
}
}
impl TypedAttribute for IceOptions {
const NAME: &'static str = "ice-options";
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EndOfCandidates;
impl FromStr for EndOfCandidates {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.is_empty() {
return Err(AttributeError::UnsupportedFormat {
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
});
}
Ok(EndOfCandidates)
}
}
impl Display for EndOfCandidates {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl TypedAttribute for EndOfCandidates {
const NAME: &'static str = "end-of-candidates";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Msid {
pub id: String,
pub appdata: Option<String>,
}
impl Msid {
pub fn new(id: impl ToString, appdata: Option<impl ToString>) -> Self {
Msid {
id: id.to_string(),
appdata: appdata.map(|appdata| appdata.to_string()),
}
}
}
impl FromStr for Msid {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let mut tokens = s.split_whitespace();
let id = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "MSID id".to_string(),
attr: attr.clone(),
})?;
let appdata = tokens.next();
let trailing: Vec<_> = tokens.collect();
if !trailing.is_empty() {
return Err(AttributeError::UnexpectedTrailingItem {
val: trailing.join(" "),
attr,
});
}
if id.len() > 64 || !is_token(id.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "MSID id".to_string(),
val: id.to_string(),
attr,
});
}
if let Some(appdata) = appdata {
if appdata.len() > 64 || !is_token(appdata.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "MSID appdata".to_string(),
val: appdata.to_string(),
attr,
});
}
}
Ok(Msid {
id: id.to_string(),
appdata: appdata.map(ToString::to_string),
})
}
}
impl Display for Msid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.id)?;
if let Some(appdata) = &self.appdata {
f.write_char(' ')?;
f.write_str(appdata)?;
}
Ok(())
}
}
impl TypedAttribute for Msid {
const NAME: &'static str = "msid";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SctpPort(pub u16);
fn parse_1_5_digits(s: &str) -> Option<u16> {
if s.is_empty()
|| s.len() > 5
|| !s.chars().all(|c| c.is_ascii_digit())
|| (s.len() > 1 && s.starts_with('0'))
{
return None;
}
s.parse().ok()
}
impl FromStr for SctpPort {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let invalid = || AttributeError::InvalidParamValue {
param: "SCTP port".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
};
let port = parse_1_5_digits(s).ok_or_else(invalid)?;
Ok(SctpPort(port))
}
}
impl Display for SctpPort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TypedAttribute for SctpPort {
const NAME: &'static str = "sctp-port";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MaxMessageSize(pub u64);
impl FromStr for MaxMessageSize {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let invalid = || AttributeError::InvalidParamValue {
param: "Max message size".to_string(),
val: s.to_string(),
attr: <Self as TypedAttribute>::NAME.to_string(),
};
let size = parse_decimal_no_leading_zeroes(s).ok_or_else(invalid)?;
Ok(MaxMessageSize(size))
}
}
impl Display for MaxMessageSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TypedAttribute for MaxMessageSize {
const NAME: &'static str = "max-message-size";
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SctpMap {
pub number: u16,
pub app: String,
pub streams: Option<u16>,
}
impl SctpMap {
pub fn new(number: u16, app: impl ToString, streams: Option<u16>) -> Self {
SctpMap {
number,
app: app.to_string(),
streams,
}
}
}
impl FromStr for SctpMap {
type Err = AttributeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let attr = <Self as TypedAttribute>::NAME.to_string();
let mut tokens = s.split_whitespace();
let number = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "SCTP port number".to_string(),
attr: attr.clone(),
})?;
let number = parse_1_5_digits(number).ok_or_else(|| AttributeError::InvalidParamValue {
param: "SCTP port number".to_string(),
val: number.to_string(),
attr: attr.clone(),
})?;
let app = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
param: "Application".to_string(),
attr: attr.clone(),
})?;
if !is_token(app.as_bytes()) {
return Err(AttributeError::InvalidParamValue {
param: "Application".to_string(),
val: app.to_string(),
attr,
});
}
let streams = match tokens.next() {
Some(streams) => Some(parse_1_5_digits(streams).ok_or_else(|| {
AttributeError::InvalidParamValue {
param: "Stream count".to_string(),
val: streams.to_string(),
attr: attr.clone(),
}
})?),
None => None,
};
let trailing: Vec<_> = tokens.collect();
if !trailing.is_empty() {
return Err(AttributeError::UnexpectedTrailingItem {
val: trailing.join(" "),
attr,
});
}
Ok(SctpMap {
number,
app: app.to_string(),
streams,
})
}
}
impl Display for SctpMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.number, self.app)?;
if let Some(streams) = self.streams {
write!(f, " {}", streams)?;
}
Ok(())
}
}
impl TypedAttribute for SctpMap {
const NAME: &'static str = "sctpmap";
}
#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use super::*;
use crate::*;
#[test]
fn direction_parse() {
assert_eq!("sendonly".parse::<Direction>(), Ok(Direction::SendOnly));
assert_eq!("recvonly".parse::<Direction>(), Ok(Direction::RecvOnly));
assert_eq!("sendrecv".parse::<Direction>(), Ok(Direction::SendRecv));
assert_eq!("inactive".parse::<Direction>(), Ok(Direction::Inactive));
assert!("invalid".parse::<Direction>().is_err());
}
#[test]
fn direction_display() {
assert_eq!(Direction::SendOnly.to_string(), "sendonly");
assert_eq!(Direction::RecvOnly.to_string(), "recvonly");
assert_eq!(Direction::SendRecv.to_string(), "sendrecv");
assert_eq!(Direction::Inactive.to_string(), "inactive");
}
#[test]
fn parse_rtcp_fb() {
let sdp = "v=0\r
o=alice 3203093520 3203093520 IN IP4 host.example.com\r
s=Multicast video with feedback\r
t=3203130148 3203137348\r
m=audio 49170 RTP/AVP 0\r
c=IN IP4 224.2.1.183\r
a=rtpmap:0 PCMU/8000\r
m=video 51372 RTP/AVPF 98 99\r
c=IN IP4 224.2.1.184\r
a=rtpmap:98 H263-1998/90000\r
a=rtpmap:99 H261/90000\r
a=rtcp-fb:* nack\r
a=rtcp-fb:98 nack rpsi\r
a=rtcp-fb:* trr-int 1000\r
a=rtcp-fb:98 ccm vbcm 1 2\r
a=rtcp-fb:* ccm tmmbr smaxpr=120\r
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
let mut written = vec![];
parsed.write(&mut written).unwrap();
let v = fallible_iterator::convert(parsed.medias[1].attributes_typed::<RtcpFb>())
.collect::<Vec<_>>()
.expect("Valid vector of attributes");
assert_eq!(v[0].pt, RtcpFbPt::Wildcard);
assert_eq!(v[1].val, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)));
assert_eq!(v[2].val, RtcpFbVal::TrrInt(1000));
assert_eq!(v[3].val, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(vec![1, 2])));
assert_eq!(
v[4].val,
RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(Some("smaxpr=120".to_string())))
);
}
#[test]
fn parse_group_attribute() {
let sdp = "v=0\r
o=Laura 289083124 289083124 IN IP4 two.example.com\r
c=IN IP4 233.252.0.1/127\r
t=0 0\r
a=group:LS 1 2\r
m=audio 30000 RTP/AVP 0\r
a=mid:1\r
m=video 30002 RTP/AVP 31\r
a=mid:2\r
m=audio 30004 RTP/AVP 0\r
i=This media stream contains the Spanish translation\r
a=mid:3\r
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
let g = parsed.attributes_typed::<Group>().collect::<Vec<_>>();
assert_eq!(g.len(), 1);
assert_eq!(g[0].as_ref().unwrap().semantics, GroupSemantics::LS);
assert_eq!(
g[0].as_ref().unwrap().mid_tags,
vec!["1".to_string(), "2".to_string()]
);
}
#[test]
fn parse_setup_attribute() {
let sdp = "v=0\r
m=image 54111 TCP t38\r
c=IN IP4 192.0.2.2\r
a=setup:actpass\r
a=connection:new\r
";
let media = Session::parse(sdp.as_bytes()).unwrap().medias;
let s = media[0].attributes_typed::<Setup>().collect::<Vec<_>>();
assert_eq!(s.len(), 1);
assert_eq!(s[0].as_ref().unwrap().to_owned(), Setup::ActPass);
}
#[test]
fn parse_ssrc_attributes() {
let sdp = "v=0\r
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
m=video 49174 RTP/AVPF 96 98\r
a=rtpmap:98 rtx/90000\r
a=fmtp:98 apt=96;rtx-time=3000\r
a=ssrc-group:FID 11111 22222\r
a=ssrc:11111 cname:user3@example.com\r
a=ssrc:22222 fmtp:0 0-15\r
a=ssrc-group:FID 33333 44444\r
a=ssrc:33333 cname:user3@example.com\r
a=ssrc:44444 cname:user3@example.com\r
a=ssrc:1698359993 rtcp:5003 IN IP4 127.0.0.1
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
let m = &parsed.medias[0];
let ssrcs = m
.attributes_typed::<Ssrc>()
.filter(|s| {
let Ok(ssrc) = s else { return false };
ssrc.attribute == SsrcAttribute::Fmtp || ssrc.attribute == SsrcAttribute::Rtcp
})
.collect::<Vec<_>>();
let ssrc_id = ssrcs[0].as_ref().unwrap().ssrc_id;
let ssrc_groups = m
.attributes_typed::<SsrcGroup>()
.filter(|s| {
let Ok(ssrc_group) = s else { return false };
ssrc_group.ssrc_ids[1] == ssrc_id
})
.collect::<Vec<_>>();
assert_eq!(
ssrc_groups[0].as_ref().unwrap().semantics,
GroupSemantics::FID
);
assert_eq!(ssrcs[1].as_ref().unwrap().attribute, SsrcAttribute::Rtcp);
}
#[test]
fn parse_crypto_attributes() {
let sdp = "v=0\r
o=sam 2890844526 2890842807 IN IP4 10.47.16.5\r
s=SRTP Discussion\r
i=A discussion of Secure RTP\r
u=http://www.example.com/seminars/srtp.pdf\r
e=marge@example.com (Marge Simpson)\r
c=IN IP4 168.2.17.12\r
t=2873397496 2873404696\r
m=audio 49170 RTP/SAVP 0\r
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC\r
a=crypto:2 F8_128_HMAC_SHA1_80 inline:MTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5QUJjZGVm|2^20|1:4;inline:QUJjZGVmMTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5|2^20|2:4 FEC_ORDER=FEC_SRTP\r
m=video 51372 RTP/SAVP 31\r
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2|1066:4\r
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
let a = &parsed.medias[0];
let audio_cryptos = a.attributes_typed::<Crypto>().collect::<Vec<_>>();
assert_eq!(
audio_cryptos[0].as_ref().unwrap().crypto_suite,
CryptoSuite::AesCm128HmacSha1_80
);
assert_eq!(audio_cryptos[1].as_ref().unwrap().key_params.len(), 2);
assert_eq!(
audio_cryptos[1].as_ref().unwrap().key_params[1].mki_and_length,
Some((2, 4))
);
assert_eq!(
audio_cryptos[1].as_ref().unwrap().session_params[0],
SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
);
let v = &parsed.medias[1];
let video_cryptos = v
.attributes_typed::<Crypto>()
.filter(|c| {
let Ok(crypto) = c else { return false };
crypto.tag == 1
})
.collect::<Vec<_>>();
let test_crypto = Crypto {
tag: 1,
crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
key_params: vec![SrtpKeyParam {
key_and_salt: "YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2".to_string(),
lifetime: None,
mki_and_length: Some((1066, 4)),
}],
session_params: Vec::new(),
};
assert_eq!(&test_crypto, video_cryptos[0].as_ref().unwrap());
}
#[test]
fn write_crypto_attribute() {
let crypto = Crypto {
tag: 1,
crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
key_params: vec![
SrtpKeyParam {
key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
lifetime: Some(1048576),
mki_and_length: Some((1, 4)),
},
SrtpKeyParam {
key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
lifetime: Some(1048576),
mki_and_length: Some((1, 4)),
},
],
session_params: vec![SrtpSessionParam::FecOrder(FecOrder::SrtpFec)],
};
assert_eq!(
crypto.to_string(),
"1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4;inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC"
);
}
#[test]
fn parse_srtp_key_param_lifetime() {
let param = SrtpKeyParam::from_str("inline:YWJj|2^48").unwrap();
assert_eq!(param.lifetime, Some(1 << 48));
assert_eq!(param.to_string(), "inline:YWJj|2^48");
let param = SrtpKeyParam::from_str("inline:YWJj|1000000000000").unwrap();
assert_eq!(param.lifetime, Some(1_000_000_000_000));
assert_eq!(param.to_string(), "inline:YWJj|1000000000000");
for invalid in [
"inline:YWJj|2^49",
"inline:YWJj|2^100",
"inline:YWJj|281474976710657",
"inline:YWJj|0",
"inline:YWJj|007",
"inline:YWJj|0123",
"inline:YWJj|2^007",
"inline:YWJj|+1048576",
"inline:YWJj|2^+20",
] {
assert!(
SrtpKeyParam::from_str(invalid).is_err(),
"{} should be invalid",
invalid
);
}
}
#[test]
fn parse_candidate_attributes() {
use std::net::{Ipv4Addr, Ipv6Addr};
let sdp = "v=0\r
o=- 2890844526 2890842807 IN IP4 192.168.1.1\r
s=-\r
c=IN IP4 192.168.1.1\r
t=0 0\r
m=audio 49152 RTP/AVP 0\r
a=candidate:1 1 UDP 2130706432 192.168.1.1 49152 typ host raddr 10.0.1.1 rport 49153 generation 0\r
a=candidate:2 1 UDP 1692467200 10.0.1.1 49152 typ srflx raddr 192.168.1.1 rport 49153\r
a=candidate:3 2 UDP 1692467184 192.168.1.1 49153 typ host\r
a=candidate:4 1 UDP 100 2001:db8::1 49152 typ host\r
a=candidate:5 1 UDP 50 192.168.1.1 49154 typ prflx\r
a=candidate:6 1 UDP 25 192.168.1.1 49155 typ relay raddr 10.0.0.1 rport 49156\r
a=candidate:7 1 UDP 10 192.168.1.1 49157 typ unknown_type\r
";
let session = Session::parse(sdp.as_bytes()).unwrap();
let candidates: Vec<Candidate> =
fallible_iterator::convert(session.medias[0].attributes_typed::<Candidate>())
.collect::<Vec<_>>()
.expect("Valid vector of candidates");
assert_eq!(candidates.len(), 7);
assert_eq!(candidates[0].foundation, "1");
assert_eq!(candidates[0].component_id, 1);
assert_eq!(
candidates[0].address,
CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
);
assert_eq!(candidates[0].port, 49152);
assert_eq!(candidates[0].typ, CandidateType::Host);
assert_eq!(
candidates[0].rel_addr,
Some(IpAddr::V4(Ipv4Addr::new(10, 0, 1, 1)))
);
assert_eq!(candidates[0].rel_port, Some(49153));
assert_eq!(
candidates[0].extensions,
vec![("generation".to_string(), "0".to_string())]
);
assert_eq!(candidates[1].foundation, "2");
assert_eq!(candidates[1].typ, CandidateType::Srflx);
assert_eq!(
candidates[1].rel_addr,
Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
);
assert_eq!(candidates[1].rel_port, Some(49153));
assert_eq!(candidates[2].foundation, "3");
assert_eq!(candidates[2].component_id, 2);
assert_eq!(candidates[2].typ, CandidateType::Host);
assert_eq!(candidates[3].foundation, "4");
assert_eq!(
candidates[3].address,
CandidateAddress::IpAddr(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)))
);
assert_eq!(candidates[3].typ, CandidateType::Host);
assert_eq!(candidates[4].foundation, "5");
assert_eq!(candidates[4].typ, CandidateType::Prflx);
assert_eq!(candidates[5].foundation, "6");
assert_eq!(candidates[5].typ, CandidateType::Relay);
assert_eq!(candidates[6].foundation, "7");
assert_eq!(
candidates[6].typ,
CandidateType::Other("unknown_type".to_string())
);
}
#[test]
fn write_candidate() {
use std::net::Ipv4Addr;
let candidate = Candidate {
foundation: "abcd/1234".into(),
component_id: 1,
transport: "UDP".into(),
priority: 2130706432,
address: CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
port: 49152,
typ: CandidateType::Srflx,
rel_addr: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
rel_port: Some(49153),
extensions: vec![("tcptype".to_string(), "active".to_string())],
};
assert_eq!(
candidate.to_string(),
"abcd/1234 1 UDP 2130706432 192.168.0.1 49152 typ srflx raddr 10.0.0.1 rport 49153 tcptype active"
);
}
#[test]
fn test_attribute_errors() {
assert_eq!(
"99".parse::<RtpMap>().unwrap_err(),
AttributeError::UnsupportedFormat {
val: "99".to_string(),
attr: "rtpmap".to_string()
}
);
assert_eq!(
"abc 90000".parse::<RtpMap>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Payload type".to_string(),
val: "abc".to_string(),
attr: "rtpmap".to_string()
}
);
assert_eq!(
"200 enc/90000".parse::<RtpMap>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Payload type".to_string(),
val: "200(expected 0-127)".to_string(),
attr: "rtpmap".to_string()
}
);
assert_eq!(
"99 ".parse::<RtpMap>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Clock rate".to_string(),
attr: "rtpmap".to_string()
}
);
assert_eq!(
"99 /".parse::<RtpMap>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Clock rate".to_string(),
val: "".to_string(),
attr: "rtpmap".to_string()
}
);
assert_eq!(
"invalid".parse::<Fmtp>().unwrap_err(),
AttributeError::UnsupportedFormat {
val: "invalid".to_string(),
attr: "fmtp".to_string()
}
);
assert_eq!(
"abc profile=1".parse::<Fmtp>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "fmtp".to_string(),
val: "abc".to_string(),
attr: "fmtp".to_string()
}
);
assert_eq!(
"".parse::<Rtcp>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Port".to_string(),
attr: "rtcp".to_string()
}
);
assert_eq!(
"abc IN IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Port".to_string(),
val: "abc".to_string(),
attr: "rtcp".to_string()
}
);
assert_eq!(
"".parse::<Fingerprint>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Hash value".to_string(),
attr: "fingerprint".to_string()
}
);
assert_eq!(
"SHA-1".parse::<Fingerprint>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Hash value".to_string(),
attr: "fingerprint".to_string()
}
);
assert_eq!(
"".parse::<Candidate>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Component id".to_string(),
attr: "candidate".to_string()
}
);
assert_eq!(
"1 1 UDP 100".parse::<Candidate>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Address".to_string(),
attr: "candidate".to_string()
}
);
assert_eq!(
"".parse::<ExtMap>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Id".to_string(),
val: "".to_string(),
attr: "extmap".to_string()
}
);
assert_eq!(
"999999 http://example.com".parse::<ExtMap>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Id".to_string(),
val: "999999".to_string(),
attr: "extmap".to_string()
}
);
assert_eq!(
"".parse::<Group>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Media identification tags".to_string(),
attr: "group".to_string()
}
);
assert_eq!(
"LS".parse::<Group>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Media identification tags".to_string(),
attr: "group".to_string()
}
);
assert_eq!(
"".parse::<Ssrc>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Ssrc id".to_string(),
attr: "ssrc".to_string()
}
);
assert_eq!(
"abc".parse::<Ssrc>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Ssrc id".to_string(),
attr: "ssrc".to_string()
}
);
let setup_err = "foo".parse::<Setup>().err().unwrap();
assert!(matches!(setup_err, AttributeError::Other { .. }));
assert_eq!(format!("{}", setup_err), "setup: Invalid Setup value foo");
assert_eq!(
"".parse::<Crypto>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Tag".to_string(),
val: "".to_string(),
attr: "crypto".to_string()
}
);
assert_eq!(
"abc AES_CM_128_HMAC_SHA1_32 inline:key"
.parse::<Crypto>()
.unwrap_err(),
AttributeError::InvalidParamValue {
param: "Tag".to_string(),
val: "abc".to_string(),
attr: "crypto".to_string()
}
);
assert_eq!(
"".parse::<RtcpFb>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Payload format".to_string(),
val: "".to_string(),
attr: "rtcp-fb".to_string()
}
);
assert_eq!(
"*".parse::<RtcpFb>().unwrap_err(),
AttributeError::ParamNotFound {
param: "Rtcp feedback value".to_string(),
attr: "rtcp-fb".to_string()
}
);
assert_eq!(
"1 ack ccfb".parse::<RtcpFb>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Payload type of Congestion control feedback (ccfb)".to_string(),
val: "1(expected wildcard (*))".to_string(),
attr: "rtcp-fb".to_string()
}
);
let media = Media {
media: "video".into(),
port: 1234,
num_ports: None,
proto: "RTP/SAVPF".into(),
fmt: "".into(),
media_title: None,
connections: vec![],
bandwidths: vec![],
key: None,
attributes: vec![Attribute {
attribute: "rtpmap".into(),
value: None,
}],
};
assert_eq!(
media
.attributes_typed::<RtpMap>()
.collect::<Vec<Result<RtpMap, AttributeError>>>()
.remove(0)
.unwrap_err(),
AttributeError::UnsupportedFormat {
val: String::new(),
attr: "rtpmap".to_string()
}
);
}
#[test]
#[allow(deprecated)]
fn parse_rtcp_address() {
let sdp = "v=0\r
o=alice 3203093520 3203093520 IN IP4 host.example.com\r
s=parse rtcp attribute address test\r
a=rtcp:5000 IN IP4 127.0.0.1\r
a=rtcp:5000 IN IP4 127.0.0.0/24\r
a=rtcp:5000 IN IP6 ::1\r
a=rtcp:5000 IN NONIP non-IP\r
a=rtcp:53020\r
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
let mut rtcp_attr_iter = parsed.attributes_typed::<Rtcp>();
let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.1"));
assert_eq!(
rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
IpAddr::V4(Ipv4Addr::LOCALHOST),
);
let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.0/24"));
assert_eq!(
rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 0)),
);
let address = rtcp.try_address().unwrap().unwrap();
assert_eq!(address.ttl, Some(24));
assert_eq!(address.count, None);
assert!(!rtcp.is_multicast());
let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
assert_eq!(rtcp.addrtype, Some(AddrType::Ip6));
assert_eq!(rtcp.connection_address.as_deref(), Some("::1"));
assert_eq!(
rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
IpAddr::V6(Ipv6Addr::LOCALHOST),
);
let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
assert_eq!(rtcp.addrtype, Some(AddrType::Other("NONIP".to_string())));
assert_eq!(rtcp.connection_address.as_deref(), Some("non-IP"));
assert_eq!(
rtcp.try_parse_connection_ip_address().unwrap().unwrap_err(),
"non-IP",
);
let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
assert_eq!(rtcp.port, 53020);
assert_eq!(rtcp.nettype, None);
assert_eq!(rtcp.addrtype, None);
assert_eq!(rtcp.connection_address, None);
assert_eq!(rtcp.try_parse_connection_ip_address(), None);
assert_eq!(rtcp.try_address(), None);
assert!(!rtcp.is_multicast());
assert_eq!(rtcp.to_string(), "53020");
}
#[test]
fn rtcp_multicast_address_helpers() {
let rtcp = "5000 IN IP4 233.252.0.1/127/3".parse::<Rtcp>().unwrap();
assert!(rtcp.is_multicast());
assert_eq!(
rtcp.try_address().unwrap().unwrap(),
ConnectionAddress {
ip: Ipv4Addr::new(233, 252, 0, 1).into(),
ttl: Some(127),
count: Some(3),
}
);
let mut rtcp = Rtcp::with_port(5000);
rtcp.connection_address = Some("224.2.1.1/16".to_string());
let address = rtcp.try_address().unwrap().unwrap();
assert_eq!(address.ttl, Some(16));
assert_eq!(address.count, None);
let mut rtcp = Rtcp::with_port(5000);
rtcp.connection_address = Some("ff12::5/3".to_string());
let address = rtcp.try_address().unwrap().unwrap();
assert_eq!(address.ttl, None);
assert_eq!(address.count, Some(3));
let rtcp = "5000 IN IP6 ff12::5/3".parse::<Rtcp>().unwrap();
assert!(rtcp.is_ipv6());
assert!(!rtcp.is_ipv4());
assert_eq!(rtcp.try_is_multicast(), Some(Ok(true)));
let rtcp = Rtcp::with_port(5000);
assert_eq!(rtcp.try_is_multicast(), None);
assert!(!rtcp.is_multicast());
assert!(!rtcp.is_ipv4());
assert!(!rtcp.is_ipv6());
let rtcp = "5000 IN IP4 rtcp.example.com".parse::<Rtcp>().unwrap();
assert!(!rtcp.is_multicast());
assert!(!rtcp.is_ipv4());
assert_eq!(
rtcp.try_is_multicast(),
Some(Err(ConnectionAddressError::InvalidAddress(
"rtcp.example.com".to_string()
)))
);
}
#[test]
fn parse_rtcp_attribute_whitespace() {
assert_eq!(
"6000 IN IP4 127.0.0.1".parse::<Rtcp>().unwrap(),
Rtcp::new(6000, NetType::In, AddrType::Ip4, "127.0.0.1")
);
assert_eq!(
"6001 IN IP4 127.0.0.1 ".parse::<Rtcp>().unwrap(),
Rtcp::new(6001, NetType::In, AddrType::Ip4, "127.0.0.1")
);
assert_eq!("53020 ".parse::<Rtcp>().unwrap(), Rtcp::with_port(53020));
assert!("6002 IN 127.0.0.1".parse::<Rtcp>().is_err());
assert!("6003 IN IP4 127.0.0.1 extra".parse::<Rtcp>().is_err());
}
#[test]
fn parse_rtcp_nettype_and_addrtype_must_be_tokens() {
assert_eq!(
"6000 a<b IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Network type".to_string(),
val: "a<b".to_string(),
attr: "rtcp".to_string(),
}
);
assert_eq!(
"6000 IN IP4/x 127.0.0.1".parse::<Rtcp>().unwrap_err(),
AttributeError::InvalidParamValue {
param: "Address type".to_string(),
val: "IP4/x".to_string(),
attr: "rtcp".to_string(),
}
);
}
#[test]
fn parse_rid_attribute() {
let rid: Rid = "5 send pt=99,102;max-br=64000".parse().unwrap();
assert_eq!(
rid,
Rid::with_params("5", RidDirection::Send, ["pt=99,102", "max-br=64000"])
);
assert_eq!(rid.to_string(), "5 send pt=99,102;max-br=64000");
let rid: Rid = "6 send pt=100,97,101,102".parse().unwrap();
assert_eq!(
rid,
Rid::with_params("6", RidDirection::Send, ["pt=100,97,101,102"])
);
let rid: Rid = "f recv max-br=19.5;apt=100".parse().unwrap();
assert_eq!(
rid,
Rid::with_params("f", RidDirection::Recv, ["max-br=19.5", "apt=100"])
);
assert_eq!(rid.to_string(), "f recv max-br=19.5;apt=100");
let rid: Rid = "1 recv".parse().unwrap();
assert_eq!(rid.direction, RidDirection::Recv);
assert!(rid.params.is_empty());
assert_eq!(rid.to_string(), "1 recv");
assert_eq!(
"2 send max-width".parse::<Rid>().unwrap(),
Rid::with_params("2", RidDirection::Send, ["max-width"])
);
assert_eq!(
"2 SEND pt=99;".parse::<Rid>().unwrap(),
Rid::with_params("2", RidDirection::Send, ["pt=99"])
);
for invalid in [
"",
"1",
" send",
"1! send",
"id:1 recv",
"1 up",
"1 send x y",
"1 send pt=1;max br=2",
] {
assert!(
invalid.parse::<Rid>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_key_mgmt_attribute() {
let key_mgmt: KeyMgmt = "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0".parse().unwrap();
assert_eq!(
key_mgmt,
KeyMgmt::with_data(KeyMgmtMethod::Mikey, "AAAAIklkAAAAdGV4dHRleHR0ZXh0")
);
assert_eq!(key_mgmt.to_string(), "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0");
for method in ["rawurl", "MIKEY", "internal"] {
let key_mgmt: KeyMgmt = format!("{} data", method).parse().unwrap();
assert_eq!(key_mgmt.method, KeyMgmtMethod::Other(method.to_string()));
assert_eq!(key_mgmt.data.as_deref(), Some("data"));
assert_eq!(key_mgmt.to_string(), format!("{} data", method));
}
let key_mgmt: KeyMgmt = "internal".parse().unwrap();
assert_eq!(
key_mgmt,
KeyMgmt::new(KeyMgmtMethod::Other("internal".to_string()))
);
assert_eq!(key_mgmt.to_string(), "internal");
let key_mgmt: KeyMgmt = "mikey bar baz".parse().unwrap();
assert_eq!(key_mgmt.data.as_deref(), Some("bar baz"));
assert_eq!(key_mgmt.to_string(), "mikey bar baz");
assert!("".parse::<KeyMgmt>().is_err());
assert!(" ".parse::<KeyMgmt>().is_err());
assert!("mi!key data".parse::<KeyMgmt>().is_err());
assert!("-mikey".parse::<KeyMgmt>().is_err());
}
#[test]
fn parse_remote_candidate_attribute() {
let candidates: RemoteCandidates = "1 192.168.1.1 50000".parse().unwrap();
assert_eq!(
candidates,
RemoteCandidates::new([RemoteCandidate::new(
1,
"192.168.1.1".parse::<IpAddr>().unwrap(),
50000
)])
);
assert_eq!(candidates.to_string(), "1 192.168.1.1 50000");
let candidates: RemoteCandidates = "1 192.0.2.3 45664 2 192.0.2.3 45665".parse().unwrap();
assert_eq!(candidates.0.len(), 2);
assert_eq!(candidates.0[1].component_id, 2);
assert_eq!(
candidates.to_string(),
"1 192.0.2.3 45664 2 192.0.2.3 45665"
);
let candidate: RemoteCandidate = "1 2001:db8::1 9".parse().unwrap();
assert_eq!(candidate.address, "2001:db8::1".parse::<IpAddr>().unwrap());
assert_eq!(candidate.to_string(), "1 2001:db8::1 9");
for invalid in [
"",
"1",
"1 10.0.0.1",
"x 10.0.0.1 50000",
"1 x 50000",
"1 example.com 50000",
"1 10.0.0.1 70000",
"1 10.0.0.1 50000 2",
] {
assert!(
invalid.parse::<RemoteCandidates>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_source_filter_attribute() {
let filter: SourceFilter = "incl IN IP4 232.3.4.5 192.0.2.10".parse().unwrap();
assert_eq!(filter.mode, SourceFilterMode::Incl);
assert_eq!(filter.nettype, NetType::In);
assert_eq!(filter.addrtype, SourceFilterAddrType::Ip4);
assert_eq!(
filter.destination,
SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
"232.3.4.5".parse().unwrap()
))
);
assert_eq!(
filter.sources,
["192.0.2.10".parse::<SourceFilterAddress>().unwrap()]
);
assert_eq!(filter.to_string(), "incl IN IP4 232.3.4.5 192.0.2.10");
let filter: SourceFilter = "excl IN IP4 192.0.2.11 192.0.2.10".parse().unwrap();
assert_eq!(filter.mode, SourceFilterMode::Excl);
assert_eq!(filter.to_string(), "excl IN IP4 192.0.2.11 192.0.2.10");
let filter: SourceFilter = "incl IN IP4 * 192.0.2.10".parse().unwrap();
assert_eq!(filter.destination, SourceFilterDestination::Wildcard);
assert_eq!(filter.to_string(), "incl IN IP4 * 192.0.2.10");
let filter: SourceFilter = "incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
.parse()
.unwrap();
assert_eq!(filter.sources.len(), 2);
assert_eq!(
filter.to_string(),
"incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
);
let filter: SourceFilter = "incl IN IP6 ff0e::11a 2001:db8::1".parse().unwrap();
assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
assert_eq!(
filter.destination,
SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
"ff0e::11a".parse().unwrap()
))
);
assert_eq!(filter.to_string(), "incl IN IP6 ff0e::11a 2001:db8::1");
let filter: SourceFilter = "incl IN * channel-1.example.com src-1.example.com"
.parse()
.unwrap();
assert_eq!(filter.addrtype, SourceFilterAddrType::Any);
assert_eq!(
filter.destination,
SourceFilterDestination::Address(SourceFilterAddress::FQDN(
"channel-1.example.com".to_string()
))
);
assert_eq!(
filter.sources,
[SourceFilterAddress::FQDN("src-1.example.com".to_string())]
);
assert_eq!(
filter.to_string(),
"incl IN * channel-1.example.com src-1.example.com"
);
let filter: SourceFilter = "EXCL IN ip6 ff0e::11a 2001:db8::1".parse().unwrap();
assert_eq!(filter.mode, SourceFilterMode::Excl);
assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
for invalid in [
"",
"incl",
"incl IN",
"incl IN IP4",
"incl IN IP4 232.3.4.5",
"incl IN IP4 232.3.4.5/127 192.0.2.10",
"incl IN * 232.3.4.5 192.0.2.10",
"incl IN IP4 232.3.4.5 *",
"unknown IN IP4 232.3.4.5 192.0.2.10",
"incl IN(IP4 232.3.4.5 192.0.2.10",
"incl IN IP8 232.3.4.5 192.0.2.10",
] {
assert!(
invalid.parse::<SourceFilter>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_mid_attribute() {
let mid: Mid = "1".parse().unwrap();
assert_eq!(mid, Mid("1".to_string()));
assert_eq!(mid.to_string(), "1");
let attribute: Attribute = mid.clone().into();
assert_eq!(attribute.attribute, "mid");
assert_eq!(attribute.value.as_deref(), Some("1"));
let mid: Mid = "v2R1d1".parse().unwrap();
assert_eq!(mid.to_string(), "v2R1d1");
for invalid in [
"", " ", "1 2", "a:b",
] {
assert!(
invalid.parse::<Mid>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_rtcp_mux_attribute() {
assert_eq!("".parse::<RtcpMux>().unwrap(), RtcpMux);
let attribute: Attribute = RtcpMux.into();
assert_eq!(attribute.attribute, "rtcp-mux");
assert_eq!(attribute.value, None);
assert!("1".parse::<RtcpMux>().is_err());
let sdp = "v=0\r
o=alice 3203093520 3203093520 IN IP4 host.example.com\r
s=Property attribute test\r
t=3203130148 3203137348\r
m=audio 49170 RTP/AVP 0\r
a=rtcp-mux\r
";
let parsed = Session::parse(sdp.as_bytes()).unwrap();
assert_eq!(
parsed.medias[0].get_first_attribute_typed::<RtcpMux>(),
Some(Ok(RtcpMux))
);
}
#[test]
fn parse_rtcp_mux_only_attribute() {
assert!(matches!("".parse::<RtcpMuxOnly>(), Ok(RtcpMuxOnly)));
let attribute: Attribute = RtcpMuxOnly.into();
assert_eq!(attribute.attribute, "rtcp-mux-only");
assert_eq!(attribute.value, None);
assert!("1".parse::<RtcpMuxOnly>().is_err());
}
#[test]
fn parse_rtcp_rsize_attribute() {
assert!(matches!("".parse::<RtcpRsize>(), Ok(RtcpRsize)));
let attribute: Attribute = RtcpRsize.into();
assert_eq!(attribute.attribute, "rtcp-rsize");
assert_eq!(attribute.value, None);
assert!("1".parse::<RtcpRsize>().is_err());
}
#[test]
fn parse_ice_lite_attribute() {
assert!(matches!("".parse::<IceLite>(), Ok(IceLite)));
let attribute: Attribute = IceLite.into();
assert_eq!(attribute.attribute, "ice-lite");
assert_eq!(attribute.value, None);
assert!("1".parse::<IceLite>().is_err());
}
#[test]
fn parse_ice_ufrag_attribute() {
let ufrag: IceUfrag = "EpGh".parse().unwrap();
assert_eq!(ufrag, IceUfrag("EpGh".to_string()));
assert_eq!(ufrag.to_string(), "EpGh");
let attribute: Attribute = ufrag.into();
assert_eq!(attribute.attribute, "ice-ufrag");
assert_eq!(attribute.value.as_deref(), Some("EpGh"));
assert_eq!("a+Z/9".parse::<IceUfrag>().unwrap().to_string(), "a+Z/9");
assert!("a".repeat(256).parse::<IceUfrag>().is_ok());
let too_long = "a".repeat(257);
for invalid in [
"",
"abc", too_long.as_str(), "Ep-Gh", "Ep Gh", "\u{30b0}pGh", ] {
assert!(
invalid.parse::<IceUfrag>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_ice_pwd_attribute() {
let password = "aq1s2dq3w4er5ty6ui7p8o9i0u";
let pwd: IcePwd = password.parse().unwrap();
assert_eq!(pwd, IcePwd(password.to_string()));
assert_eq!(pwd.to_string(), password);
let attribute: Attribute = pwd.into();
assert_eq!(attribute.attribute, "ice-pwd");
assert_eq!(attribute.value.as_deref(), Some(password));
let shortest = "a".repeat(22);
assert_eq!(shortest.parse::<IcePwd>().unwrap().to_string(), shortest);
assert!("a".repeat(256).parse::<IcePwd>().is_ok());
let too_long = "a".repeat(257);
for invalid in [
"",
"a".repeat(21).as_str(), too_long.as_str(), "abcdefghijklmnopqr-stu", "\u{30b0}bcdefghijklmnopqrstuvw", ] {
assert!(
invalid.parse::<IcePwd>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_ice_options_attribute() {
let options: IceOptions = "trickle".parse().unwrap();
assert_eq!(options, IceOptions::new(["trickle"]));
assert_eq!(options.to_string(), "trickle");
let options: IceOptions = "trickle renomination".parse().unwrap();
assert_eq!(options, IceOptions::new(["trickle", "renomination"]));
assert_eq!(options.to_string(), "trickle renomination");
let attribute: Attribute = options.into();
assert_eq!(attribute.attribute, "ice-options");
assert_eq!(attribute.value.as_deref(), Some("trickle renomination"));
for invalid in ["", " ", "tr-ickle"] {
assert!(
invalid.parse::<IceOptions>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_end_of_candidates_attribute() {
assert!(matches!("".parse::<EndOfCandidates>(), Ok(EndOfCandidates)));
let attribute: Attribute = EndOfCandidates.into();
assert_eq!(attribute.attribute, "end-of-candidates");
assert_eq!(attribute.value, None);
assert!("1".parse::<EndOfCandidates>().is_err());
}
#[test]
fn parse_msid_attribute() {
let msid: Msid = "examplefoo examplebar".parse().unwrap();
assert_eq!(msid, Msid::new("examplefoo", Some("examplebar")));
assert_eq!(msid.to_string(), "examplefoo examplebar");
let attribute: Attribute = msid.into();
assert_eq!(attribute.attribute, "msid");
assert_eq!(attribute.value.as_deref(), Some("examplefoo examplebar"));
let msid: Msid = "-".parse().unwrap();
assert_eq!(msid, Msid::new("-", None::<&str>));
assert_eq!(msid.to_string(), "-");
let too_long = "a".repeat(65);
for invalid in [
"",
too_long.as_str(), "foo \u{fffd}", "foo bar baz", ] {
assert!(
invalid.parse::<Msid>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_sctp_port_attribute() {
let port: SctpPort = "5000".parse().unwrap();
assert_eq!(port, SctpPort(5000));
assert_eq!(port.to_string(), "5000");
let attribute: Attribute = port.into();
assert_eq!(attribute.attribute, "sctp-port");
assert_eq!(attribute.value.as_deref(), Some("5000"));
assert_eq!("0".parse::<SctpPort>().unwrap(), SctpPort(0));
assert_eq!("65535".parse::<SctpPort>().unwrap(), SctpPort(65535));
for invalid in [
"", "65536", "99999", "05000", "500000", "abc", "50a",
] {
assert!(
invalid.parse::<SctpPort>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_max_message_size_attribute() {
let size: MaxMessageSize = "100000".parse().unwrap();
assert_eq!(size, MaxMessageSize(100000));
assert_eq!(size.to_string(), "100000");
let attribute: Attribute = size.into();
assert_eq!(attribute.attribute, "max-message-size");
assert_eq!(attribute.value.as_deref(), Some("100000"));
assert_eq!("0".parse::<MaxMessageSize>().unwrap(), MaxMessageSize(0));
let too_large = "1".repeat(21);
for invalid in [
"",
"01", "1_000",
"abc",
"10a",
too_large.as_str(), ] {
assert!(
invalid.parse::<MaxMessageSize>().is_err(),
"expected error for {:?}",
invalid
);
}
}
#[test]
fn parse_sctpmap_attribute() {
let map: SctpMap = "5000 webrtc-datachannel 256".parse().unwrap();
assert_eq!(map, SctpMap::new(5000, "webrtc-datachannel", Some(256)));
assert_eq!(map.to_string(), "5000 webrtc-datachannel 256");
let attribute: Attribute = map.into();
assert_eq!(attribute.attribute, "sctpmap");
assert_eq!(
attribute.value.as_deref(),
Some("5000 webrtc-datachannel 256")
);
let map: SctpMap = "9 webrtc-datachannel".parse().unwrap();
assert_eq!(map.streams, None);
assert_eq!(map.to_string(), "9 webrtc-datachannel");
assert_eq!("0 app 0".parse::<SctpMap>().unwrap().streams, Some(0));
for invalid in [
"",
"5000", "65536 app", "05000 app", "app app", "5000 app 70000", "5000 a:pp", "5000 app 256 extra", ] {
assert!(
invalid.parse::<SctpMap>().is_err(),
"expected error for {:?}",
invalid
);
}
}
}