use crate::error::{Error, Result};
use crate::rfc2326_lex as lex;
const PROTO_RTP: &str = "RTP";
const PROFILE_AVP: &str = "AVP";
const LOWER_TCP: &str = "TCP";
const LOWER_UDP: &str = "UDP";
const TTL_DIGITS: usize = 3;
const CHANNEL_DIGITS: usize = 3;
const PORT_DIGITS: usize = 5;
const SSRC_HEX_DIGITS: usize = 8;
const LAYERS_DIGITS: usize = 10;
const MODE_PLAY: &str = "PLAY";
const MODE_RECORD: &str = "RECORD";
const MODE_SEP: char = ',';
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LowerTransport {
Udp,
Tcp,
}
impl LowerTransport {
pub fn name(&self) -> &'static str {
match self {
LowerTransport::Udp => "UDP",
LowerTransport::Tcp => "TCP",
}
}
}
impl core::fmt::Display for LowerTransport {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Delivery {
Unicast,
Multicast,
}
impl Delivery {
pub fn name(&self) -> &'static str {
match self {
Delivery::Unicast => "unicast",
Delivery::Multicast => "multicast",
}
}
}
impl core::fmt::Display for Delivery {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransportMode {
Play,
Record,
Other(String),
}
impl TransportMode {
pub fn name(&self) -> &str {
match self {
TransportMode::Play => MODE_PLAY,
TransportMode::Record => MODE_RECORD,
TransportMode::Other(m) => m,
}
}
fn from_token(t: &str) -> Self {
if t.eq_ignore_ascii_case(MODE_PLAY) {
TransportMode::Play
} else if t.eq_ignore_ascii_case(MODE_RECORD) {
TransportMode::Record
} else {
TransportMode::Other(t.to_string())
}
}
}
impl core::fmt::Display for TransportMode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.name())
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransportSpec {
pub lower_transport: Option<LowerTransport>,
pub delivery: Option<Delivery>,
pub interleaved: Option<(u8, u8)>,
pub client_port: Option<(u16, u16)>,
pub server_port: Option<(u16, u16)>,
pub port: Option<(u16, u16)>,
pub ttl: Option<u8>,
pub layers: Option<u32>,
pub ssrc: Option<u32>,
pub destination: Option<String>,
pub source: Option<String>,
pub mode: Vec<TransportMode>,
pub append: bool,
pub extensions: Vec<(String, Option<String>)>,
}
fn bad<T>(msg: impl Into<String>) -> Result<T> {
Err(Error::TransportParse(msg.into()))
}
impl From<lex::LexError> for Error {
fn from(e: lex::LexError) -> Self {
Error::TransportParse(e.0)
}
}
fn pair_u8(p: (u32, u32)) -> (u8, u8) {
(p.0 as u8, p.1 as u8)
}
fn pair_u16(p: (u32, u32)) -> (u16, u16) {
(p.0 as u16, p.1 as u16)
}
impl TransportSpec {
pub fn rtp_avp_tcp_interleaved(lo: u8, hi: u8) -> Self {
TransportSpec {
lower_transport: Some(LowerTransport::Tcp),
delivery: Some(Delivery::Unicast),
interleaved: Some((lo, hi)),
..Default::default()
}
}
fn parse(spec: &str) -> Result<Self> {
let mut segs = lex::split_outside_quotes(spec, lex::PARAM_SEP).into_iter();
let head = segs.next().unwrap_or("");
let triple: Vec<&str> = lex::split_outside_quotes(head, lex::SLASH)
.into_iter()
.map(lex::trim_lws)
.collect();
let (proto, profile, lower) = match triple.as_slice() {
[p, pr] => (*p, *pr, None),
[p, pr, l] => (*p, *pr, Some(*l)),
_ => {
return bad(format!(
"transport-spec {head:?} is not transport/profile[/lower]"
));
}
};
if !proto.eq_ignore_ascii_case(PROTO_RTP) {
return bad(format!(
"unsupported transport protocol {proto:?} (only RTP)"
));
}
if !profile.eq_ignore_ascii_case(PROFILE_AVP) {
return bad(format!("unsupported profile {profile:?} (only AVP)"));
}
let mut out = TransportSpec {
lower_transport: match lower {
None => None,
Some(l) if l.eq_ignore_ascii_case(LOWER_TCP) => Some(LowerTransport::Tcp),
Some(l) if l.eq_ignore_ascii_case(LOWER_UDP) => Some(LowerTransport::Udp),
Some(l) => return bad(format!("unknown lower-transport {l:?}")),
},
..Default::default()
};
for seg in segs {
if lex::trim_lws(seg).is_empty() {
continue;
}
let (name, raw) = lex::split_param(seg)?;
out.apply(name, raw)?;
}
Ok(out)
}
fn apply(&mut self, name: &str, raw: Option<&str>) -> Result<()> {
let is = |n: &str| name.eq_ignore_ascii_case(n);
let value = match raw {
Some(v) => Some(lex::unquote(v)?.0),
None => None,
};
let need = |what: &str| -> Result<&str> {
match &value {
Some(v) => Ok(v.as_str()),
None => bad(format!("{what} requires a value")),
}
};
let flag = |what: &str| -> Result<()> {
match &value {
None => Ok(()),
Some(_) => bad(format!("{what} takes no value")),
}
};
if is("unicast") {
flag("unicast")?;
self.delivery = Some(Delivery::Unicast);
} else if is("multicast") {
flag("multicast")?;
self.delivery = Some(Delivery::Multicast);
} else if is("append") {
flag("append")?;
self.append = true;
} else if is("destination") {
self.destination = Some(value.unwrap_or_default());
} else if is("source") {
self.source = Some(value.unwrap_or_default());
} else if is("interleaved") {
let r = lex::range(
need("interleaved")?,
CHANNEL_DIGITS,
u8::MAX.into(),
"interleaved",
)?;
self.interleaved = Some(pair_u8(r));
} else if is("ttl") {
let t = lex::range(need("ttl")?, TTL_DIGITS, u8::MAX.into(), "ttl")?;
if t.0 != t.1 {
return bad("ttl is a single value");
}
self.ttl = Some(t.0 as u8);
} else if is("layers") {
self.layers = Some(lex::digits(need("layers")?, LAYERS_DIGITS, "layers")?);
} else if is("port") {
self.port = Some(pair_u16(lex::range(
need("port")?,
PORT_DIGITS,
u16::MAX.into(),
"port",
)?));
} else if is("client_port") {
self.client_port = Some(pair_u16(lex::range(
need("client_port")?,
PORT_DIGITS,
u16::MAX.into(),
"client_port",
)?));
} else if is("server_port") {
self.server_port = Some(pair_u16(lex::range(
need("server_port")?,
PORT_DIGITS,
u16::MAX.into(),
"server_port",
)?));
} else if is("ssrc") {
self.ssrc = Some(lex::hex_exact(need("ssrc")?, SSRC_HEX_DIGITS, "ssrc")?);
} else if is("mode") {
self.mode = lex::split_outside_quotes(need("mode")?, MODE_SEP)
.into_iter()
.map(lex::trim_lws)
.filter(|m| !m.is_empty())
.map(|m| {
if lex::is_token(m) {
Ok(TransportMode::from_token(m))
} else {
bad(format!("invalid mode method {m:?}"))
}
})
.collect::<Result<Vec<_>>>()?;
} else {
self.extensions.push((name.to_string(), value));
}
Ok(())
}
pub fn to_header_value(&self) -> Result<String> {
let emit = |v: &str| lex::emit_value(v).map_err(|e| Error::HeaderSerialize(e.0));
let mut s = format!("{PROTO_RTP}/{PROFILE_AVP}");
if let Some(lt) = self.lower_transport {
s.push(lex::SLASH);
s.push_str(match lt {
LowerTransport::Tcp => LOWER_TCP,
LowerTransport::Udp => LOWER_UDP,
});
}
let mut param = |text: String| {
s.push(lex::PARAM_SEP);
s.push_str(&text);
};
let kv = |k: &str, v: &str| format!("{k}{}{v}", lex::VALUE_SEP);
let rng =
|k: &str, lo: u32, hi: u32| format!("{k}{}{lo}{}{hi}", lex::VALUE_SEP, lex::RANGE_SEP);
if let Some(d) = self.delivery {
param(d.name().to_string());
}
if let Some(d) = &self.destination {
param(if d.is_empty() {
"destination".to_string()
} else {
kv("destination", &emit(d)?)
});
}
if let Some(d) = &self.source {
param(if d.is_empty() {
"source".to_string()
} else {
kv("source", &emit(d)?)
});
}
if let Some((lo, hi)) = self.interleaved {
param(rng("interleaved", lo.into(), hi.into()));
}
if self.append {
param("append".to_string());
}
if let Some(ttl) = self.ttl {
param(kv("ttl", &ttl.to_string()));
}
if let Some(l) = self.layers {
param(kv("layers", &l.to_string()));
}
if let Some((lo, hi)) = self.port {
param(rng("port", lo.into(), hi.into()));
}
if let Some((lo, hi)) = self.client_port {
param(rng("client_port", lo.into(), hi.into()));
}
if let Some((lo, hi)) = self.server_port {
param(rng("server_port", lo.into(), hi.into()));
}
if let Some(ssrc) = self.ssrc {
param(kv(
"ssrc",
&format!("{ssrc:0width$X}", width = SSRC_HEX_DIGITS),
));
}
if !self.mode.is_empty() {
if let Some(m) = self.mode.iter().find(|m| !lex::is_token(m.name())) {
return Err(Error::HeaderSerialize(format!(
"mode method {:?} is not a token",
m.name()
)));
}
let list = self
.mode
.iter()
.map(TransportMode::name)
.collect::<Vec<_>>()
.join(&MODE_SEP.to_string());
param(kv(
"mode",
&lex::emit_value_quoted(&list).map_err(|e| Error::HeaderSerialize(e.0))?,
));
}
for (name, value) in &self.extensions {
if !lex::is_token(name) {
return Err(Error::HeaderSerialize(format!(
"extension name {name:?} is not a token"
)));
}
param(match value {
Some(v) => kv(name, &emit(v)?),
None => name.clone(),
});
}
Ok(s)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Transport {
pub specs: Vec<TransportSpec>,
}
impl Transport {
pub fn single(spec: TransportSpec) -> Self {
Transport { specs: vec![spec] }
}
pub fn parse(value: &str) -> Result<Self> {
let specs = lex::split_outside_quotes(value, lex::LIST_SEP)
.into_iter()
.filter(|s| !lex::trim_lws(s).is_empty())
.map(TransportSpec::parse)
.collect::<Result<Vec<_>>>()?;
if specs.is_empty() {
return bad("no transport-specs");
}
Ok(Transport { specs })
}
pub fn to_header_value(&self) -> Result<String> {
Ok(self
.specs
.iter()
.map(TransportSpec::to_header_value)
.collect::<Result<Vec<_>>>()?
.join(&lex::LIST_SEP.to_string()))
}
pub fn first(&self) -> Option<&TransportSpec> {
self.specs.first()
}
}