use derive_into_owned::IntoOwned;
use enum_as_inner::EnumAsInner;
use nom::{
branch::alt,
bytes::complete::{is_not, tag},
combinator::map,
sequence::{preceded, separated_pair, tuple},
IResult,
};
use std::borrow::Cow;
pub mod candidate;
pub mod dtls;
pub mod extmap;
pub mod ice;
pub mod rtcp;
pub mod rtpmap;
pub mod ssrc;
use crate::parsers::*;
#[cfg(test)]
use crate::{assert_line, assert_line_print};
pub use bundle::*;
pub use candidate::*;
pub use control::*;
pub use direction::*;
pub use fingerprint::*;
pub use fmtp::*;
pub use ice::*;
pub use rtcp_option::*;
pub use rtp::*;
pub use ssrc::*;
#[derive(Clone, IntoOwned, EnumAsInner, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[non_exhaustive]
pub enum AttributeLine<'a> {
Candidate(candidate::Candidate<'a>),
Ice(ice::IceParameter<'a>),
Mid(mid::Mid<'a>),
MsidSemantic(msid::MsidSemantic<'a>),
Msid(msid::Msid<'a>),
RtpMap(rtpmap::RtpMap<'a>),
PTime(rtpmap::PTime),
Ssrc(Ssrc<'a>),
BundleGroup(BundleGroup<'a>),
SsrcGroup(SsrcGroup),
Fingerprint(Fingerprint<'a>),
Direction(Direction),
Rtp(Rtp<'a>),
Rtcp(rtcp::Rtcp),
Fmtp(Fmtp<'a>),
RtcpFb(rtcp::Fb<'a>),
RtcpOption(RtcpOption),
Control(Control<'a>),
SetupRole(dtls::SetupRole),
Extmap(extmap::Extmap<'a>),
BundleOnly,
EoC,
KeyValue {
key: Cow<'a, str>,
val: Cow<'a, str>,
},
KeyOnly(Cow<'a, str>),
}
pub fn attribute_line(input: &str) -> IResult<&str, AttributeLine> {
alt((
alt((
map(mid::mid_line, AttributeLine::Mid),
map(msid::msid_semantic_line, AttributeLine::MsidSemantic),
map(msid::msid_line, AttributeLine::Msid),
map(bundle::bundle_group_line, AttributeLine::BundleGroup),
map(ice::ice_parameter_line, AttributeLine::Ice),
map(ssrc::ssrc_line, AttributeLine::Ssrc),
map(ssrc::ssrc_group_line, AttributeLine::SsrcGroup),
map(rtpmap::rtpmap_line, AttributeLine::RtpMap),
map(rtpmap::read_p_time, AttributeLine::PTime),
map(fingerprint_line, AttributeLine::Fingerprint),
)),
alt((
map(candidate::candidate_line, AttributeLine::Candidate),
map(direction::direction_line, AttributeLine::Direction),
map(extmap::extmap_line, AttributeLine::Extmap),
map(dtls::setup_role_line, AttributeLine::SetupRole),
map(rtp_attribute_line, AttributeLine::Rtp),
map(rtcp::rtcp_attribute_line, AttributeLine::Rtcp),
map(fmtp_attribute_line, AttributeLine::Fmtp),
map(control_attribute_line, AttributeLine::Control),
map(rtcp::rtcpfb_attribute_line, AttributeLine::RtcpFb),
map(rtp_option_line, AttributeLine::RtcpOption),
map(generic::key_val_attribute_line, |(key, val)| {
AttributeLine::KeyValue { key, val }
}),
map(tag("a=bundle-only"), |_| AttributeLine::BundleOnly),
map(tag("a=end-of-candidates"), |_| AttributeLine::EoC),
map(generic::key_only_attribute_line, AttributeLine::KeyOnly),
)),
))(input)
}
#[test]
fn test_attribute_line() {
assert_line_print!(attribute_line, "a=bundle-only");
assert_line_print!(attribute_line, "a=end-of-candidates");
assert_line_print!(attribute_line, "a=extmap-allowed-mixed");
}
pub mod generic {
use super::*;
pub fn key_val_attribute_line(input: &str) -> IResult<&str, (Cow<'_, str>, Cow<'_, str>)> {
a_line(map(
separated_pair(
cowify(read_non_colon_string),
tag(":"),
cowify(is_not("\n")),
),
|(key, val)| (key, val),
))(input)
}
pub fn key_only_attribute_line(input: &str) -> IResult<&str, Cow<'_, str>> {
a_line(cowify(is_not("\n")))(input)
}
#[test]
fn test_lazy_attribute_line() {
assert_line!(
key_val_attribute_line,
"a=foo:bar",
("foo".into(), "bar".into())
);
assert_line!(
key_val_attribute_line,
"a=fmtp:111 minptime=10; useinbandfec=1",
("fmtp".into(), "111 minptime=10; useinbandfec=1".into())
);
assert_line!(
key_val_attribute_line,
"a=setup:actpass",
("setup".into(), "actpass".into())
);
}
}
pub mod bundle {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct BundleGroup<'a>(pub Vec<Cow<'a, str>>);
pub fn bundle_group_line(input: &str) -> IResult<&str, BundleGroup> {
attribute("group", bundle_group)(input)
}
fn bundle_group(input: &str) -> IResult<&str, BundleGroup> {
preceded(
tag("BUNDLE"),
map(wsf(space_separated_cow_strings), BundleGroup),
)(input)
}
#[test]
fn test_bundle_group_line() {
assert_line!(
bundle_group_line,
"a=group:BUNDLE 0 1",
BundleGroup(create_test_vec(&["0", "1"])),
print
);
assert_line!(
bundle_group_line,
"a=group:BUNDLE video",
BundleGroup(create_test_vec(&["video"])),
print
);
assert_line!(
bundle_group_line,
"a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2",
BundleGroup(create_test_vec(&["sdparta_0", "sdparta_1", "sdparta_2"])),
print
);
}
}
pub mod rtp {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Rtp<'a> {
pub payload: u32,
pub codec: Cow<'a, str>,
pub rate: u32,
pub encoding: u32,
}
pub fn rtp_attribute_line(input: &str) -> IResult<&str, Rtp> {
attribute("rtpmap", rtp_attribute)(input)
}
fn rtp_attribute(input: &str) -> IResult<&str, Rtp> {
map(
tuple((
wsf(read_number), wsf(cowify(read_non_slash_string)), preceded(tag("/"), read_number), preceded(tag("/"), read_number), )),
|(payload, codec, rate, encoding)| Rtp {
payload,
codec,
rate,
encoding,
},
)(input)
}
#[test]
fn test_rtp_attribute_line() {
assert_line!("a=rtpmap:110 opus/48000/2", rtp_attribute_line);
}
}
pub mod fmtp {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Fmtp<'a> {
pub payload: u32,
pub config: Cow<'a, str>,
}
pub fn fmtp_attribute_line(input: &str) -> IResult<&str, Fmtp> {
attribute("fmtp", fmtp_attribute)(input)
}
fn fmtp_attribute(input: &str) -> IResult<&str, Fmtp> {
map(
tuple((
read_number, cowify(wsf(is_not("\n"))), )),
|(payload, config)| (Fmtp { payload, config }),
)(input)
}
#[test]
fn test_fmtp_attribute_line() {
assert_line!(
fmtp_attribute_line,
"a=fmtp:108 profile-level-id=24;object=23;bitrate=64000",
Fmtp {
payload: 108,
config: "profile-level-id=24;object=23;bitrate=64000".into(),
},
print
);
assert_line_print!(
fmtp_attribute_line,
"a=fmtp:111 minptime=10; useinbandfec=1"
);
}
}
pub mod control {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Control<'a>(pub Cow<'a, str>);
pub fn control_attribute_line(input: &str) -> IResult<&str, Control> {
attribute("control", control_attribute)(input)
}
fn control_attribute(input: &str) -> IResult<&str, Control> {
map(cowify(read_string), Control)(input)
}
#[test]
fn test_control_attribute_line() {
assert_line_print!(control_attribute_line, "a=control:streamid=0");
}
}
pub mod direction {
use super::*;
#[derive(PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[non_exhaustive]
pub enum Direction {
SendOnly,
SendRecv,
RecvOnly,
Inactive,
}
pub fn read_direction(input: &str) -> IResult<&str, Direction> {
alt((
map(tag("sendrecv"), |_| Direction::SendRecv),
map(tag("sendonly"), |_| Direction::SendOnly),
map(tag("recvonly"), |_| Direction::RecvOnly),
map(tag("inactive"), |_| Direction::Inactive),
))(input)
}
pub fn direction_line(input: &str) -> IResult<&str, Direction> {
a_line(wsf(read_direction))(input)
}
#[test]
fn test_direction_line() {
assert_line!(read_direction, "sendrecv", Direction::SendRecv);
assert_line!(direction_line, "a=sendrecv", Direction::SendRecv);
assert_line!(read_direction, "sendonly", Direction::SendOnly);
assert_line!(direction_line, "a=sendonly", Direction::SendOnly);
assert_line!(read_direction, "recvonly", Direction::RecvOnly);
assert_line!(direction_line, "a=recvonly", Direction::RecvOnly);
assert_line!(read_direction, "inactive", Direction::Inactive);
assert_line!(direction_line, "a=inactive", Direction::Inactive);
}
}
pub mod rtcp_option {
use super::*;
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
#[non_exhaustive]
pub enum RtcpOption {
RtcpMux,
RtcpMuxOnly,
RtcpRsize,
}
pub fn rtp_option(input: &str) -> IResult<&str, RtcpOption> {
alt((
map(tag("rtcp-rsize"), |_| RtcpOption::RtcpRsize),
map(tag("rtcp-mux-only"), |_| RtcpOption::RtcpMuxOnly),
map(tag("rtcp-mux"), |_| RtcpOption::RtcpMux),
))(input)
}
pub fn rtp_option_line(input: &str) -> IResult<&str, RtcpOption> {
a_line(rtp_option)(input)
}
#[test]
fn test_read_rtp_option() {
assert_line!(rtp_option_line, "a=rtcp-mux", RtcpOption::RtcpMux, print);
assert_line!(
rtp_option_line,
"a=rtcp-mux-only",
RtcpOption::RtcpMuxOnly,
print
);
assert_line!(
rtp_option_line,
"a=rtcp-rsize",
RtcpOption::RtcpRsize,
print
);
}
}
pub mod fingerprint {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Fingerprint<'a> {
pub r#type: Cow<'a, str>,
pub hash: Cow<'a, str>,
}
pub fn fingerprint_line(input: &str) -> IResult<&str, Fingerprint> {
attribute("fingerprint", fingerprint)(input)
}
pub fn fingerprint(input: &str) -> IResult<&str, Fingerprint> {
map(
tuple((
cowify(wsf(read_string)), cowify(wsf(read_string)), )),
|(r#type, hash)| Fingerprint { r#type, hash },
)(input)
}
#[test]
fn test_fingerprint_line() {
assert_line_print!(
fingerprint_line,
"a=fingerprint:sha-256 19:E2:1C:3B:4B:9F:81:E6:B8:5C:F4:A5:A8:D8:73:04:BB:05:2F:70:9F:04:A9:0E:05:E9:26:33:E8:70:88:A2");
}
}
pub mod mid {
use super::*;
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Mid<'a>(pub Cow<'a, str>);
pub fn mid_line(input: &str) -> IResult<&str, Mid> {
attribute("mid", mid)(input)
}
pub fn mid(input: &str) -> IResult<&str, Mid> {
map(cowify(read_string), Mid)(input)
}
#[test]
fn test_mid_line() {
assert_line_print!(mid_line, "a=mid:1");
assert_line_print!(mid_line, "a=mid:a1");
assert_line_print!(mid_line, "a=mid:0");
assert_line_print!(mid_line, "a=mid:audio")
}
}
pub mod msid {
use nom::{character::complete::multispace1, combinator::opt};
use super::*;
#[derive(Clone, derive_into_owned::IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct MsidSemantic<'a> {
pub semantic: Cow<'a, str>,
pub token: Option<Cow<'a, str>>,
}
pub fn msid_semantic_line(input: &str) -> IResult<&str, MsidSemantic> {
attribute("msid-semantic", msid_semantic)(input)
}
pub fn msid_semantic(input: &str) -> IResult<&str, MsidSemantic> {
wsf(map(
tuple((cowify(read_string), multispace1, opt(cowify(read_string)))),
|(semantic, _, token)| MsidSemantic { semantic, token },
))(input)
}
#[test]
fn test_msid_semantic_line() {
assert_line!(
msid_semantic_line,
"a=msid-semantic: WMS lgsCFqt9kN2fVKw5wg3NKqGdATQoltEwOdMS",
MsidSemantic {
semantic: Cow::Borrowed("WMS"),
token: Some(Cow::Borrowed("lgsCFqt9kN2fVKw5wg3NKqGdATQoltEwOdMS"))
}
);
assert_line_print!(
msid_semantic_line,
"a=msid-semantic: WMS lgsCFqt9kN2fVKw5wg3NKqGdATQoltEwOdMS"
);
}
#[derive(Clone, IntoOwned, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct Msid<'a>(pub Vec<Cow<'a, str>>);
pub fn msid_line(input: &str) -> IResult<&str, Msid> {
attribute("msid", msid)(input)
}
pub fn msid(input: &str) -> IResult<&str, Msid> {
wsf(map(space_separated_cow_strings, Msid))(input)
}
#[test]
fn test_msid_line() {
assert_line!(
msid_line,
"a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9",
Msid(vec![
"47017fee-b6c1-4162-929c-a25110252400".into(),
"f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9".into()
]),
print
);
assert_line_print!(
msid_line,
"a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae f30bdb4a-5db8-49b5-bcdc-e0c9a23172e0"
);
}
}