Skip to main content

sdp_types/
enums.rs

1// Copyright (C) 2026 Taruntej Kanakamalla <tarun@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5//! Contains all the helper enums used by a Session, Media and other Attribute structs
6
7use std::{
8    fmt::{Display, Write},
9    net::IpAddr,
10    str::FromStr,
11};
12
13use crate::{attributes::SrtpKeyParam, TypedAttribute};
14
15/// Errors while parsing strings to Enum
16#[derive(Debug, PartialEq, Eq, Clone)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub enum ParseEnumError {
19    Invalid(String),
20}
21
22impl std::error::Error for ParseEnumError {}
23
24impl std::fmt::Display for ParseEnumError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            ParseEnumError::Invalid(s) => {
28                write!(f, "Failed to parse {s} as an enum type")
29            }
30        }
31    }
32}
33
34/// Type of network of the originator or a connection of the session.
35///
36/// See [RFC 8866 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8866#section-5.2),
37/// [RFC 8866 Section 5.7](https://datatracker.ietf.org/doc/html/rfc8866#section-5.7) and
38/// [RFC 8866 Section 8.2.6](https://datatracker.ietf.org/doc/html/rfc8866#section-8.2.6) for more details
39#[derive(Debug, PartialEq, Eq, Clone)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub enum NetType {
42    /// Internet
43    In,
44    /// Telephone Network
45    Tn,
46    /// ATM Bearer Connection
47    Atm,
48    /// Public Switched Telephone Network
49    Pstn,
50    /// Other
51    Other(String),
52}
53
54impl NetType {
55    pub fn as_str(&self) -> &str {
56        match self {
57            NetType::In => "IN",
58            NetType::Tn => "TN",
59            NetType::Atm => "ATM",
60            NetType::Pstn => "PSTN",
61            NetType::Other(nettype) => nettype.as_str(),
62        }
63    }
64}
65
66impl FromStr for NetType {
67    // FIXME use the never type when it is stable
68    type Err = ();
69
70    fn from_str(s: &str) -> Result<Self, Self::Err> {
71        Ok(match s {
72            "IN" => NetType::In,
73            "TN" => NetType::Tn,
74            "ATM" => NetType::Atm,
75            "PSTN" => NetType::Pstn,
76            other => NetType::Other(other.to_string()),
77        })
78    }
79}
80
81impl From<&str> for NetType {
82    fn from(value: &str) -> Self {
83        NetType::from_str(value).expect("infallible")
84    }
85}
86
87impl Display for NetType {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.write_str(self.as_str())
90    }
91}
92
93/// Type of address of the originator or a connection of the session
94///
95/// See [RFC 8866 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8866#section-5.2),
96/// [RFC 8866 Section 5.7](https://datatracker.ietf.org/doc/html/rfc8866#section-5.7)
97#[derive(Debug, PartialEq, Eq, Clone)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99pub enum AddrType {
100    /// IPv4 address
101    Ip4,
102    /// IPv6 address
103    Ip6,
104    /// Other
105    Other(String),
106}
107
108impl AddrType {
109    pub fn new(addrtype: impl AsRef<str>) -> Self {
110        match addrtype.as_ref() {
111            "IP4" => AddrType::Ip4,
112            "IP6" => AddrType::Ip6,
113            other => AddrType::Other(other.to_string()),
114        }
115    }
116
117    /// Whether `self` matches an IPv4 or IPv6 address.
118    pub fn is_ip(&self) -> bool {
119        matches!(self, AddrType::Ip4 | AddrType::Ip6)
120    }
121
122    pub fn as_str(&self) -> &str {
123        match self {
124            AddrType::Ip4 => "IP4",
125            AddrType::Ip6 => "IP6",
126            AddrType::Other(other) => other.as_str(),
127        }
128    }
129}
130
131impl FromStr for AddrType {
132    // FIXME use the never type when it is stable
133    type Err = ();
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        Ok(match s {
137            "IP4" => AddrType::Ip4,
138            "IP6" => AddrType::Ip6,
139            other => AddrType::Other(other.to_string()),
140        })
141    }
142}
143
144impl From<&str> for AddrType {
145    fn from(value: &str) -> Self {
146        AddrType::from_str(value).expect("infallible")
147    }
148}
149
150impl From<IpAddr> for AddrType {
151    fn from(addr: IpAddr) -> Self {
152        match addr {
153            IpAddr::V4(_) => AddrType::Ip4,
154            IpAddr::V6(_) => AddrType::Ip6,
155        }
156    }
157}
158
159impl Display for AddrType {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        f.write_str(self.as_str())
162    }
163}
164
165/// Type of the Bandwidth value.
166///
167/// See [RFC 8866 Section 5.8](https://datatracker.ietf.org/doc/html/rfc8866#section-5.8) for more details.
168#[derive(Debug, PartialEq, Eq, Clone, Copy)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub enum BandwidthType {
171    /// Conference total - maximum bandwidth a session will use
172    Ct,
173    /// Application Specific maximum bandwidth
174    As,
175    /// Bandwidth assigned for RTCP reports by active receivers. See [RFC 3890 Section 1.1.3](https://datatracker.ietf.org/doc/html/rfc3890#section-1.1.3)
176    Rr,
177    /// Bandwidth assigned for RTCP reports by active senders. See [RFC 3890 Section 1.1.3](https://datatracker.ietf.org/doc/html/rfc3890#section-1.1.3)
178    Rs,
179}
180
181impl BandwidthType {
182    pub fn as_str(&self) -> &'static str {
183        match self {
184            BandwidthType::As => "AS",
185            BandwidthType::Ct => "CT",
186            BandwidthType::Rr => "RR",
187            BandwidthType::Rs => "RS",
188        }
189    }
190}
191
192impl FromStr for BandwidthType {
193    type Err = ParseEnumError;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        match s {
197            "AS" => Ok(BandwidthType::As),
198            "CT" => Ok(BandwidthType::Ct),
199            "RR" => Ok(BandwidthType::Rr),
200            "RS" => Ok(BandwidthType::Rs),
201            _ => Err(ParseEnumError::Invalid(s.to_string())),
202        }
203    }
204}
205
206impl Display for BandwidthType {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        f.write_str(self.as_str())
209    }
210}
211
212/// Method of encryption (Obsolete)
213///
214/// Note: This field is obsolete and MUST NOT be used. It is included only for legacy reasons
215/// See [RFC 8866 Section 5.12](https://datatracker.ietf.org/doc/html/rfc8866#section-5.12)
216#[derive(Debug, PartialEq, Eq, Clone, Copy)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218pub enum KeyMethod {
219    /// Untransformed
220    Clear,
221    /// Base64 encoded
222    Base64,
223    /// URI to obtain the key
224    Uri,
225    /// User should be prompted for the key
226    Prompt,
227}
228
229impl KeyMethod {
230    pub fn as_str(&self) -> &'static str {
231        match self {
232            KeyMethod::Clear => "clear",
233            KeyMethod::Base64 => "base64",
234            KeyMethod::Uri => "uri",
235            KeyMethod::Prompt => "prompt",
236        }
237    }
238}
239
240impl FromStr for KeyMethod {
241    type Err = ParseEnumError;
242
243    fn from_str(s: &str) -> Result<Self, Self::Err> {
244        // case-sensitive
245        match s {
246            "clear" => Ok(KeyMethod::Clear),
247            "base64" => Ok(KeyMethod::Base64),
248            "uri" => Ok(KeyMethod::Uri),
249            "prompt" => Ok(KeyMethod::Prompt),
250            _ => Err(ParseEnumError::Invalid(s.to_string())),
251        }
252    }
253}
254
255impl Display for KeyMethod {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        f.write_str(self.as_str())
258    }
259}
260
261/// The media type
262///
263/// See [RFC 8866 Section 5.14](https://datatracker.ietf.org/doc/html/rfc8866#section-5.14)
264#[derive(Debug, PartialEq, Eq, Clone, Copy)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266pub enum MediaType {
267    /// Audio type
268    Audio,
269    /// Video type
270    Video,
271    /// Text type
272    Text,
273    /// Application type
274    Application,
275    /// Message type
276    Message,
277    /// Image type. See [RFC 6466](https://datatracker.ietf.org/doc/html/rfc6466)
278    Image,
279}
280
281impl MediaType {
282    pub fn as_str(&self) -> &'static str {
283        match self {
284            MediaType::Audio => "audio",
285            MediaType::Video => "video",
286            MediaType::Text => "text",
287            MediaType::Application => "application",
288            MediaType::Message => "message",
289            MediaType::Image => "image",
290        }
291    }
292}
293
294impl FromStr for MediaType {
295    type Err = ParseEnumError;
296
297    fn from_str(s: &str) -> Result<Self, Self::Err> {
298        match s {
299            "audio" => Ok(MediaType::Audio),
300            "video" => Ok(MediaType::Video),
301            "text" => Ok(MediaType::Text),
302            "application" => Ok(MediaType::Application),
303            "message" => Ok(MediaType::Message),
304            "image" => Ok(MediaType::Image),
305            _ => Err(ParseEnumError::Invalid(s.to_string())),
306        }
307    }
308}
309
310impl Display for MediaType {
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        f.write_str(self.as_str())
313    }
314}
315
316/// Transport Protocol for the media
317///
318/// See [RFC 8866 Section 5.14](https://datatracker.ietf.org/doc/html/rfc8866#section-5.14)
319#[derive(Debug, PartialEq, Eq, Clone)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub enum TransportProto {
322    /// Direct UDP
323    Udp,
324    /// RTP over UDP
325    RtpAvp,
326    /// Secure RTP over UDP
327    RtpSavp,
328    /// RTP over UDP with RTCP-based feedback
329    RtpAvpf,
330    /// Secure RTP over UDP with RTCP-based feedback
331    RtpSavpf,
332    /// RTP over TCP/DTLS
333    TcpDtlsRtpSavp,
334    /// RTP over TCP/DTLS with RTCP-based feedback
335    TcpDtlsRtpSavpf,
336    /// RTP over UDP/TLS
337    UdpTlsRtpSavp,
338    /// RTP over UDP/TLS with RTCP-based feedback
339    UdpTlsRtpSavpf,
340    UdpDtlsSctp,
341    TcpDtlsSctp,
342    DtlsSctp,
343    Other(String),
344}
345
346impl TransportProto {
347    pub fn as_str(&self) -> &str {
348        match self {
349            // The spec (RFC 8866, section 5.14) uses lower-case for the "udp"
350            // protocol and upper-case for the others
351            TransportProto::Udp => "udp",
352            TransportProto::RtpAvp => "RTP/AVP",
353            TransportProto::RtpAvpf => "RTP/AVPF",
354            TransportProto::RtpSavp => "RTP/SAVP",
355            TransportProto::RtpSavpf => "RTP/SAVPF",
356            TransportProto::TcpDtlsRtpSavp => "TCP/DTLS/RTP/SAVP",
357            TransportProto::TcpDtlsRtpSavpf => "TCP/DTLS/RTP/SAVPF",
358            TransportProto::UdpTlsRtpSavp => "UDP/TLS/RTP/SAVP",
359            TransportProto::UdpTlsRtpSavpf => "UDP/TLS/RTP/SAVPF",
360            TransportProto::UdpDtlsSctp => "UDP/DTLS/SCTP",
361            TransportProto::TcpDtlsSctp => "TCP/DTLS/SCTP",
362            TransportProto::DtlsSctp => "DTLS/SCTP",
363            TransportProto::Other(proto) => proto.as_str(),
364        }
365    }
366
367    /// Whether this is an RTP transport (any of the `RTP/...` profiles).
368    ///
369    /// Profile strings are classified by their RTP profile component
370    /// (RFC 8866, section 5.7), so unregistered profiles containing it (e.g.
371    /// the legacy `RTP/AVP/TCP`) are also detected. Returns `false` for SCTP
372    /// transports and [`TransportProto::Udp`].
373    pub fn is_rtp(&self) -> bool {
374        let proto = self.as_str();
375        proto.starts_with("RTP/") || proto.contains("/RTP/")
376    }
377
378    /// Whether this is an SCTP transport (used for datachannels).
379    pub fn is_sctp(&self) -> bool {
380        self.as_str().ends_with("/SCTP")
381    }
382
383    /// Whether this is an RTP transport secured with SRTP (the `SAVP`
384    /// profiles).
385    pub fn is_secure(&self) -> bool {
386        self.is_rtp() && self.as_str().contains("SAVP")
387    }
388
389    /// Whether this RTP transport includes the RTP feedback (`F`) profile
390    /// extension, i.e. it supports per-PT `a=rtcp-fb` attributes.
391    pub fn is_avpf(&self) -> bool {
392        self.is_rtp() && self.as_str().ends_with("AVPF")
393    }
394}
395
396impl FromStr for TransportProto {
397    // FIXME use the never type when it is stable
398    type Err = ();
399
400    fn from_str(s: &str) -> Result<Self, Self::Err> {
401        Ok(match s {
402            "udp" => TransportProto::Udp,
403            "RTP/AVP" => TransportProto::RtpAvp,
404            "RTP/AVPF" => TransportProto::RtpAvpf,
405            "RTP/SAVP" => TransportProto::RtpSavp,
406            "RTP/SAVPF" => TransportProto::RtpSavpf,
407            "TCP/DTLS/RTP/SAVP" => TransportProto::TcpDtlsRtpSavp,
408            "TCP/DTLS/RTP/SAVPF" => TransportProto::TcpDtlsRtpSavpf,
409            "UDP/TLS/RTP/SAVP" => TransportProto::UdpTlsRtpSavp,
410            "UDP/TLS/RTP/SAVPF" => TransportProto::UdpTlsRtpSavpf,
411            "UDP/DTLS/SCTP" => TransportProto::UdpDtlsSctp,
412            "TCP/DTLS/SCTP" => TransportProto::TcpDtlsSctp,
413            "DTLS/SCTP" => TransportProto::DtlsSctp,
414            other => TransportProto::Other(other.to_string()),
415        })
416    }
417}
418
419impl From<&str> for TransportProto {
420    fn from(value: &str) -> Self {
421        TransportProto::from_str(value).expect("infallible")
422    }
423}
424
425impl Display for TransportProto {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        f.write_str(self.as_str())
428    }
429}
430
431/// Direction of a restricted RTP stream
432///
433/// See [RFC 8851 Section 4](https://datatracker.ietf.org/doc/html/rfc8851#section-4)
434#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
435#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
436pub enum RidDirection {
437    /// Sender-side restriction
438    Send,
439    /// Receiver-side restriction
440    Recv,
441}
442
443impl RidDirection {
444    pub fn as_str(&self) -> &'static str {
445        match self {
446            RidDirection::Send => "send",
447            RidDirection::Recv => "recv",
448        }
449    }
450}
451
452impl FromStr for RidDirection {
453    type Err = ParseEnumError;
454
455    fn from_str(s: &str) -> Result<Self, Self::Err> {
456        match s {
457            "send" => Ok(RidDirection::Send),
458            "recv" => Ok(RidDirection::Recv),
459            _ => Err(ParseEnumError::Invalid(s.to_string())),
460        }
461    }
462}
463
464impl std::fmt::Display for RidDirection {
465    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466        f.write_str(self.as_str())
467    }
468}
469
470/// Secure Hash functions
471///
472/// See [RFC 8122 Table 1](https://datatracker.ietf.org/doc/html/rfc8122#section-8)
473#[derive(Debug, Clone, PartialEq, Eq)]
474#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
475pub enum HashFunc {
476    Sha1,
477    Sha224,
478    Sha256,
479    Sha384,
480    Sha512,
481    Md5,
482    Md2,
483    Other(String),
484}
485
486impl HashFunc {
487    pub fn new(hash_func: impl AsRef<str>) -> Self {
488        let hash_func = hash_func.as_ref();
489
490        if hash_func.eq_ignore_ascii_case("sha-1") {
491            HashFunc::Sha1
492        } else if hash_func.eq_ignore_ascii_case("sha-224") {
493            HashFunc::Sha224
494        } else if hash_func.eq_ignore_ascii_case("sha-256") {
495            HashFunc::Sha256
496        } else if hash_func.eq_ignore_ascii_case("sha-384") {
497            HashFunc::Sha384
498        } else if hash_func.eq_ignore_ascii_case("sha-512") {
499            HashFunc::Sha512
500        } else if hash_func.eq_ignore_ascii_case("md-5") {
501            HashFunc::Md5
502        } else if hash_func.eq_ignore_ascii_case("md-2") {
503            HashFunc::Md2
504        } else {
505            HashFunc::Other(hash_func.to_string())
506        }
507    }
508
509    pub fn as_str(&self) -> &str {
510        match self {
511            HashFunc::Sha1 => "sha-1",
512            HashFunc::Sha224 => "sha-224",
513            HashFunc::Sha256 => "sha-256",
514            HashFunc::Sha384 => "sha-384",
515            HashFunc::Sha512 => "sha-512",
516            HashFunc::Md5 => "md-5",
517            HashFunc::Md2 => "md-2",
518            HashFunc::Other(s) => s.as_str(),
519        }
520    }
521}
522
523/// Semantics for the `group` and `ssrc-group` attributes
524///
525/// See [RFC 5888 Section 5](https://datatracker.ietf.org/doc/html/rfc5888#section-5)
526/// and [RFC 5576 Section 12.3](https://datatracker.ietf.org/doc/html/rfc5576#section-12.3)
527#[derive(Debug, Clone, PartialEq, Eq)]
528#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
529pub enum GroupSemantics {
530    /// Lip Synchronization
531    ///
532    /// See [RFC 5888 Section 7](https://datatracker.ietf.org/doc/html/rfc5888#section-7)
533    LS,
534    /// Flow Identification
535    ///
536    /// See [RFC 5888 Section 8](https://datatracker.ietf.org/doc/html/rfc5888#section-8)
537    FID,
538    /// Single Reservation Flow
539    ///
540    /// See [RFC 3524 Section 2](https://datatracker.ietf.org/doc/html/rfc3524#section-2)
541    SRF,
542    /// Alternative Network Address Types
543    ///
544    /// See [RFC 4091 Section 3](https://datatracker.ietf.org/doc/html/rfc4091#section-3)
545    ANAT,
546    /// Forward Error Correction
547    ///
548    /// See [RFC 4756 Section 4](https://datatracker.ietf.org/doc/html/rfc4756#section-4)
549    FEC,
550    /// Decoding Dependency
551    ///
552    /// See [RFC 5583 Section 5.2.1](https://datatracker.ietf.org/doc/html/rfc5583#section-5.2.1)
553    DDP,
554    /// Media Multiplexing
555    ///
556    /// See [RFC 8843 Section 5](https://datatracker.ietf.org/doc/html/rfc8843#section-5)
557    Bundle,
558    /// Other Semantics
559    Other(String),
560}
561
562impl GroupSemantics {
563    pub fn new(semantics: impl AsRef<str>) -> Self {
564        let semantics = semantics.as_ref();
565
566        if "LS".eq_ignore_ascii_case(semantics) {
567            GroupSemantics::LS
568        } else if "FID".eq_ignore_ascii_case(semantics) {
569            GroupSemantics::FID
570        } else if "SRF".eq_ignore_ascii_case(semantics) {
571            GroupSemantics::SRF
572        } else if "ANAT".eq_ignore_ascii_case(semantics) {
573            GroupSemantics::ANAT
574        } else if "FEC".eq_ignore_ascii_case(semantics) {
575            GroupSemantics::FEC
576        } else if "DDP".eq_ignore_ascii_case(semantics) {
577            GroupSemantics::DDP
578        } else if "BUNDLE".eq_ignore_ascii_case(semantics) {
579            GroupSemantics::Bundle
580        } else {
581            GroupSemantics::Other(semantics.to_string())
582        }
583    }
584
585    pub fn as_str(&self) -> &str {
586        match self {
587            GroupSemantics::LS => "LS",
588            GroupSemantics::FID => "FID",
589            GroupSemantics::SRF => "SRF",
590            GroupSemantics::ANAT => "ANAT",
591            GroupSemantics::DDP => "DDP",
592            GroupSemantics::FEC => "FEC",
593            GroupSemantics::Bundle => "BUNDLE",
594            GroupSemantics::Other(s) => s.as_str(),
595        }
596    }
597}
598
599/// Encryption and authentication algorithms for Crypto attribute
600///
601/// See [RFC 4568 Section 10.3.2](https://datatracker.ietf.org/doc/html/rfc4568#section-10.3.2)
602///
603/// Note: `F8_128_HMAC_SHA1_32` appears in the [RFC 4568 Section 9.2](https://datatracker.ietf.org/doc/html/rfc4568#section-9.2)
604/// grammar but was never registered in the IANA registry, so it is not defined as a variant here.
605/// It will be parsed as `Other`.
606#[derive(Debug, PartialEq, Clone, Eq)]
607#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
608pub enum CryptoSuite {
609    /// AES_CM_128_HMAC_SHA1_80
610    AesCm128HmacSha1_80,
611    /// AES_CM_128_HMAC_SHA1_32
612    AesCm128HmacSha1_32,
613    /// F8_128_HMAC_SHA1_80
614    F8_128HmacSha1_80,
615    /// Other Crypto Suite
616    Other(String),
617}
618
619impl CryptoSuite {
620    pub fn new(crypto_suite: impl AsRef<str>) -> Self {
621        let crypto_suite = crypto_suite.as_ref();
622
623        if "AES_CM_128_HMAC_SHA1_32".eq_ignore_ascii_case(crypto_suite) {
624            CryptoSuite::AesCm128HmacSha1_32
625        } else if "F8_128_HMAC_SHA1_80".eq_ignore_ascii_case(crypto_suite) {
626            CryptoSuite::F8_128HmacSha1_80
627        } else if "AES_CM_128_HMAC_SHA1_80".eq_ignore_ascii_case(crypto_suite) {
628            CryptoSuite::AesCm128HmacSha1_80
629        } else {
630            CryptoSuite::Other(crypto_suite.to_string())
631        }
632    }
633
634    pub fn as_str(&self) -> &str {
635        match self {
636            CryptoSuite::AesCm128HmacSha1_80 => "AES_CM_128_HMAC_SHA1_80",
637            CryptoSuite::AesCm128HmacSha1_32 => "AES_CM_128_HMAC_SHA1_32",
638            CryptoSuite::F8_128HmacSha1_80 => "F8_128_HMAC_SHA1_80",
639            CryptoSuite::Other(s) => s.as_str(),
640        }
641    }
642}
643
644/// Signals whether FEC is applied before or after SRTP processing
645///
646/// See [RFC 4568 Section 6.3.5](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.5)
647#[derive(Debug, PartialEq, Clone, Eq, Copy)]
648#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
649pub enum FecOrder {
650    /// FEC is applied before SRTP processing
651    FecSrtp,
652    /// FEC is applied after SRTP processing
653    SrtpFec,
654}
655
656/// SRTP session parameters
657///
658/// See [RFC 4568 Section 6.3](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3)
659#[derive(Debug, PartialEq, Clone, Eq)]
660#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
661pub enum SrtpSessionParam {
662    /// Key Derivation Rate
663    ///
664    /// See [RFC 4568 Section 6.3.1](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.1)
665    Kdr(u8),
666    /// Signals that the SRTP packets are without encryption
667    ///
668    /// See [RFC 4568 Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.2)
669    UnencryptedSrtp,
670    /// Signals that the SRTCP packets are without encryption
671    ///
672    /// See [RFC 4568 Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.2)
673    UnencryptedSrtcp,
674    /// Signals that the SRTP packets are not authenticated. (Not recommended)
675    ///
676    /// See [RFC 4568 Section 6.3.3](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.3)
677    UnauthenticatedSrtp,
678    /// Signals whether FEC is applied before or after SRTP processing
679    ///
680    /// See [RFC 4568 Section 6.3.4](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.4)
681    FecOrder(FecOrder),
682    /// Signals the use of separate master key(s) for forward error correction
683    ///
684    /// See [RFC 4568 Section 6.3.5](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.5)
685    FecKey(Vec<SrtpKeyParam>),
686    /// Window Size Hint - provides a hint for how big the SRTP Window size should be
687    ///
688    /// See [RFC 4568 Section 6.3.6](https://datatracker.ietf.org/doc/html/rfc4568#section-6.3.6)
689    Wsh(u8),
690    /// Unknown parameter
691    Extension(String),
692}
693
694/// Type of the Candidate
695///
696/// See [RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)
697#[derive(Debug, PartialEq, Clone, Eq)]
698#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
699pub enum CandidateType {
700    /// Host
701    Host,
702    /// Server-reflexive
703    Srflx,
704    /// Peer-reflexive
705    Prflx,
706    /// Relay
707    Relay,
708    /// Unknown type
709    Other(String),
710}
711
712impl CandidateType {
713    pub fn new(cand_type: impl AsRef<str>) -> Self {
714        let cand_type = cand_type.as_ref();
715
716        if "host".eq_ignore_ascii_case(cand_type) {
717            CandidateType::Host
718        } else if "srflx".eq_ignore_ascii_case(cand_type) {
719            CandidateType::Srflx
720        } else if "prflx".eq_ignore_ascii_case(cand_type) {
721            CandidateType::Prflx
722        } else if "relay".eq_ignore_ascii_case(cand_type) {
723            CandidateType::Relay
724        } else {
725            CandidateType::Other(cand_type.to_string())
726        }
727    }
728
729    pub fn as_str(&self) -> &str {
730        match self {
731            CandidateType::Host => "host",
732            CandidateType::Srflx => "srflx",
733            CandidateType::Prflx => "prflx",
734            CandidateType::Relay => "relay",
735            CandidateType::Other(o) => o.as_str(),
736        }
737    }
738}
739
740/// RTCP Positive feedback values
741///
742/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
743#[derive(Debug, PartialEq, Clone, Eq)]
744#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
745pub enum RtcpFbAck {
746    /// Reference Picture Selection Indication
747    Rpsi,
748    /// Application layer feedback
749    App(Option<String>),
750    /// Congestion Control Feedback
751    ///
752    /// See [RFC 8888 Section 6](https://datatracker.ietf.org/doc/html/rfc8888#section-6)
753    Ccfb,
754    /// Other Ack types
755    Other(String),
756}
757
758/// RTCP Negative feedback values
759///
760/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
761#[derive(Debug, PartialEq, Clone, Eq)]
762#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
763pub enum RtcpFbNack {
764    /// Picture Loss Indication
765    Pli,
766    /// Slice Loss Indication
767    Sli,
768    /// Reference Picture Selection Indication
769    Rpsi,
770    /// Application layer feedback
771    App(Option<String>),
772    /// Explicit Congestion Notification
773    ///
774    /// See [RFC 6679 Section 6.2](https://datatracker.ietf.org/doc/html/rfc6679#section-6.2)
775    Ecn,
776    /// Other Nack types
777    Other(String),
778}
779
780/// Codec Control using RTCP feedback messages
781///
782/// See [RFC 5104 Section 7.1](https://datatracker.ietf.org/doc/html/rfc5104#section-7.1)
783#[derive(Debug, PartialEq, Clone, Eq)]
784#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
785pub enum RtcpFbCcm {
786    /// Full Intra Request
787    Fir,
788    /// Temporary Maximum Media Stream Bit Rate
789    Tmmbr(Option<String>),
790    /// Temporal-Spatial Trade-off
791    Tstr,
792    /// Video Back Channel Messages
793    Vbcm(Vec<u8>),
794    /// Other messages (for future commands/Indications)
795    Other(String),
796}
797
798/// Types of RTCP feedback values
799#[derive(Debug, PartialEq, Clone, Eq)]
800#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
801pub enum RtcpFbVal {
802    /// Positive Acknowledgement
803    Ack(Option<RtcpFbAck>),
804    /// Negative Acknowledgement
805    Nack(Option<RtcpFbNack>),
806    /// Minimum interval between two Regular RTCP packets in milliseconds
807    TrrInt(u64),
808    /// Codec Control messages
809    Ccm(RtcpFbCcm),
810    /// Transport-wide Congestion Control
811    TransportCc,
812    /// Others Rtcp Fb types
813    Other(String),
814}
815
816impl RtcpFbVal {
817    pub fn is_ack(&self) -> bool {
818        matches!(self, RtcpFbVal::Ack(None))
819    }
820
821    pub fn is_ack_rpsi(&self) -> bool {
822        matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::Rpsi)))
823    }
824
825    pub fn is_ack_app(&self) -> bool {
826        matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::App(_))))
827    }
828
829    pub fn is_ack_ccfb(&self) -> bool {
830        matches!(self, RtcpFbVal::Ack(Some(RtcpFbAck::Ccfb)))
831    }
832
833    pub fn is_nack(&self) -> bool {
834        matches!(self, RtcpFbVal::Nack(None))
835    }
836
837    pub fn is_nack_pli(&self) -> bool {
838        matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Pli)))
839    }
840
841    pub fn is_nack_sli(&self) -> bool {
842        matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Sli)))
843    }
844
845    pub fn is_nack_rpsi(&self) -> bool {
846        matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)))
847    }
848
849    pub fn is_nack_app(&self) -> bool {
850        matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::App(_))))
851    }
852
853    pub fn is_nack_ecn(&self) -> bool {
854        matches!(self, RtcpFbVal::Nack(Some(RtcpFbNack::Ecn)))
855    }
856
857    pub fn is_trr_int(&self) -> bool {
858        matches!(self, RtcpFbVal::TrrInt(_))
859    }
860
861    pub fn is_ccm_fir(&self) -> bool {
862        matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Fir))
863    }
864
865    pub fn is_ccm_tmmbr(&self) -> bool {
866        matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(_)))
867    }
868
869    pub fn is_ccm_tstr(&self) -> bool {
870        matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Tstr))
871    }
872
873    pub fn is_ccm_vbcm(&self) -> bool {
874        matches!(self, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(_)))
875    }
876
877    pub fn is_transport_cc(&self) -> bool {
878        matches!(self, RtcpFbVal::TransportCc)
879    }
880}
881
882impl From<RtcpFbAck> for RtcpFbVal {
883    fn from(value: RtcpFbAck) -> Self {
884        RtcpFbVal::Ack(Some(value))
885    }
886}
887
888impl From<RtcpFbNack> for RtcpFbVal {
889    fn from(value: RtcpFbNack) -> Self {
890        RtcpFbVal::Nack(Some(value))
891    }
892}
893
894impl From<RtcpFbCcm> for RtcpFbVal {
895    fn from(value: RtcpFbCcm) -> Self {
896        RtcpFbVal::Ccm(value)
897    }
898}
899
900impl Display for RtcpFbVal {
901    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902        match self {
903            RtcpFbVal::Ack(ack) => {
904                write!(f, "ack")?;
905                if let Some(ack) = ack {
906                    match ack {
907                        RtcpFbAck::Rpsi => write!(f, " rpsi")?,
908                        RtcpFbAck::Ccfb => write!(f, " ccfb")?,
909                        RtcpFbAck::App(app) => {
910                            write!(f, " app")?;
911                            if let Some(app_param) = app {
912                                f.write_char(' ')?;
913                                f.write_str(app_param)?;
914                            }
915                        }
916                        RtcpFbAck::Other(other) => {
917                            f.write_char(' ')?;
918                            f.write_str(other)?;
919                        }
920                    }
921                }
922                Ok(())
923            }
924            RtcpFbVal::Nack(nack) => {
925                write!(f, "nack")?;
926                if let Some(nack) = nack {
927                    match nack {
928                        RtcpFbNack::Pli => write!(f, " pli")?,
929                        RtcpFbNack::Sli => write!(f, " sli")?,
930                        RtcpFbNack::Rpsi => write!(f, " rpsi")?,
931                        RtcpFbNack::Ecn => write!(f, " ecn")?,
932                        RtcpFbNack::App(app) => {
933                            write!(f, " app")?;
934                            if let Some(app_param) = app {
935                                f.write_char(' ')?;
936                                f.write_str(app_param)?;
937                            }
938                        }
939                        RtcpFbNack::Other(other) => {
940                            f.write_char(' ')?;
941                            f.write_str(other)?;
942                        }
943                    }
944                }
945                Ok(())
946            }
947            RtcpFbVal::TrrInt(trr_int) => write!(f, "trr-int {trr_int}"),
948            RtcpFbVal::Ccm(ccm) => {
949                write!(f, "ccm")?;
950                match ccm {
951                    RtcpFbCcm::Fir => write!(f, " fir")?,
952                    RtcpFbCcm::Tstr => write!(f, " tstr")?,
953                    RtcpFbCcm::Tmmbr(smaxpr) => {
954                        write!(f, " tmmbr")?;
955                        if let Some(smaxpr) = smaxpr {
956                            f.write_char(' ')?;
957                            f.write_str(smaxpr)?;
958                        }
959                    }
960                    RtcpFbCcm::Vbcm(vbcm) => {
961                        write!(f, " vbcm")?;
962                        for v in vbcm {
963                            f.write_char(' ')?;
964                            write!(f, "{v}")?;
965                        }
966                    }
967                    RtcpFbCcm::Other(other) => {
968                        f.write_char(' ')?;
969                        f.write_str(other)?;
970                    }
971                }
972                Ok(())
973            }
974            RtcpFbVal::TransportCc => f.write_str("transport-cc"),
975            RtcpFbVal::Other(other) => f.write_str(other),
976        }
977    }
978}
979
980/// Source attribute types
981///
982/// See [RFC 5576 Section 6](https://datatracker.ietf.org/doc/html/rfc5576#section-6)
983#[derive(Debug, Clone, PartialEq, Eq)]
984#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
985pub enum SsrcAttribute {
986    /// See [RFC 5576 Section 6.1](https://datatracker.ietf.org/doc/html/rfc5576#section-6.1)
987    Cname,
988    /// See [RFC 5576 Section 6.2](https://datatracker.ietf.org/doc/html/rfc5576#section-6.2)
989    PreviousSsrc,
990    /// See [RFC 5576 Section 6.3](https://datatracker.ietf.org/doc/html/rfc5576#section-6.3)
991    Fmtp,
992    /// Rtcp attribute
993    Rtcp,
994    /// Reference Clock attribute
995    ReferenceClock,
996    /// Media Clock Source attribute
997    MediaClockSource,
998    /// See [RFC 5576 Section 6.4](https://datatracker.ietf.org/doc/html/rfc5576#section-6.4)
999    Other(String),
1000}
1001
1002impl SsrcAttribute {
1003    pub fn new(attribute: impl AsRef<str>) -> Self {
1004        use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
1005
1006        match attribute.as_ref() {
1007            "cname" => SsrcAttribute::Cname,
1008            "previous-ssrc" => SsrcAttribute::PreviousSsrc,
1009            <Fmtp as TypedAttribute>::NAME => SsrcAttribute::Fmtp,
1010            <Rtcp as TypedAttribute>::NAME => SsrcAttribute::Rtcp,
1011            <ReferenceClock as TypedAttribute>::NAME => SsrcAttribute::ReferenceClock,
1012            <MediaClockSource as TypedAttribute>::NAME => SsrcAttribute::MediaClockSource,
1013            other => SsrcAttribute::Other(other.to_string()),
1014        }
1015    }
1016
1017    pub fn as_str(&self) -> &str {
1018        use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
1019
1020        match self {
1021            SsrcAttribute::Cname => "cname",
1022            SsrcAttribute::PreviousSsrc => "previous-ssrc",
1023            SsrcAttribute::Fmtp => <Fmtp as TypedAttribute>::NAME,
1024            SsrcAttribute::Rtcp => <Rtcp as TypedAttribute>::NAME,
1025            SsrcAttribute::ReferenceClock => <ReferenceClock as TypedAttribute>::NAME,
1026            SsrcAttribute::MediaClockSource => <MediaClockSource as TypedAttribute>::NAME,
1027            SsrcAttribute::Other(other) => other.as_str(),
1028        }
1029    }
1030}
1031
1032impl<T: TypedAttribute> From<T> for SsrcAttribute {
1033    fn from(_attr: T) -> Self {
1034        SsrcAttribute::new(T::NAME)
1035    }
1036}
1037
1038/// Payload format for which feedback messages may be used
1039#[derive(Debug, PartialEq, Clone, Eq)]
1040#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1041pub enum RtcpFbPt {
1042    /// Fixed payload format
1043    Fmt(u8),
1044    /// Applies to all formats
1045    Wildcard,
1046}
1047
1048impl From<u8> for RtcpFbPt {
1049    fn from(pt: u8) -> Self {
1050        RtcpFbPt::Fmt(pt)
1051    }
1052}
1053
1054/// Candidate connection address type
1055///
1056/// Can be IPv4, IPv6 or a FQDN
1057#[derive(Debug, PartialEq, Clone, Eq)]
1058#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1059pub enum CandidateAddress {
1060    IpAddr(std::net::IpAddr),
1061    FQDN(String),
1062}
1063
1064#[cfg(test)]
1065mod tests {
1066    use super::*;
1067
1068    #[test]
1069    fn transport_proto_classification() {
1070        let cases = [
1071            // (proto, is_rtp, is_sctp, is_secure, is_avpf)
1072            (TransportProto::Udp, false, false, false, false),
1073            (TransportProto::RtpAvp, true, false, false, false),
1074            (TransportProto::RtpAvpf, true, false, false, true),
1075            (TransportProto::RtpSavp, true, false, true, false),
1076            (TransportProto::RtpSavpf, true, false, true, true),
1077            (TransportProto::TcpDtlsRtpSavp, true, false, true, false),
1078            (TransportProto::TcpDtlsRtpSavpf, true, false, true, true),
1079            (TransportProto::UdpTlsRtpSavp, true, false, true, false),
1080            (TransportProto::UdpTlsRtpSavpf, true, false, true, true),
1081            (TransportProto::UdpDtlsSctp, false, true, false, false),
1082            (TransportProto::TcpDtlsSctp, false, true, false, false),
1083            (TransportProto::DtlsSctp, false, true, false, false),
1084            (
1085                TransportProto::Other("FOO".into()),
1086                false,
1087                false,
1088                false,
1089                false,
1090            ),
1091            (
1092                TransportProto::Other("RTP/AV".into()),
1093                true,
1094                false,
1095                false,
1096                false,
1097            ),
1098            (
1099                TransportProto::Other("RTP/AVP/TCP".into()),
1100                true,
1101                false,
1102                false,
1103                false,
1104            ),
1105            (
1106                TransportProto::Other("UDP/TLS/RTP/SAVPF".into()),
1107                true,
1108                false,
1109                true,
1110                true,
1111            ),
1112            (
1113                TransportProto::Other("AVPF".into()),
1114                false,
1115                false,
1116                false,
1117                false,
1118            ),
1119        ];
1120
1121        for (proto, rtp, sctp, secure, avpf) in cases {
1122            assert_eq!(proto.is_rtp(), rtp, "{:?} is_rtp", proto);
1123            assert_eq!(proto.is_sctp(), sctp, "{:?} is_sctp", proto);
1124            assert_eq!(proto.is_secure(), secure, "{:?} is_secure", proto);
1125            assert_eq!(proto.is_avpf(), avpf, "{:?} is_avpf", proto);
1126        }
1127    }
1128
1129    #[test]
1130    fn group_semantics_case() {
1131        // Registered semantics are matched case-insensitively, but an unknown
1132        // one has no registered spelling to be normalized to.
1133        let cases = [
1134            ("ls", GroupSemantics::LS),
1135            ("LS", GroupSemantics::LS),
1136            ("fid", GroupSemantics::FID),
1137            ("srf", GroupSemantics::SRF),
1138            ("anat", GroupSemantics::ANAT),
1139            ("fec", GroupSemantics::FEC),
1140            ("ddp", GroupSemantics::DDP),
1141            ("bundle", GroupSemantics::Bundle),
1142            ("Bundle", GroupSemantics::Bundle),
1143            ("BUNDLE", GroupSemantics::Bundle),
1144            ("foo", GroupSemantics::Other("foo".to_string())),
1145        ];
1146
1147        for (value, semantics) in cases {
1148            assert_eq!(GroupSemantics::new(value), semantics, "{value}");
1149        }
1150
1151        // The registered spelling is what is written back out.
1152        let cases = [
1153            (GroupSemantics::LS, "LS"),
1154            (GroupSemantics::FID, "FID"),
1155            (GroupSemantics::SRF, "SRF"),
1156            (GroupSemantics::ANAT, "ANAT"),
1157            (GroupSemantics::FEC, "FEC"),
1158            (GroupSemantics::DDP, "DDP"),
1159            (GroupSemantics::Bundle, "BUNDLE"),
1160            (GroupSemantics::Other("foo".to_string()), "foo"),
1161        ];
1162
1163        for (semantics, value) in cases {
1164            assert_eq!(semantics.as_str(), value, "{semantics:?}");
1165        }
1166    }
1167}