use std::{
fmt::{Display, Write},
net::IpAddr,
str::FromStr,
};
use crate::{attributes::SrtpKeyParam, TypedAttribute};
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ParseEnumError {
Invalid(String),
}
impl std::error::Error for ParseEnumError {}
impl std::fmt::Display for ParseEnumError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseEnumError::Invalid(s) => {
write!(f, "Failed to parse {s} as an enum type")
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NetType {
In,
Tn,
Atm,
Pstn,
Other(String),
}
impl NetType {
pub fn as_str(&self) -> &str {
match self {
NetType::In => "IN",
NetType::Tn => "TN",
NetType::Atm => "ATM",
NetType::Pstn => "PSTN",
NetType::Other(nettype) => nettype.as_str(),
}
}
}
impl FromStr for NetType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"IN" => NetType::In,
"TN" => NetType::Tn,
"ATM" => NetType::Atm,
"PSTN" => NetType::Pstn,
other => NetType::Other(other.to_string()),
})
}
}
impl From<&str> for NetType {
fn from(value: &str) -> Self {
NetType::from_str(value).expect("infallible")
}
}
impl Display for NetType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AddrType {
Ip4,
Ip6,
Other(String),
}
impl AddrType {
pub fn new(addrtype: impl AsRef<str>) -> Self {
match addrtype.as_ref() {
"IP4" => AddrType::Ip4,
"IP6" => AddrType::Ip6,
other => AddrType::Other(other.to_string()),
}
}
pub fn is_ip(&self) -> bool {
matches!(self, AddrType::Ip4 | AddrType::Ip6)
}
pub fn as_str(&self) -> &str {
match self {
AddrType::Ip4 => "IP4",
AddrType::Ip6 => "IP6",
AddrType::Other(other) => other.as_str(),
}
}
}
impl FromStr for AddrType {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"IP4" => AddrType::Ip4,
"IP6" => AddrType::Ip6,
other => AddrType::Other(other.to_string()),
})
}
}
impl From<&str> for AddrType {
fn from(value: &str) -> Self {
AddrType::from_str(value).expect("infallible")
}
}
impl From<IpAddr> for AddrType {
fn from(addr: IpAddr) -> Self {
match addr {
IpAddr::V4(_) => AddrType::Ip4,
IpAddr::V6(_) => AddrType::Ip6,
}
}
}
impl Display for AddrType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BandwidthType {
Ct,
As,
Rr,
Rs,
}
impl BandwidthType {
pub fn as_str(&self) -> &'static str {
match self {
BandwidthType::As => "AS",
BandwidthType::Ct => "CT",
BandwidthType::Rr => "RR",
BandwidthType::Rs => "RS",
}
}
}
impl FromStr for BandwidthType {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"AS" => Ok(BandwidthType::As),
"CT" => Ok(BandwidthType::Ct),
"RR" => Ok(BandwidthType::Rr),
"RS" => Ok(BandwidthType::Rs),
_ => Err(ParseEnumError::Invalid(s.to_string())),
}
}
}
impl Display for BandwidthType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum KeyMethod {
Clear,
Base64,
Uri,
Prompt,
}
impl KeyMethod {
pub fn as_str(&self) -> &'static str {
match self {
KeyMethod::Clear => "clear",
KeyMethod::Base64 => "base64",
KeyMethod::Uri => "uri",
KeyMethod::Prompt => "prompt",
}
}
}
impl FromStr for KeyMethod {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"clear" => Ok(KeyMethod::Clear),
"base64" => Ok(KeyMethod::Base64),
"uri" => Ok(KeyMethod::Uri),
"prompt" => Ok(KeyMethod::Prompt),
_ => Err(ParseEnumError::Invalid(s.to_string())),
}
}
}
impl Display for KeyMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum MediaType {
Audio,
Video,
Text,
Application,
Message,
Image,
}
impl MediaType {
pub fn as_str(&self) -> &'static str {
match self {
MediaType::Audio => "audio",
MediaType::Video => "video",
MediaType::Text => "text",
MediaType::Application => "application",
MediaType::Message => "message",
MediaType::Image => "image",
}
}
}
impl FromStr for MediaType {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"audio" => Ok(MediaType::Audio),
"video" => Ok(MediaType::Video),
"text" => Ok(MediaType::Text),
"application" => Ok(MediaType::Application),
"message" => Ok(MediaType::Message),
"image" => Ok(MediaType::Image),
_ => Err(ParseEnumError::Invalid(s.to_string())),
}
}
}
impl Display for MediaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransportProto {
Udp,
RtpAvp,
RtpSavp,
RtpAvpf,
RtpSavpf,
TcpDtlsRtpSavp,
TcpDtlsRtpSavpf,
UdpTlsRtpSavp,
UdpTlsRtpSavpf,
UdpDtlsSctp,
TcpDtlsSctp,
DtlsSctp,
Other(String),
}
impl TransportProto {
pub fn as_str(&self) -> &str {
match self {
TransportProto::Udp => "udp",
TransportProto::RtpAvp => "RTP/AVP",
TransportProto::RtpAvpf => "RTP/AVPF",
TransportProto::RtpSavp => "RTP/SAVP",
TransportProto::RtpSavpf => "RTP/SAVPF",
TransportProto::TcpDtlsRtpSavp => "TCP/DTLS/RTP/SAVP",
TransportProto::TcpDtlsRtpSavpf => "TCP/DTLS/RTP/SAVPF",
TransportProto::UdpTlsRtpSavp => "UDP/TLS/RTP/SAVP",
TransportProto::UdpTlsRtpSavpf => "UDP/TLS/RTP/SAVPF",
TransportProto::UdpDtlsSctp => "UDP/DTLS/SCTP",
TransportProto::TcpDtlsSctp => "TCP/DTLS/SCTP",
TransportProto::DtlsSctp => "DTLS/SCTP",
TransportProto::Other(proto) => proto.as_str(),
}
}
pub fn is_rtp(&self) -> bool {
let proto = self.as_str();
proto.starts_with("RTP/") || proto.contains("/RTP/")
}
pub fn is_sctp(&self) -> bool {
self.as_str().ends_with("/SCTP")
}
pub fn is_secure(&self) -> bool {
self.is_rtp() && self.as_str().contains("SAVP")
}
pub fn is_avpf(&self) -> bool {
self.is_rtp() && self.as_str().ends_with("AVPF")
}
}
impl FromStr for TransportProto {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"udp" => TransportProto::Udp,
"RTP/AVP" => TransportProto::RtpAvp,
"RTP/AVPF" => TransportProto::RtpAvpf,
"RTP/SAVP" => TransportProto::RtpSavp,
"RTP/SAVPF" => TransportProto::RtpSavpf,
"TCP/DTLS/RTP/SAVP" => TransportProto::TcpDtlsRtpSavp,
"TCP/DTLS/RTP/SAVPF" => TransportProto::TcpDtlsRtpSavpf,
"UDP/TLS/RTP/SAVP" => TransportProto::UdpTlsRtpSavp,
"UDP/TLS/RTP/SAVPF" => TransportProto::UdpTlsRtpSavpf,
"UDP/DTLS/SCTP" => TransportProto::UdpDtlsSctp,
"TCP/DTLS/SCTP" => TransportProto::TcpDtlsSctp,
"DTLS/SCTP" => TransportProto::DtlsSctp,
other => TransportProto::Other(other.to_string()),
})
}
}
impl From<&str> for TransportProto {
fn from(value: &str) -> Self {
TransportProto::from_str(value).expect("infallible")
}
}
impl Display for TransportProto {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RidDirection {
Send,
Recv,
}
impl RidDirection {
pub fn as_str(&self) -> &'static str {
match self {
RidDirection::Send => "send",
RidDirection::Recv => "recv",
}
}
}
impl FromStr for RidDirection {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"send" => Ok(RidDirection::Send),
"recv" => Ok(RidDirection::Recv),
_ => Err(ParseEnumError::Invalid(s.to_string())),
}
}
}
impl std::fmt::Display for RidDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum HashFunc {
Sha1,
Sha224,
Sha256,
Sha384,
Sha512,
Md5,
Md2,
Other(String),
}
impl HashFunc {
pub fn new(hash_func: impl AsRef<str>) -> Self {
let hash_func = hash_func.as_ref();
if hash_func.eq_ignore_ascii_case("sha-1") {
HashFunc::Sha1
} else if hash_func.eq_ignore_ascii_case("sha-224") {
HashFunc::Sha224
} else if hash_func.eq_ignore_ascii_case("sha-256") {
HashFunc::Sha256
} else if hash_func.eq_ignore_ascii_case("sha-384") {
HashFunc::Sha384
} else if hash_func.eq_ignore_ascii_case("sha-512") {
HashFunc::Sha512
} else if hash_func.eq_ignore_ascii_case("md-5") {
HashFunc::Md5
} else if hash_func.eq_ignore_ascii_case("md-2") {
HashFunc::Md2
} else {
HashFunc::Other(hash_func.to_string())
}
}
pub fn as_str(&self) -> &str {
match self {
HashFunc::Sha1 => "sha-1",
HashFunc::Sha224 => "sha-224",
HashFunc::Sha256 => "sha-256",
HashFunc::Sha384 => "sha-384",
HashFunc::Sha512 => "sha-512",
HashFunc::Md5 => "md-5",
HashFunc::Md2 => "md-2",
HashFunc::Other(s) => s.as_str(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GroupSemantics {
LS,
FID,
SRF,
ANAT,
FEC,
DDP,
Bundle,
Other(String),
}
impl GroupSemantics {
pub fn new(semantics: impl AsRef<str>) -> Self {
let semantics = semantics.as_ref();
if "LS".eq_ignore_ascii_case(semantics) {
GroupSemantics::LS
} else if "FID".eq_ignore_ascii_case(semantics) {
GroupSemantics::FID
} else if "SRF".eq_ignore_ascii_case(semantics) {
GroupSemantics::SRF
} else if "ANAT".eq_ignore_ascii_case(semantics) {
GroupSemantics::ANAT
} else if "FEC".eq_ignore_ascii_case(semantics) {
GroupSemantics::FEC
} else if "DDP".eq_ignore_ascii_case(semantics) {
GroupSemantics::DDP
} else if "BUNDLE".eq_ignore_ascii_case(semantics) {
GroupSemantics::Bundle
} else {
GroupSemantics::Other(semantics.to_string())
}
}
pub fn as_str(&self) -> &str {
match self {
GroupSemantics::LS => "LS",
GroupSemantics::FID => "FID",
GroupSemantics::SRF => "SRF",
GroupSemantics::ANAT => "ANAT",
GroupSemantics::DDP => "DDP",
GroupSemantics::FEC => "FEC",
GroupSemantics::Bundle => "BUNDLE",
GroupSemantics::Other(s) => s.as_str(),
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CryptoSuite {
AesCm128HmacSha1_80,
AesCm128HmacSha1_32,
F8_128HmacSha1_80,
Other(String),
}
impl CryptoSuite {
pub fn new(crypto_suite: impl AsRef<str>) -> Self {
let crypto_suite = crypto_suite.as_ref();
if "AES_CM_128_HMAC_SHA1_32".eq_ignore_ascii_case(crypto_suite) {
CryptoSuite::AesCm128HmacSha1_32
} else if "F8_128_HMAC_SHA1_80".eq_ignore_ascii_case(crypto_suite) {
CryptoSuite::F8_128HmacSha1_80
} else if "AES_CM_128_HMAC_SHA1_80".eq_ignore_ascii_case(crypto_suite) {
CryptoSuite::AesCm128HmacSha1_80
} else {
CryptoSuite::Other(crypto_suite.to_string())
}
}
pub fn as_str(&self) -> &str {
match self {
CryptoSuite::AesCm128HmacSha1_80 => "AES_CM_128_HMAC_SHA1_80",
CryptoSuite::AesCm128HmacSha1_32 => "AES_CM_128_HMAC_SHA1_32",
CryptoSuite::F8_128HmacSha1_80 => "F8_128_HMAC_SHA1_80",
CryptoSuite::Other(s) => s.as_str(),
}
}
}
#[derive(Debug, PartialEq, Clone, Eq, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FecOrder {
FecSrtp,
SrtpFec,
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SrtpSessionParam {
Kdr(u8),
UnencryptedSrtp,
UnencryptedSrtcp,
UnauthenticatedSrtp,
FecOrder(FecOrder),
FecKey(Vec<SrtpKeyParam>),
Wsh(u8),
Extension(String),
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CandidateType {
Host,
Srflx,
Prflx,
Relay,
Other(String),
}
impl CandidateType {
pub fn new(cand_type: impl AsRef<str>) -> Self {
let cand_type = cand_type.as_ref();
if "host".eq_ignore_ascii_case(cand_type) {
CandidateType::Host
} else if "srflx".eq_ignore_ascii_case(cand_type) {
CandidateType::Srflx
} else if "prflx".eq_ignore_ascii_case(cand_type) {
CandidateType::Prflx
} else if "relay".eq_ignore_ascii_case(cand_type) {
CandidateType::Relay
} else {
CandidateType::Other(cand_type.to_string())
}
}
pub fn as_str(&self) -> &str {
match self {
CandidateType::Host => "host",
CandidateType::Srflx => "srflx",
CandidateType::Prflx => "prflx",
CandidateType::Relay => "relay",
CandidateType::Other(o) => o.as_str(),
}
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcpFbAck {
Rpsi,
App(Option<String>),
Ccfb,
Other(String),
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcpFbNack {
Pli,
Sli,
Rpsi,
App(Option<String>),
Ecn,
Other(String),
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcpFbCcm {
Fir,
Tmmbr(Option<String>),
Tstr,
Vbcm(Vec<u8>),
Other(String),
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcpFbVal {
Ack(Option<RtcpFbAck>),
Nack(Option<RtcpFbNack>),
TrrInt(u64),
Ccm(RtcpFbCcm),
TransportCc,
Other(String),
}
impl RtcpFbVal {
pub fn is_ack(&self) -> bool {
matches!(self, RtcpFbVal::Ack(None))
}
pub fn is_ack_rpsi(&self) -> bool {
matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::Rpsi)))
}
pub fn is_ack_app(&self) -> bool {
matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::App(_))))
}
pub fn is_ack_ccfb(&self) -> bool {
matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::Ccfb)))
}
pub fn is_nack(&self) -> bool {
matches!(self, RtcpFbVal::Nack(None))
}
pub fn is_nack_pli(&self) -> bool {
matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Pli)))
}
pub fn is_nack_sli(&self) -> bool {
matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Sli)))
}
pub fn is_nack_rpsi(&self) -> bool {
matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)))
}
pub fn is_nack_app(&self) -> bool {
matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::App(_))))
}
pub fn is_nack_ecn(&self) -> bool {
matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Ecn)))
}
pub fn is_trr_int(&self) -> bool {
matches!(self, RtcpFbVal::TrrInt(_))
}
pub fn is_ccm_fir(&self) -> bool {
matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Fir))
}
pub fn is_ccm_tmmbr(&self) -> bool {
matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(_)))
}
pub fn is_ccm_tstr(&self) -> bool {
matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Tstr))
}
pub fn is_ccm_vbcm(&self) -> bool {
matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(_)))
}
pub fn is_transport_cc(&self) -> bool {
matches!(self, RtcpFbVal::TransportCc)
}
}
impl From<RtcpFbAck> for RtcpFbVal {
fn from(value: RtcpFbAck) -> Self {
RtcpFbVal::Ack(Some(value))
}
}
impl From<RtcpFbNack> for RtcpFbVal {
fn from(value: RtcpFbNack) -> Self {
RtcpFbVal::Nack(Some(value))
}
}
impl From<RtcpFbCcm> for RtcpFbVal {
fn from(value: RtcpFbCcm) -> Self {
RtcpFbVal::Ccm(value)
}
}
impl Display for RtcpFbVal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RtcpFbVal::Ack(ack) => {
write!(f, "ack")?;
if let Some(ack) = ack {
match ack {
RtcpFbAck::Rpsi => write!(f, " rpsi")?,
RtcpFbAck::Ccfb => write!(f, " ccfb")?,
RtcpFbAck::App(app) => {
write!(f, " app")?;
if let Some(app_param) = app {
f.write_char(' ')?;
f.write_str(app_param)?;
}
}
RtcpFbAck::Other(other) => {
f.write_char(' ')?;
f.write_str(other)?;
}
}
}
Ok(())
}
RtcpFbVal::Nack(nack) => {
write!(f, "nack")?;
if let Some(nack) = nack {
match nack {
RtcpFbNack::Pli => write!(f, " pli")?,
RtcpFbNack::Sli => write!(f, " sli")?,
RtcpFbNack::Rpsi => write!(f, " rpsi")?,
RtcpFbNack::Ecn => write!(f, " ecn")?,
RtcpFbNack::App(app) => {
write!(f, " app")?;
if let Some(app_param) = app {
f.write_char(' ')?;
f.write_str(app_param)?;
}
}
RtcpFbNack::Other(other) => {
f.write_char(' ')?;
f.write_str(other)?;
}
}
}
Ok(())
}
RtcpFbVal::TrrInt(trr_int) => write!(f, "trr-int {trr_int}"),
RtcpFbVal::Ccm(ccm) => {
write!(f, "ccm")?;
match ccm {
RtcpFbCcm::Fir => write!(f, " fir")?,
RtcpFbCcm::Tstr => write!(f, " tstr")?,
RtcpFbCcm::Tmmbr(smaxpr) => {
write!(f, " tmmbr")?;
if let Some(smaxpr) = smaxpr {
f.write_char(' ')?;
f.write_str(smaxpr)?;
}
}
RtcpFbCcm::Vbcm(vbcm) => {
write!(f, " vbcm")?;
for v in vbcm {
f.write_char(' ')?;
write!(f, "{v}")?;
}
}
RtcpFbCcm::Other(other) => {
f.write_char(' ')?;
f.write_str(other)?;
}
}
Ok(())
}
RtcpFbVal::TransportCc => f.write_str("transport-cc"),
RtcpFbVal::Other(other) => f.write_str(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SsrcAttribute {
Cname,
PreviousSsrc,
Fmtp,
Rtcp,
ReferenceClock,
MediaClockSource,
Other(String),
}
impl SsrcAttribute {
pub fn new(attribute: impl AsRef<str>) -> Self {
use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
match attribute.as_ref() {
"cname" => SsrcAttribute::Cname,
"previous-ssrc" => SsrcAttribute::PreviousSsrc,
<Fmtp as TypedAttribute>::NAME => SsrcAttribute::Fmtp,
<Rtcp as TypedAttribute>::NAME => SsrcAttribute::Rtcp,
<ReferenceClock as TypedAttribute>::NAME => SsrcAttribute::ReferenceClock,
<MediaClockSource as TypedAttribute>::NAME => SsrcAttribute::MediaClockSource,
other => SsrcAttribute::Other(other.to_string()),
}
}
pub fn as_str(&self) -> &str {
use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
match self {
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(),
}
}
}
impl<T: TypedAttribute> From<T> for SsrcAttribute {
fn from(_attr: T) -> Self {
SsrcAttribute::new(T::NAME)
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RtcpFbPt {
Fmt(u8),
Wildcard,
}
impl From<u8> for RtcpFbPt {
fn from(pt: u8) -> Self {
RtcpFbPt::Fmt(pt)
}
}
#[derive(Debug, PartialEq, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CandidateAddress {
IpAddr(std::net::IpAddr),
FQDN(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transport_proto_classification() {
let cases = [
(TransportProto::Udp, false, false, false, false),
(TransportProto::RtpAvp, true, false, false, false),
(TransportProto::RtpAvpf, true, false, false, true),
(TransportProto::RtpSavp, true, false, true, false),
(TransportProto::RtpSavpf, true, false, true, true),
(TransportProto::TcpDtlsRtpSavp, true, false, true, false),
(TransportProto::TcpDtlsRtpSavpf, true, false, true, true),
(TransportProto::UdpTlsRtpSavp, true, false, true, false),
(TransportProto::UdpTlsRtpSavpf, true, false, true, true),
(TransportProto::UdpDtlsSctp, false, true, false, false),
(TransportProto::TcpDtlsSctp, false, true, false, false),
(TransportProto::DtlsSctp, false, true, false, false),
(
TransportProto::Other("FOO".into()),
false,
false,
false,
false,
),
(
TransportProto::Other("RTP/AV".into()),
true,
false,
false,
false,
),
(
TransportProto::Other("RTP/AVP/TCP".into()),
true,
false,
false,
false,
),
(
TransportProto::Other("UDP/TLS/RTP/SAVPF".into()),
true,
false,
true,
true,
),
(
TransportProto::Other("AVPF".into()),
false,
false,
false,
false,
),
];
for (proto, rtp, sctp, secure, avpf) in cases {
assert_eq!(proto.is_rtp(), rtp, "{:?} is_rtp", proto);
assert_eq!(proto.is_sctp(), sctp, "{:?} is_sctp", proto);
assert_eq!(proto.is_secure(), secure, "{:?} is_secure", proto);
assert_eq!(proto.is_avpf(), avpf, "{:?} is_avpf", proto);
}
}
#[test]
fn group_semantics_case() {
let cases = [
("ls", GroupSemantics::LS),
("LS", GroupSemantics::LS),
("fid", GroupSemantics::FID),
("srf", GroupSemantics::SRF),
("anat", GroupSemantics::ANAT),
("fec", GroupSemantics::FEC),
("ddp", GroupSemantics::DDP),
("bundle", GroupSemantics::Bundle),
("Bundle", GroupSemantics::Bundle),
("BUNDLE", GroupSemantics::Bundle),
("foo", GroupSemantics::Other("foo".to_string())),
];
for (value, semantics) in cases {
assert_eq!(GroupSemantics::new(value), semantics, "{value}");
}
let cases = [
(GroupSemantics::LS, "LS"),
(GroupSemantics::FID, "FID"),
(GroupSemantics::SRF, "SRF"),
(GroupSemantics::ANAT, "ANAT"),
(GroupSemantics::FEC, "FEC"),
(GroupSemantics::DDP, "DDP"),
(GroupSemantics::Bundle, "BUNDLE"),
(GroupSemantics::Other("foo".to_string()), "foo"),
];
for (semantics, value) in cases {
assert_eq!(semantics.as_str(), value, "{semantics:?}");
}
}
}