Skip to main content

sdp_types/
attributes.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 Session description Attributes defined as Structs/Enums
6
7use std::{
8    convert::TryFrom,
9    fmt::{Display, Write},
10    net::IpAddr,
11    str::FromStr,
12};
13
14use crate::{
15    builders, enums::*, parse_connection_address, parse_connection_ip_address, parser::is_token,
16    Attribute, ConnectionAddress, ConnectionAddressError,
17};
18
19/// Trait for Typed Attribute structs
20pub trait TypedAttribute: Display + FromStr<Err = AttributeError> {
21    const NAME: &'static str;
22}
23
24impl<T: TypedAttribute> From<T> for Attribute {
25    fn from(attr: T) -> Attribute {
26        let value = attr.to_string();
27        Attribute {
28            attribute: T::NAME.to_string(),
29            // Property attributes format as the empty string and carry no value.
30            value: (!value.is_empty()).then_some(value),
31        }
32    }
33}
34
35/// Attribute error with specific details
36#[derive(Debug, PartialEq, Eq, thiserror::Error)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub enum AttributeError {
39    /// If an Attribute is not found in a media or the session
40    #[error("Attribute {} not found", .0)]
41    NotFound(String),
42    /// If a parameter is missing in an attribute
43    #[error("Param {} not found in {}", .param, .attr)]
44    ParamNotFound { param: String, attr: String },
45    /// If a parameter value is not valid type or not in range
46    #[error("Invalid value {} for Param {} in {}", .val ,.param, .attr)]
47    InvalidParamValue {
48        param: String,
49        val: String,
50        attr: String,
51    },
52    /// If an attribute is not in expected format
53    #[error("Unsupported attribute format: {} for {}", .val, .attr)]
54    UnsupportedFormat { val: String, attr: String },
55    /// If there are more than expected items trailing in the attribute parameters
56    #[error("Unexpected trailing item {} for in {}", .val, .attr)]
57    UnexpectedTrailingItem { val: String, attr: String },
58    /// Unspecified error
59    #[error("{}: {}", .attr, .error)]
60    Other { error: String, attr: String },
61}
62
63impl AttributeError {
64    pub fn is_attribute_not_found(&self) -> bool {
65        matches!(self, AttributeError::NotFound(_))
66    }
67
68    /// Preprends the provided `param_context` to the parameter field of this AttributeError if applicable
69    fn add_param_context(&mut self, param_context: &str) {
70        use AttributeError::*;
71        match self {
72            NotFound(_)
73            | Other { .. }
74            | UnsupportedFormat { .. }
75            | UnexpectedTrailingItem { .. } => (),
76            ParamNotFound { param, .. } | InvalidParamValue { param, .. } => {
77                param.push(')');
78                param.insert_str(0, " (");
79                param.insert_str(0, param_context);
80            }
81        }
82    }
83
84    /// Sets the attribute field of this AttributeError to new_attr
85    pub(crate) fn set_attr(&mut self, new_attr: impl ToString) {
86        use AttributeError::*;
87        match self {
88            NotFound(attr)
89            | ParamNotFound { attr, .. }
90            | InvalidParamValue { attr, .. }
91            | UnsupportedFormat { attr, .. }
92            | UnexpectedTrailingItem { attr, .. }
93            | Other { attr, .. } => *attr = new_attr.to_string(),
94        }
95    }
96}
97
98pub(crate) trait ErrorContext {
99    fn with_param_context(self, param_context: &str) -> Self;
100    fn with_attr(self, new_attr: impl ToString) -> Self;
101}
102
103impl<T> ErrorContext for Result<T, AttributeError> {
104    /// Returns this error with the provided `param_context` prepended to the parameter field if applicable
105    fn with_param_context(self, param_context: &str) -> Self {
106        self.map_err(|mut err| {
107            err.add_param_context(param_context);
108            err
109        })
110    }
111
112    /// Returns this error with the attribute field set to new_attr
113    fn with_attr(self, new_attr: impl ToString) -> Self {
114        self.map_err(|mut err| {
115            err.set_attr(new_attr);
116            err
117        })
118    }
119}
120
121/// RtpMap Attribute
122///
123/// See [RFC 8866 Section 6.6](https://datatracker.ietf.org/doc/html/rfc8866#section-6.6) for more details
124#[derive(Debug, Clone, PartialEq, Eq)]
125#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126pub struct RtpMap {
127    /// Payload type, a numerical value between 0 and 127
128    pub payload_type: u8,
129    /// Name of the encoding
130    // TODO: is it useful to have an enum for all known encoding?
131    pub encoding_name: String,
132    /// Clock rate
133    pub clock_rate: u32,
134    /// Encoding parameters.
135    ///
136    /// Currently used only for audio channel count
137    pub encoding_params: Option<String>,
138}
139
140impl RtpMap {
141    pub fn new(payload_type: u8, encoding_name: impl ToString, clock_rate: u32) -> Self {
142        RtpMap {
143            payload_type,
144            encoding_name: encoding_name.to_string(),
145            clock_rate,
146            encoding_params: None,
147        }
148    }
149
150    pub fn builder(
151        payload_type: u8,
152        encoding_name: impl ToString,
153        clock_rate: u32,
154    ) -> builders::RtpMap {
155        builders::RtpMap::new(payload_type, encoding_name, clock_rate)
156    }
157
158    pub fn with_encoding_params(
159        payload_type: u8,
160        encoding_name: impl ToString,
161        clock_rate: u32,
162        encoding_params: impl ToString,
163    ) -> Self {
164        RtpMap {
165            payload_type,
166            encoding_name: encoding_name.to_string(),
167            clock_rate,
168            encoding_params: Some(encoding_params.to_string()),
169        }
170    }
171
172    pub fn set_encoding_params(&mut self, encoding_params: impl ToString) {
173        self.encoding_params = Some(encoding_params.to_string());
174    }
175}
176
177impl FromStr for RtpMap {
178    type Err = AttributeError;
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        let Some((pt, rest)) = s.split_once(' ') else {
182            return Err(AttributeError::UnsupportedFormat {
183                val: s.to_string(),
184                attr: <Self as TypedAttribute>::NAME.to_string(),
185            });
186        };
187
188        let Ok(pt) = pt.parse::<u8>() else {
189            return Err(AttributeError::InvalidParamValue {
190                param: "Payload type".to_string(),
191                val: pt.to_string(),
192                attr: <Self as TypedAttribute>::NAME.to_string(),
193            });
194        };
195
196        if pt > 127 {
197            return Err(AttributeError::InvalidParamValue {
198                param: "Payload type".to_string(),
199                val: format!("{pt}(expected 0-127)"),
200                attr: <Self as TypedAttribute>::NAME.to_string(),
201            });
202        }
203
204        let mut i = rest.splitn(3, '/');
205        let Some(encoding) = i.next() else {
206            return Err(AttributeError::ParamNotFound {
207                param: "Encoding name".to_string(),
208                attr: <Self as TypedAttribute>::NAME.to_string(),
209            });
210        };
211
212        let Some(clock_rate) = i.next() else {
213            return Err(AttributeError::ParamNotFound {
214                param: "Clock rate".to_string(),
215                attr: <Self as TypedAttribute>::NAME.to_string(),
216            });
217        };
218
219        let Ok(clock_rate) = clock_rate.parse::<u32>() else {
220            return Err(AttributeError::InvalidParamValue {
221                param: "Clock rate".to_string(),
222                val: clock_rate.to_string(),
223                attr: <Self as TypedAttribute>::NAME.to_string(),
224            });
225        };
226
227        let params = i.next().map(String::from);
228
229        Ok(Self {
230            payload_type: pt,
231            encoding_name: encoding.to_owned(),
232            clock_rate,
233            encoding_params: params,
234        })
235    }
236}
237
238impl Display for RtpMap {
239    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240        write!(
241            f,
242            "{} {}/{}",
243            self.payload_type, self.encoding_name, self.clock_rate
244        )?;
245        if let Some(params) = &self.encoding_params {
246            f.write_char('/')?;
247            f.write_str(params)?;
248        }
249        Ok(())
250    }
251}
252
253impl TypedAttribute for RtpMap {
254    const NAME: &'static str = "rtpmap";
255}
256
257/// Format specific parameters
258#[derive(Debug, Clone, PartialEq, Eq)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260pub struct FmtpParam {
261    pub param: String,
262    pub val: Option<String>,
263}
264
265impl FmtpParam {
266    pub fn new(param: impl ToString) -> Self {
267        FmtpParam {
268            param: param.to_string(),
269            val: None,
270        }
271    }
272
273    pub fn builder(param: impl ToString) -> builders::FmtpParam {
274        builders::FmtpParam::new(param)
275    }
276
277    pub fn with_value(param: impl ToString, value: impl ToString) -> Self {
278        FmtpParam {
279            param: param.to_string(),
280            val: Some(value.to_string()),
281        }
282    }
283
284    pub fn set_value(&mut self, value: impl ToString) {
285        self.val = Some(value.to_string());
286    }
287}
288
289/// Format Parameters
290///
291/// See [RFC 8866 Section 6.15](https://datatracker.ietf.org/doc/html/rfc8866#section-6.15) for more details
292#[derive(Debug, Clone, PartialEq, Eq)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
294pub struct Fmtp {
295    /// Payload format
296    pub fmt: u8,
297    /// Format specific parameters
298    // Multiple params are expected to be semicolon separated
299    // Each param can be a 'key=value' pair or just single parameter
300    pub format_specific_params: Vec<FmtpParam>,
301}
302
303impl Fmtp {
304    pub fn new(fmt: u8) -> Self {
305        Fmtp {
306            fmt,
307            format_specific_params: vec![],
308        }
309    }
310
311    pub fn builder(fmt: u8) -> builders::Fmtp {
312        builders::Fmtp::new(fmt)
313    }
314
315    pub fn add_format_specific_param(&mut self, format_specific_param: FmtpParam) {
316        self.format_specific_params.push(format_specific_param)
317    }
318
319    pub fn add_format_specific_params(
320        &mut self,
321        format_specific_params: impl IntoIterator<Item = FmtpParam>,
322    ) {
323        self.format_specific_params.extend(format_specific_params)
324    }
325}
326
327impl FromStr for Fmtp {
328    type Err = AttributeError;
329
330    fn from_str(s: &str) -> Result<Self, Self::Err> {
331        let Some((fmt, rest)) = s.split_once(' ') else {
332            return Err(AttributeError::UnsupportedFormat {
333                val: s.to_string(),
334                attr: <Self as TypedAttribute>::NAME.to_string(),
335            });
336        };
337
338        let Ok(fmt) = fmt.parse::<u8>() else {
339            return Err(AttributeError::InvalidParamValue {
340                param: "fmtp".to_string(),
341                val: fmt.to_string(),
342                attr: <Self as TypedAttribute>::NAME.to_string(),
343            });
344        };
345
346        let mut params: Vec<FmtpParam> = Vec::new();
347        for param in rest.split(';') {
348            if let Some((key, value)) = param.split_once('=') {
349                params.push(FmtpParam {
350                    param: key.to_string(),
351                    val: Some(value.to_string()),
352                });
353            } else {
354                params.push(FmtpParam {
355                    param: param.to_string(),
356                    val: None,
357                });
358            }
359        }
360
361        Ok(Self {
362            fmt,
363            format_specific_params: params,
364        })
365    }
366}
367
368impl Display for Fmtp {
369    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370        write!(f, "{} ", self.fmt)?;
371        let mut iter = self.format_specific_params.iter().peekable();
372        while let Some(p) = iter.next() {
373            write!(f, "{}", p.param)?;
374            if let Some(val) = &p.val {
375                f.write_char('=')?;
376                f.write_str(val.as_str())?;
377            }
378            if iter.peek().is_some() {
379                f.write_char(';')?;
380            }
381        }
382        Ok(())
383    }
384}
385
386impl TypedAttribute for Fmtp {
387    const NAME: &'static str = "fmtp";
388}
389
390/// RTCP port number and address
391///
392/// To be used if not algorithmically derived
393/// from the RTP port described in the media line
394///
395/// The address part is optional: when absent, RTCP is sent to the
396/// connection address given for the media.
397///
398/// See [RFC 3605 Section 2.1](https://datatracker.ietf.org/doc/html/rfc3605#section-2.1)
399#[derive(Debug, Clone, PartialEq, Eq)]
400#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
401pub struct Rtcp {
402    /// Port used for the RTCP stream
403    pub port: u16,
404    /// Network Type
405    ///
406    /// `None` if the address part is absent
407    pub nettype: Option<NetType>,
408    /// Address type
409    ///
410    /// `None` if the address part is absent
411    pub addrtype: Option<AddrType>,
412    /// Connection address: can be IP Address, unicast, multicast, ...
413    /// Conformity may be checked against the `addrtype`.
414    ///
415    /// `None` if the address part is absent
416    pub connection_address: Option<String>,
417}
418
419impl Rtcp {
420    /// Construct an [`Rtcp`] with only a port number, i.e. without an address
421    /// part.
422    ///
423    /// RTCP is then sent to the connection address given for the media.
424    pub fn with_port(port: u16) -> Self {
425        Rtcp {
426            port,
427            nettype: None,
428            addrtype: None,
429            connection_address: None,
430        }
431    }
432
433    /// Construct an [`Rtcp`] with the specified IP `connection_address`
434    pub fn with_ip_addr(port: u16, connection_address: impl Into<IpAddr>) -> Self {
435        let connection_address = connection_address.into();
436        Rtcp {
437            port,
438            nettype: Some(NetType::In),
439            addrtype: Some(connection_address.into()),
440            connection_address: Some(connection_address.to_string()),
441        }
442    }
443
444    /// Construct an [`Rtcp`]
445    ///
446    /// See also [`Rtcp::with_ip_addr`] and [`Rtcp::with_port`]
447    pub fn new(
448        port: u16,
449        nettype: NetType,
450        addrtype: AddrType,
451        connection_address: impl ToString,
452    ) -> Self {
453        Rtcp {
454            port,
455            nettype: Some(nettype),
456            addrtype: Some(addrtype),
457            connection_address: Some(connection_address.to_string()),
458        }
459    }
460
461    /// Tries to parse the `connection_address` `String` of `self` as `IpAddr`
462    ///
463    /// An optional time-to-live suffix in `address/ttl` notation
464    /// ([RFC 8866 Section 5.7](https://tools.ietf.org/html/rfc8866#section-5.7))
465    /// is ignored, see [`Rtcp::try_address`].
466    ///
467    /// Returns `None` if the address part is absent, the `Ok` with the parsed
468    /// `IpAddr` or `Err` with the string address if parsing failed.
469    #[deprecated(since = "0.3.1", note = "use Rtcp::try_address instead")]
470    pub fn try_parse_connection_ip_address(&self) -> Option<Result<IpAddr, &str>> {
471        self.connection_address
472            .as_deref()
473            .map(parse_connection_ip_address)
474    }
475
476    /// The effective `addrtype` of the `connection_address`
477    ///
478    /// The `addrtype` field, or the address family inferred from the parsed
479    /// `IpAddr` if the field is `None`. `None` if the address part is absent
480    /// or its address family cannot be determined.
481    fn effective_addrtype(&self) -> Option<AddrType> {
482        self.addrtype.clone().or_else(|| {
483            let address = parse_connection_ip_address(self.connection_address.as_deref()?).ok()?;
484            Some(address.into())
485        })
486    }
487
488    /// Parses the `connection_address` into its components
489    ///
490    /// The `connection_address` of `a=rtcp` reuses the SDP connection
491    /// address token, so a multicast address may carry a time-to-live and a
492    /// number-of-addresses count in `address/ttl[/count]` notation, e.g.
493    /// `"224.2.1.1/16/3"`
494    /// ([RFC 3605 Section 2](https://datatracker.ietf.org/doc/html/rfc3605#section-2),
495    /// [RFC 8866 Section 5.7](https://tools.ietf.org/html/rfc8866#section-5.7)).
496    ///
497    /// The address must parse as an [`IpAddr`]; an IPv4 multicast
498    /// time-to-live and a number-of-addresses suffix must parse as numbers.
499    /// A time-to-live is only defined for "IP4", for "IP6" the only suffix
500    /// is the address count.
501    ///
502    /// Returns `None` if the address part is absent, otherwise the parsed
503    /// [`ConnectionAddress`] or a [`ConnectionAddressError`] for the first
504    /// invalid component.
505    pub fn try_address(&self) -> Option<Result<ConnectionAddress, ConnectionAddressError>> {
506        let address = self.connection_address.as_deref()?;
507        match self.effective_addrtype() {
508            Some(addrtype) => Some(parse_connection_address(address, &addrtype)),
509            None => Some(Err(ConnectionAddressError::InvalidAddress(
510                address.to_string(),
511            ))),
512        }
513    }
514
515    /// Whether the `connection_address` is an IP multicast address
516    ///
517    /// Returns `false` if the address part is absent or cannot be parsed as
518    /// an [`IpAddr`].
519    pub fn is_multicast(&self) -> bool {
520        matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
521            Some(Ok(address)) if address.is_multicast())
522    }
523
524    /// Like [`Rtcp::is_multicast`] but returns the parse error instead
525    /// of `false` if the address cannot be parsed. Only the address itself
526    /// is considered, invalid TTL or address-count suffixes do not error.
527    ///
528    /// Returns `None` if the address part is absent.
529    pub fn try_is_multicast(&self) -> Option<Result<bool, ConnectionAddressError>> {
530        let address = self.connection_address.as_deref()?;
531        Some(
532            parse_connection_ip_address(address)
533                .map(|address| address.is_multicast())
534                .map_err(|_| ConnectionAddressError::InvalidAddress(address.to_string())),
535        )
536    }
537
538    /// Whether the `connection_address` is an IPv4 address
539    ///
540    /// Returns `false` if the address part is absent or cannot be parsed as
541    /// an [`IpAddr`].
542    pub fn is_ipv4(&self) -> bool {
543        matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
544            Some(Ok(address)) if address.is_ipv4())
545    }
546
547    /// Whether the `connection_address` is an IPv6 address
548    ///
549    /// Returns `false` if the address part is absent or cannot be parsed as
550    /// an [`IpAddr`].
551    pub fn is_ipv6(&self) -> bool {
552        matches!(self.connection_address.as_deref().map(parse_connection_ip_address),
553            Some(Ok(address)) if address.is_ipv6())
554    }
555
556    /// Sets the `connection_address` & `addrtype` of `self` from the specified `IpAddr`
557    ///
558    /// Also sets the `nettype` to `IN` if it is not set yet.
559    pub fn set_connection_ip_address(&mut self, connection_address: impl Into<IpAddr>) {
560        let connection_address = connection_address.into();
561        self.nettype.get_or_insert(NetType::In);
562        self.addrtype = Some(connection_address.into());
563        self.connection_address = Some(connection_address.to_string());
564    }
565}
566
567impl FromStr for Rtcp {
568    type Err = AttributeError;
569
570    fn from_str(s: &str) -> Result<Self, Self::Err> {
571        // Runs of spaces are a single separator, like in o= and c= lines,
572        // so that no field can end up empty.
573        let mut i = s.split(' ').filter(|part| !part.is_empty());
574        let Some(port) = i.next() else {
575            return Err(AttributeError::ParamNotFound {
576                param: "Port".to_string(),
577                attr: <Self as TypedAttribute>::NAME.to_string(),
578            });
579        };
580
581        let Ok(port) = port.parse::<u16>() else {
582            return Err(AttributeError::InvalidParamValue {
583                param: "Port".to_string(),
584                val: port.to_string(),
585                attr: <Self as TypedAttribute>::NAME.to_string(),
586            });
587        };
588
589        // The address part is optional (RFC 3605): if the port is the only
590        // field, RTCP is sent to the media's connection address.
591        let Some(nettype) = i.next() else {
592            return Ok(Self::with_port(port));
593        };
594
595        // RFC 3605 reuses the SDP nettype and addrtype tokens (RFC 8866,
596        // section 9), same as in o= and c= lines.
597        if !is_token(nettype.as_bytes()) {
598            return Err(AttributeError::InvalidParamValue {
599                param: "Network type".to_string(),
600                val: nettype.to_string(),
601                attr: <Self as TypedAttribute>::NAME.to_string(),
602            });
603        }
604        let nettype = NetType::from_str(nettype).expect("infallible");
605
606        let Some(addrtype) = i.next() else {
607            return Err(AttributeError::ParamNotFound {
608                param: "Address type".to_string(),
609                attr: <Self as TypedAttribute>::NAME.to_string(),
610            });
611        };
612
613        if !is_token(addrtype.as_bytes()) {
614            return Err(AttributeError::InvalidParamValue {
615                param: "Address type".to_string(),
616                val: addrtype.to_string(),
617                attr: <Self as TypedAttribute>::NAME.to_string(),
618            });
619        }
620        let addrtype = AddrType::from_str(addrtype).expect("infallible");
621
622        let Some(connection_address) = i.next() else {
623            return Err(AttributeError::ParamNotFound {
624                param: "Connection address".to_string(),
625                attr: <Self as TypedAttribute>::NAME.to_string(),
626            });
627        };
628
629        if let Some(unexpected) = i.next() {
630            return Err(AttributeError::UnexpectedTrailingItem {
631                val: unexpected.to_string(),
632                attr: <Self as TypedAttribute>::NAME.to_string(),
633            });
634        }
635
636        Ok(Self {
637            port,
638            nettype: Some(nettype),
639            addrtype: Some(addrtype),
640            connection_address: Some(connection_address.to_string()),
641        })
642    }
643}
644
645impl Display for Rtcp {
646    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
647        write!(f, "{}", self.port)?;
648        if let Some(nettype) = &self.nettype {
649            write!(f, " {nettype}")?;
650        }
651        if let Some(addrtype) = &self.addrtype {
652            write!(f, " {addrtype}")?;
653        }
654        if let Some(connection_address) = &self.connection_address {
655            write!(f, " {connection_address}")?;
656        }
657        Ok(())
658    }
659}
660
661impl TypedAttribute for Rtcp {
662    const NAME: &'static str = "rtcp";
663}
664
665/// RTCP Feedback Capability
666///
667/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
668#[derive(Debug, PartialEq, Eq, Clone)]
669#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
670pub struct RtcpFb {
671    /// Payload format for which feedback messages may be used
672    pub pt: RtcpFbPt,
673    /// RTCP Feedback value
674    pub val: RtcpFbVal,
675}
676
677impl RtcpFb {
678    pub fn new(pt: impl Into<RtcpFbPt>, val: impl Into<RtcpFbVal>) -> Self {
679        RtcpFb {
680            pt: pt.into(),
681            val: val.into(),
682        }
683    }
684}
685
686impl FromStr for RtcpFb {
687    type Err = AttributeError;
688
689    fn from_str(s: &str) -> Result<Self, Self::Err> {
690        let mut i = s.split(' ');
691        let Some(pt) = i.next() else {
692            return Err(AttributeError::ParamNotFound {
693                param: "Payload format".to_string(),
694                attr: <Self as TypedAttribute>::NAME.to_string(),
695            });
696        };
697
698        let pt = if let Ok(pt) = pt.parse::<u8>() {
699            RtcpFbPt::Fmt(pt)
700        } else if pt == "*" {
701            RtcpFbPt::Wildcard
702        } else {
703            return Err(AttributeError::InvalidParamValue {
704                param: "Payload format".to_string(),
705                val: pt.to_string(),
706                attr: <Self as TypedAttribute>::NAME.to_string(),
707            });
708        };
709
710        let Some(val) = i.next() else {
711            return Err(AttributeError::ParamNotFound {
712                param: "Rtcp feedback value".to_string(),
713                attr: <Self as TypedAttribute>::NAME.to_string(),
714            });
715        };
716
717        let rtcp_fb_val = match val {
718            "ack" => {
719                if let Some(ack_val) = i.next() {
720                    let ack_val = match ack_val {
721                        "rpsi" => RtcpFbAck::Rpsi,
722                        "app" => {
723                            if let Some(app_param) = i.next() {
724                                RtcpFbAck::App(Some(app_param.to_string()))
725                            } else {
726                                RtcpFbAck::App(None)
727                            }
728                        }
729                        "ccfb" => {
730                            // The payload type used with "ccfb" feedback MUST be the wildcard type
731                            // See https://datatracker.ietf.org/doc/html/rfc8888#section-6
732                            if let RtcpFbPt::Fmt(pt) = pt {
733                                return Err(AttributeError::InvalidParamValue {
734                                    param: "Payload type of Congestion control feedback (ccfb)"
735                                        .to_string(),
736                                    val: format!("{pt}(expected wildcard (*))"),
737                                    attr: <Self as TypedAttribute>::NAME.to_string(),
738                                });
739                            } else {
740                                RtcpFbAck::Ccfb
741                            }
742                        }
743                        other => RtcpFbAck::Other(other.to_string()),
744                    };
745                    RtcpFbVal::Ack(Some(ack_val))
746                } else {
747                    RtcpFbVal::Ack(None)
748                }
749            }
750            "nack" => {
751                if let Some(nack_val) = i.next() {
752                    let nack_val = match nack_val {
753                        "pli" => RtcpFbNack::Pli,
754                        "sli" => RtcpFbNack::Sli,
755                        "rpsi" => RtcpFbNack::Rpsi,
756                        "app" => {
757                            if let Some(app_param) = i.next() {
758                                RtcpFbNack::App(Some(app_param.to_string()))
759                            } else {
760                                RtcpFbNack::App(None)
761                            }
762                        }
763                        "ecn" => RtcpFbNack::Ecn,
764                        other => RtcpFbNack::Other(other.to_string()),
765                    };
766                    RtcpFbVal::Nack(Some(nack_val))
767                } else {
768                    RtcpFbVal::Nack(None)
769                }
770            }
771            "trr-int" => {
772                if let Some(val) = i.next() {
773                    let Ok(i) = val.parse::<u64>() else {
774                        return Err(AttributeError::InvalidParamValue {
775                            param: "Minimum interval between RTCP packets (trr-int)".to_string(),
776                            val: val.to_string(),
777                            attr: <Self as TypedAttribute>::NAME.to_string(),
778                        });
779                    };
780                    RtcpFbVal::TrrInt(i)
781                } else {
782                    return Err(AttributeError::Other {
783                        error: "Minimum interval between RTCP packets (trr-int) not specified"
784                            .to_string(),
785                        attr: <Self as TypedAttribute>::NAME.to_string(),
786                    });
787                }
788            }
789            "ccm" => {
790                if let Some(ccm_val) = i.next() {
791                    let ccm_val = match ccm_val {
792                        "fir" => RtcpFbCcm::Fir,
793                        "tmmbr" => {
794                            if let Some(tmmbr_val) = i.next() {
795                                RtcpFbCcm::Tmmbr(Some(tmmbr_val.to_string()))
796                            } else {
797                                RtcpFbCcm::Tmmbr(None)
798                            }
799                        }
800                        "tstr" => RtcpFbCcm::Tstr,
801                        "vbcm" => {
802                            let mut v = vec![];
803                            for vbcm_val in i {
804                                let Ok(p) = vbcm_val.parse::<u8>() else {
805                                    return Err(AttributeError::InvalidParamValue {
806                                        param: "Video backchannel messages (vbcm)".to_string(),
807                                        val: vbcm_val.to_string(),
808                                        attr: <Self as TypedAttribute>::NAME.to_string(),
809                                    });
810                                };
811                                v.push(p);
812                            }
813                            RtcpFbCcm::Vbcm(v)
814                        }
815                        other => RtcpFbCcm::Other(other.to_string()),
816                    };
817                    RtcpFbVal::Ccm(ccm_val)
818                } else {
819                    return Err(AttributeError::ParamNotFound {
820                        param: "Codec control messages (ccm)".to_string(),
821                        attr: <Self as TypedAttribute>::NAME.to_string(),
822                    });
823                }
824            }
825            "transport-cc" => RtcpFbVal::TransportCc,
826            other => RtcpFbVal::Other(other.to_string()),
827        };
828
829        Ok(Self {
830            pt,
831            val: rtcp_fb_val,
832        })
833    }
834}
835
836impl Display for RtcpFb {
837    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
838        match self.pt {
839            RtcpFbPt::Wildcard => f.write_char('*')?,
840            RtcpFbPt::Fmt(pt) => write!(f, "{pt}")?,
841        }
842
843        f.write_char(' ')?;
844        write!(f, "{}", self.val)
845    }
846}
847
848impl TypedAttribute for RtcpFb {
849    const NAME: &'static str = "rtcp-fb";
850}
851
852/// Media Direction Attributes
853///
854/// See [RFC 8866 Section 6.7](https://datatracker.ietf.org/doc/html/rfc8866#section-6.7)
855#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
856#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
857pub enum Direction {
858    #[default]
859    SendRecv,
860    SendOnly,
861    RecvOnly,
862    Inactive,
863}
864
865impl Direction {
866    pub fn as_str(&self) -> &'static str {
867        match self {
868            Self::SendOnly => "sendonly",
869            Self::RecvOnly => "recvonly",
870            Self::SendRecv => "sendrecv",
871            Self::Inactive => "inactive",
872        }
873    }
874
875    pub fn has_send(self) -> bool {
876        matches!(self, Self::SendRecv | Self::SendOnly)
877    }
878
879    pub fn has_recv(self) -> bool {
880        matches!(self, Self::SendRecv | Self::RecvOnly)
881    }
882
883    pub fn reverse(self) -> Self {
884        match self {
885            Self::SendRecv => Self::SendRecv,
886            Self::SendOnly => Self::RecvOnly,
887            Self::RecvOnly => Self::SendOnly,
888            Self::Inactive => Self::Inactive,
889        }
890    }
891
892    pub fn intersect_with_remote(self, remote: Self) -> Self {
893        match (self, remote) {
894            (Self::Inactive, _)
895            | (_, Self::Inactive)
896            | (Self::RecvOnly, Self::RecvOnly)
897            | (Self::SendOnly, Self::SendOnly) => Self::Inactive,
898            (Self::SendRecv, Self::SendRecv) => Self::SendRecv,
899            (Self::SendOnly, Self::RecvOnly | Self::SendRecv)
900            | (Self::SendRecv, Self::RecvOnly) => Self::SendOnly,
901            (Self::RecvOnly, Self::SendRecv | Self::SendOnly)
902            | (Self::SendRecv, Self::SendOnly) => Self::RecvOnly,
903        }
904    }
905}
906
907impl FromStr for Direction {
908    type Err = ParseEnumError;
909
910    fn from_str(s: &str) -> Result<Self, Self::Err> {
911        if "sendonly".eq_ignore_ascii_case(s) {
912            Ok(Direction::SendOnly)
913        } else if "recvonly".eq_ignore_ascii_case(s) {
914            Ok(Direction::RecvOnly)
915        } else if "sendrecv".eq_ignore_ascii_case(s) {
916            Ok(Direction::SendRecv)
917        } else if "inactive".eq_ignore_ascii_case(s) {
918            Ok(Direction::Inactive)
919        } else {
920            Err(ParseEnumError::Invalid(s.to_string()))
921        }
922    }
923}
924
925impl Display for Direction {
926    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
927        f.write_str(self.as_str())
928    }
929}
930
931impl From<Direction> for Attribute {
932    fn from(attr: Direction) -> Attribute {
933        Attribute {
934            attribute: attr.to_string(),
935            value: None,
936        }
937    }
938}
939
940/// RTP header extensions map
941///
942/// See [RFC 8285 Section 8](https://datatracker.ietf.org/doc/html/rfc8285#section-8)
943#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
944#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
945pub struct ExtMap {
946    /// The local identifier (ID) of this extension
947    pub id: u8,
948    /// Direction
949    pub direction: Option<Direction>,
950    /// The format and meaning of the extension
951    pub uri: String,
952    /// Extension attributes
953    pub attributes: Option<String>,
954}
955
956impl ExtMap {
957    pub fn new(id: u8, uri: impl ToString) -> Self {
958        ExtMap {
959            id,
960            direction: None,
961            uri: uri.to_string(),
962            attributes: None,
963        }
964    }
965
966    pub fn builder(id: u8, uri: impl ToString) -> builders::ExtMap {
967        builders::ExtMap::new(id, uri)
968    }
969
970    pub fn with_direction(id: u8, direction: Direction, uri: impl ToString) -> Self {
971        ExtMap {
972            id,
973            direction: Some(direction),
974            uri: uri.to_string(),
975            attributes: None,
976        }
977    }
978
979    pub fn with_direction_and_attributes(
980        id: u8,
981        direction: Direction,
982        uri: impl ToString,
983        attributes: impl ToString,
984    ) -> Self {
985        ExtMap {
986            id,
987            direction: Some(direction),
988            uri: uri.to_string(),
989            attributes: Some(attributes.to_string()),
990        }
991    }
992
993    pub fn set_direction(&mut self, direction: Direction) {
994        self.direction = Some(direction);
995    }
996
997    pub fn set_attributes(&mut self, attributes: impl ToString) {
998        self.attributes = Some(attributes.to_string());
999    }
1000}
1001
1002impl FromStr for ExtMap {
1003    type Err = AttributeError;
1004
1005    fn from_str(s: &str) -> Result<Self, Self::Err> {
1006        let mut i = s.splitn(3, ' ');
1007
1008        let Some(id_direction) = i.next() else {
1009            return Err(AttributeError::ParamNotFound {
1010                param: "id/direction".to_string(),
1011                attr: <Self as TypedAttribute>::NAME.to_string(),
1012            });
1013        };
1014
1015        let mut d = id_direction.split('/');
1016
1017        let Some(id) = d.next() else {
1018            return Err(AttributeError::ParamNotFound {
1019                param: "id".to_string(),
1020                attr: <Self as TypedAttribute>::NAME.to_string(),
1021            });
1022        };
1023
1024        let direction = if let Some(d) = d.next() {
1025            let Ok(dir) = Direction::from_str(d) else {
1026                return Err(AttributeError::InvalidParamValue {
1027                    param: "Direction".to_string(),
1028                    val: d.to_string(),
1029                    attr: <Self as TypedAttribute>::NAME.to_string(),
1030                });
1031            };
1032            Some(dir)
1033        } else {
1034            None
1035        };
1036
1037        let Ok(id) = id.parse::<u8>() else {
1038            return Err(AttributeError::InvalidParamValue {
1039                param: "Id".to_string(),
1040                val: id.to_string(),
1041                attr: <Self as TypedAttribute>::NAME.to_string(),
1042            });
1043        };
1044
1045        if id == 0 {
1046            return Err(AttributeError::InvalidParamValue {
1047                param: "Id".to_string(),
1048                val: id.to_string(),
1049                attr: <Self as TypedAttribute>::NAME.to_string(),
1050            });
1051        }
1052
1053        let Some(uri) = i.next() else {
1054            return Err(AttributeError::ParamNotFound {
1055                param: "URI".to_string(),
1056                attr: <Self as TypedAttribute>::NAME.to_string(),
1057            });
1058        };
1059
1060        let attributes = i.next().map(|attr| attr.to_string());
1061
1062        Ok(Self {
1063            id,
1064            direction,
1065            uri: uri.to_string(),
1066            attributes,
1067        })
1068    }
1069}
1070
1071impl Display for ExtMap {
1072    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1073        write!(f, "{}", self.id)?;
1074        if let Some(direction) = &self.direction {
1075            f.write_char('/')?;
1076            f.write_str(direction.as_str())?;
1077        }
1078
1079        f.write_char(' ')?;
1080        f.write_str(&self.uri)?;
1081
1082        if let Some(attr) = &self.attributes {
1083            f.write_char(' ')?;
1084            f.write_str(attr.as_str())?;
1085        }
1086        Ok(())
1087    }
1088}
1089
1090impl TypedAttribute for ExtMap {
1091    const NAME: &'static str = "extmap";
1092}
1093
1094/// Fingerprint Attribute
1095///
1096/// See [RFC 8122 Section 5](https://datatracker.ietf.org/doc/html/rfc8122#section-5)
1097#[derive(Debug, Clone, PartialEq, Eq)]
1098#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1099pub struct Fingerprint {
1100    /// Name of hash function used
1101    pub hash_func: HashFunc,
1102    /// Hash value
1103    pub fingerprint: Vec<u8>,
1104}
1105
1106impl Fingerprint {
1107    pub fn new(hash_func: HashFunc) -> Self {
1108        Fingerprint {
1109            hash_func,
1110            fingerprint: vec![],
1111        }
1112    }
1113
1114    pub fn with_fingerprint(
1115        hash_func: HashFunc,
1116        fingerprint: impl IntoIterator<Item = u8>,
1117    ) -> Self {
1118        Fingerprint {
1119            hash_func,
1120            fingerprint: std::iter::FromIterator::from_iter(fingerprint),
1121        }
1122    }
1123
1124    pub fn set_fingerprint(&mut self, fingerprint: impl IntoIterator<Item = u8>) {
1125        self.fingerprint.clear();
1126        self.fingerprint.extend(fingerprint);
1127    }
1128}
1129
1130impl FromStr for Fingerprint {
1131    type Err = AttributeError;
1132
1133    fn from_str(s: &str) -> Result<Self, Self::Err> {
1134        let mut i = s.splitn(2, ' ');
1135
1136        let hash_func = if let Some(hash_func) = i.next() {
1137            HashFunc::new(hash_func)
1138        } else {
1139            return Err(AttributeError::ParamNotFound {
1140                param: "Hash function".to_string(),
1141                attr: <Self as TypedAttribute>::NAME.to_string(),
1142            });
1143        };
1144
1145        let mut fingerprint: Vec<u8> = vec![];
1146        if let Some(fp) = i.next() {
1147            for f in fp.split(':') {
1148                let Ok(mut f) = hex::decode(f) else {
1149                    return Err(AttributeError::InvalidParamValue {
1150                        param: "Fingerprint value".to_string(),
1151                        val: f.to_string(),
1152                        attr: <Self as TypedAttribute>::NAME.to_string(),
1153                    });
1154                };
1155
1156                fingerprint.append(&mut f);
1157            }
1158        } else {
1159            return Err(AttributeError::ParamNotFound {
1160                param: "Hash value".to_string(),
1161                attr: <Self as TypedAttribute>::NAME.to_string(),
1162            });
1163        };
1164
1165        Ok(Self {
1166            hash_func,
1167            fingerprint,
1168        })
1169    }
1170}
1171
1172impl Display for Fingerprint {
1173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1174        f.write_str(self.hash_func.as_str())?;
1175        let mut first = true;
1176        for v in &self.fingerprint {
1177            if first {
1178                f.write_char(' ')?;
1179                first = false;
1180            } else {
1181                f.write_char(':')?;
1182            }
1183            write!(f, "{v:02X}")?;
1184        }
1185        Ok(())
1186    }
1187}
1188
1189impl TypedAttribute for Fingerprint {
1190    const NAME: &'static str = "fingerprint";
1191}
1192
1193/// Group Attribute
1194///
1195/// See [RFC 5888 Section 5](https://datatracker.ietf.org/doc/html/rfc5888#section-5)
1196#[derive(Debug, Clone, PartialEq, Eq)]
1197#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1198pub struct Group {
1199    pub semantics: GroupSemantics,
1200    pub mid_tags: Vec<String>,
1201}
1202
1203impl Group {
1204    pub fn new(semantics: GroupSemantics) -> Self {
1205        Group {
1206            semantics,
1207            mid_tags: vec![],
1208        }
1209    }
1210
1211    pub fn add_mid_tag(&mut self, mid_tag: impl ToString) {
1212        self.mid_tags.push(mid_tag.to_string())
1213    }
1214
1215    pub fn add_mid_tags(&mut self, mid_tags: impl IntoIterator<Item = impl ToString>) {
1216        self.mid_tags
1217            .extend(mid_tags.into_iter().map(|i| i.to_string()))
1218    }
1219}
1220
1221impl FromStr for Group {
1222    type Err = AttributeError;
1223
1224    fn from_str(s: &str) -> Result<Self, Self::Err> {
1225        let mut i = s.split(' ');
1226
1227        let Some(semantics) = i.next() else {
1228            return Err(AttributeError::ParamNotFound {
1229                param: "Semantics".to_string(),
1230                attr: <Self as TypedAttribute>::NAME.to_string(),
1231            });
1232        };
1233
1234        let semantics = GroupSemantics::new(semantics);
1235
1236        let mut mid_tags = vec![];
1237        for mid in i {
1238            mid_tags.push(mid.to_string());
1239        }
1240
1241        if mid_tags.is_empty() {
1242            return Err(AttributeError::ParamNotFound {
1243                param: "Media identification tags".to_string(),
1244                attr: <Self as TypedAttribute>::NAME.to_string(),
1245            });
1246        }
1247
1248        Ok(Self {
1249            semantics,
1250            mid_tags,
1251        })
1252    }
1253}
1254
1255impl Display for Group {
1256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1257        f.write_str(self.semantics.as_str())?;
1258        for m in &self.mid_tags {
1259            f.write_char(' ')?;
1260            f.write_str(m)?;
1261        }
1262        Ok(())
1263    }
1264}
1265
1266impl TypedAttribute for Group {
1267    const NAME: &'static str = "group";
1268}
1269
1270/// Setup attribute for the session or media.
1271///
1272/// See [RFC 4145 Section 4](https://datatracker.ietf.org/doc/html/rfc4145#section-4) for more details.
1273#[derive(Debug, Clone, PartialEq, Eq)]
1274#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1275pub enum Setup {
1276    /// Initiator of the connection.
1277    Active,
1278    /// Acceptor of the connection.
1279    Passive,
1280    /// Act as either initiator or acceptor of the connection.
1281    ActPass,
1282    /// Do not establish a connection.
1283    HoldConn,
1284}
1285
1286impl FromStr for Setup {
1287    type Err = AttributeError;
1288
1289    fn from_str(s: &str) -> Result<Self, Self::Err> {
1290        if "active".eq_ignore_ascii_case(s) {
1291            Ok(Setup::Active)
1292        } else if "passive".eq_ignore_ascii_case(s) {
1293            Ok(Setup::Passive)
1294        } else if "actpass".eq_ignore_ascii_case(s) {
1295            Ok(Setup::ActPass)
1296        } else if "holdconn".eq_ignore_ascii_case(s) {
1297            Ok(Setup::HoldConn)
1298        } else {
1299            Err(AttributeError::Other {
1300                error: format!("Invalid Setup value {s}"),
1301                attr: <Self as TypedAttribute>::NAME.to_string(),
1302            })
1303        }
1304    }
1305}
1306
1307impl Display for Setup {
1308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1309        let s = match self {
1310            Setup::Active => "active",
1311            Setup::Passive => "passive",
1312            Setup::ActPass => "actpass",
1313            Setup::HoldConn => "holdconn",
1314        };
1315        f.write_str(s)
1316    }
1317}
1318
1319impl TypedAttribute for Setup {
1320    const NAME: &'static str = "setup";
1321}
1322
1323/// SSRC media attribute.
1324///
1325/// See [RFC 5576 Section 4.1](https://datatracker.ietf.org/doc/html/rfc5576#section-4.1)
1326#[derive(Debug, Clone, PartialEq, Eq)]
1327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1328pub struct Ssrc {
1329    pub ssrc_id: u32,
1330    pub attribute: SsrcAttribute,
1331    pub value: Option<String>,
1332}
1333
1334impl Ssrc {
1335    pub fn new(ssrc_id: u32, attribute: SsrcAttribute) -> Self {
1336        Ssrc {
1337            ssrc_id,
1338            attribute,
1339            value: None,
1340        }
1341    }
1342
1343    pub fn with_typed_attribute(ssrc_id: u32, attribute: impl TypedAttribute) -> Self {
1344        let value = attribute.to_string();
1345        Ssrc {
1346            ssrc_id,
1347            attribute: SsrcAttribute::from(attribute),
1348            value: Some(value),
1349        }
1350    }
1351
1352    pub fn with_value(ssrc_id: u32, attribute: SsrcAttribute, value: impl ToString) -> Self {
1353        Ssrc {
1354            ssrc_id,
1355            attribute,
1356            value: Some(value.to_string()),
1357        }
1358    }
1359
1360    pub fn set_value(&mut self, value: impl ToString) {
1361        self.value = Some(value.to_string());
1362    }
1363
1364    /// Gets the inner attribute as a `TypedAttribute`.
1365    ///
1366    /// # Errors
1367    ///
1368    /// * `AttributeError::Other` if the inner attribute doesn't match
1369    ///   the specified `TypedAttribute` or if the value is empty.
1370    /// * a specific `AttributeError` if the typed attribute couldn't be built.
1371    pub fn get_typed<T: TypedAttribute>(&self) -> Result<T, AttributeError> {
1372        if !self.attribute.as_str().eq_ignore_ascii_case(T::NAME) {
1373            return Err(AttributeError::Other {
1374                error: format!("Attribute type mismatch (requested {})", T::NAME),
1375                attr: self.attribute.as_str().to_string(),
1376            });
1377        }
1378
1379        // Property attributes have no value; they parse the empty string,
1380        // consistent with Session::attributes_typed().
1381        T::from_str(self.value.as_deref().unwrap_or_default())
1382    }
1383}
1384
1385impl FromStr for Ssrc {
1386    type Err = AttributeError;
1387
1388    fn from_str(s: &str) -> Result<Self, Self::Err> {
1389        let Some((ssrc_id_str, rest)) = s.split_once(' ') else {
1390            return Err(AttributeError::ParamNotFound {
1391                param: "Ssrc id".to_string(),
1392                attr: <Self as TypedAttribute>::NAME.to_string(),
1393            });
1394        };
1395
1396        let Ok(ssrc_id) = ssrc_id_str.parse::<u32>() else {
1397            return Err(AttributeError::InvalidParamValue {
1398                param: "Ssrc id".to_string(),
1399                val: ssrc_id_str.to_string(),
1400                attr: <Self as TypedAttribute>::NAME.to_string(),
1401            });
1402        };
1403
1404        let (attr, value) = if let Some((attr_str, value)) = rest.split_once(':') {
1405            (attr_str, Some(value.to_string()))
1406        } else {
1407            (rest, None)
1408        };
1409
1410        Ok(Self {
1411            ssrc_id,
1412            attribute: SsrcAttribute::new(attr),
1413            value,
1414        })
1415    }
1416}
1417
1418impl Display for Ssrc {
1419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1420        use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
1421
1422        let attr_str = match &self.attribute {
1423            SsrcAttribute::Cname => "cname",
1424            SsrcAttribute::PreviousSsrc => "previous-ssrc",
1425            SsrcAttribute::Fmtp => <Fmtp as TypedAttribute>::NAME,
1426            SsrcAttribute::Rtcp => <Rtcp as TypedAttribute>::NAME,
1427            SsrcAttribute::ReferenceClock => <ReferenceClock as TypedAttribute>::NAME,
1428            SsrcAttribute::MediaClockSource => <MediaClockSource as TypedAttribute>::NAME,
1429            SsrcAttribute::Other(other) => other.as_str(),
1430        };
1431        write!(f, "{} {attr_str}", self.ssrc_id)?;
1432
1433        if let Some(value) = &self.value {
1434            f.write_char(':')?;
1435            f.write_str(value)?;
1436        }
1437
1438        Ok(())
1439    }
1440}
1441
1442impl TypedAttribute for Ssrc {
1443    const NAME: &'static str = "ssrc";
1444}
1445
1446/// SSRC group attribute
1447///
1448/// See [RFC 5576 Section 4.2](https://datatracker.ietf.org/doc/html/rfc5576#section-4.2)
1449#[derive(Debug, Clone, PartialEq, Eq)]
1450#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1451pub struct SsrcGroup {
1452    pub semantics: GroupSemantics,
1453    pub ssrc_ids: Vec<u32>,
1454}
1455
1456impl SsrcGroup {
1457    pub fn new(semantics: GroupSemantics) -> Self {
1458        SsrcGroup {
1459            semantics,
1460            ssrc_ids: vec![],
1461        }
1462    }
1463
1464    pub fn add_ssrc_id(&mut self, ssrc_id: u32) {
1465        self.ssrc_ids.push(ssrc_id)
1466    }
1467
1468    pub fn add_ssrc_ids(&mut self, ssrc_ids: impl IntoIterator<Item = u32>) {
1469        self.ssrc_ids.extend(ssrc_ids)
1470    }
1471}
1472
1473impl FromStr for SsrcGroup {
1474    type Err = AttributeError;
1475
1476    fn from_str(s: &str) -> Result<Self, Self::Err> {
1477        let mut i = s.split(' ');
1478
1479        let Some(semantics) = i.next() else {
1480            return Err(AttributeError::ParamNotFound {
1481                param: "Semantics".to_string(),
1482                attr: <Self as TypedAttribute>::NAME.to_string(),
1483            });
1484        };
1485
1486        let semantics = if "FEC".eq_ignore_ascii_case(semantics) {
1487            GroupSemantics::FEC
1488        } else if "FID".eq_ignore_ascii_case(semantics) {
1489            GroupSemantics::FID
1490        } else {
1491            // The initial defined semantics for ssrc-group attribute are FID and FEC
1492            // The other registered group semantics are not useful for source grouping
1493            // But keep this open for any other new semantics that are not part of GroupSemantics
1494            GroupSemantics::Other(semantics.to_string())
1495        };
1496
1497        let mut ssrc_ids = vec![];
1498        for ssrc_id in i {
1499            let Ok(ssrc_id) = ssrc_id.parse::<u32>() else {
1500                return Err(AttributeError::InvalidParamValue {
1501                    param: "Ssrc id".to_string(),
1502                    val: ssrc_id.to_string(),
1503                    attr: <Self as TypedAttribute>::NAME.to_string(),
1504                });
1505            };
1506            ssrc_ids.push(ssrc_id);
1507        }
1508
1509        if ssrc_ids.is_empty() {
1510            return Err(AttributeError::ParamNotFound {
1511                param: "ssrc_id".to_string(),
1512                attr: <Self as TypedAttribute>::NAME.to_string(),
1513            });
1514        }
1515
1516        Ok(Self {
1517            semantics,
1518            ssrc_ids,
1519        })
1520    }
1521}
1522
1523impl Display for SsrcGroup {
1524    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1525        let sem = match &self.semantics {
1526            GroupSemantics::FEC => "FEC",
1527            GroupSemantics::FID => "FID",
1528            // Semantics other than FEC and FID are not useful for source grouping but still displaying
1529            // them for debugging purpose
1530            GroupSemantics::LS => "LS",
1531            GroupSemantics::SRF => "SRF",
1532            GroupSemantics::ANAT => "ANAT",
1533            GroupSemantics::DDP => "DDP",
1534            GroupSemantics::Other(s) => s.as_str(),
1535        };
1536
1537        f.write_str(sem)?;
1538        for ssrc_id in &self.ssrc_ids {
1539            f.write_char(' ')?;
1540            write!(f, "{ssrc_id}")?;
1541        }
1542
1543        Ok(())
1544    }
1545}
1546
1547impl TypedAttribute for SsrcGroup {
1548    const NAME: &'static str = "ssrc-group";
1549}
1550
1551/// SRTP Key parameter
1552///
1553/// See [RFC 4568 Section 6.1](https://datatracker.ietf.org/doc/html/rfc4568#section-6.1)
1554#[derive(Debug, PartialEq, Eq, Clone)]
1555#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1556pub struct SrtpKeyParam {
1557    /// Concatenated key and salt, base64 encoded
1558    pub key_and_salt: String,
1559    /// Master key lifetime (max number of SRTP or SRTCP packets using this master key)
1560    ///
1561    /// The value must not exceed 2^48 (RFC 4568, section 8.1).
1562    pub lifetime: Option<u64>,
1563    /// MKI (Master Key Identifier) and length of the MKI field in SRTP packets
1564    pub mki_and_length: Option<(u32, u32)>,
1565}
1566
1567impl SrtpKeyParam {
1568    pub fn new(key_and_salt: impl ToString) -> Self {
1569        SrtpKeyParam {
1570            key_and_salt: key_and_salt.to_string(),
1571            lifetime: None,
1572            mki_and_length: None,
1573        }
1574    }
1575
1576    pub fn with_lifetime(key_and_salt: impl ToString, lifetime: u64) -> Self {
1577        SrtpKeyParam {
1578            key_and_salt: key_and_salt.to_string(),
1579            lifetime: Some(lifetime),
1580            mki_and_length: None,
1581        }
1582    }
1583
1584    pub fn with_lifetime_and_mki_and_length(
1585        key_and_salt: impl ToString,
1586        lifetime: u64,
1587        mki: u32,
1588        length: u32,
1589    ) -> Self {
1590        SrtpKeyParam {
1591            key_and_salt: key_and_salt.to_string(),
1592            lifetime: Some(lifetime),
1593            mki_and_length: Some((mki, length)),
1594        }
1595    }
1596
1597    pub fn set_lifetime(&mut self, lifetime: u64) {
1598        self.lifetime = Some(lifetime);
1599    }
1600
1601    pub fn set_mki_and_length(&mut self, mki: u32, length: u32) {
1602        self.mki_and_length = Some((mki, length));
1603    }
1604}
1605
1606// Parse a plain decimal integer as used in the crypto attribute: digits
1607// only, no sign and no leading zeroes, except for the value 0 itself
1608// (RFC 4568, section 8.1).
1609fn parse_decimal_no_leading_zeroes(s: &str) -> Option<u64> {
1610    if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
1611        return None;
1612    }
1613    if s.len() > 1 && s.starts_with('0') {
1614        return None;
1615    }
1616    s.parse().ok()
1617}
1618
1619impl FromStr for SrtpKeyParam {
1620    type Err = AttributeError;
1621    fn from_str(key_param: &str) -> Result<Self, Self::Err> {
1622        let mut k = key_param.split('|');
1623
1624        let Some(key_and_salt_with_method) = k.next() else {
1625            return Err(AttributeError::ParamNotFound {
1626                param: "Srtp Key and Salt".to_string(),
1627                attr: Crypto::NAME.to_string(),
1628            });
1629        };
1630
1631        let key_and_salt = if key_and_salt_with_method
1632            .get(..7)
1633            .is_some_and(|p| p.eq_ignore_ascii_case("inline:"))
1634        {
1635            &key_and_salt_with_method[7..]
1636        } else {
1637            return Err(AttributeError::InvalidParamValue {
1638                param: "Srtp Key and Salt".to_string(),
1639                val: key_and_salt_with_method.to_string(),
1640                attr: Crypto::NAME.to_string(),
1641            });
1642        };
1643
1644        let (lifetime, mki_and_length) = if let Some(next_param) = k.next() {
1645            match next_param.split_once(':') {
1646                Some(mki_and_length) => {
1647                    // lifetime is not specified, but only MKI and its length
1648                    let Ok(mki) = mki_and_length.0.parse::<u32>() else {
1649                        return Err(AttributeError::InvalidParamValue {
1650                            param: "MKI".to_string(),
1651                            val: next_param.to_string(),
1652                            attr: Crypto::NAME.to_string(),
1653                        });
1654                    };
1655
1656                    let Ok(len) = mki_and_length.1.parse::<u32>() else {
1657                        return Err(AttributeError::InvalidParamValue {
1658                            param: "Length".to_string(),
1659                            val: next_param.to_string(),
1660                            attr: Crypto::NAME.to_string(),
1661                        });
1662                    };
1663                    (None, Some((mki, len)))
1664                }
1665                None => {
1666                    // lifetime is specified
1667                    // The maximum master key lifetime is 2^48 packets for all
1668                    // crypto suites (RFC 4568, section 8.1).
1669                    const MAX_LIFETIME: u64 = 1 << 48;
1670                    let lifetime = match next_param.strip_prefix("2^") {
1671                        Some(exp) => {
1672                            let Some(exp) = parse_decimal_no_leading_zeroes(exp)
1673                                .and_then(|exp| u32::try_from(exp).ok())
1674                            else {
1675                                return Err(AttributeError::InvalidParamValue {
1676                                    param: "Lifetime".to_string(),
1677                                    val: next_param.to_string(),
1678                                    attr: Crypto::NAME.to_string(),
1679                                });
1680                            };
1681                            let Some(lifetime) = 1u64.checked_shl(exp) else {
1682                                return Err(AttributeError::InvalidParamValue {
1683                                    param: "Lifetime".to_string(),
1684                                    val: next_param.to_string(),
1685                                    attr: Crypto::NAME.to_string(),
1686                                });
1687                            };
1688                            // 1u64.checked_shl() never returns zero
1689                            if lifetime > MAX_LIFETIME {
1690                                return Err(AttributeError::InvalidParamValue {
1691                                    param: "Lifetime".to_string(),
1692                                    val: next_param.to_string(),
1693                                    attr: Crypto::NAME.to_string(),
1694                                });
1695                            }
1696                            Some(lifetime)
1697                        }
1698                        None => {
1699                            let Some(lifetime) = parse_decimal_no_leading_zeroes(next_param) else {
1700                                return Err(AttributeError::InvalidParamValue {
1701                                    param: "Lifetime".to_string(),
1702                                    val: next_param.to_string(),
1703                                    attr: Crypto::NAME.to_string(),
1704                                });
1705                            };
1706                            if lifetime == 0 || lifetime > MAX_LIFETIME {
1707                                return Err(AttributeError::InvalidParamValue {
1708                                    param: "Lifetime".to_string(),
1709                                    val: next_param.to_string(),
1710                                    attr: Crypto::NAME.to_string(),
1711                                });
1712                            }
1713                            Some(lifetime)
1714                        }
1715                    };
1716
1717                    // now parse the MKI and length
1718                    let mki_and_length = if let Some(m) = k.next() {
1719                        if let Some(p) = m.split_once(':') {
1720                            let Ok(mki) = p.0.parse::<u32>() else {
1721                                return Err(AttributeError::InvalidParamValue {
1722                                    param: "MKI".to_string(),
1723                                    val: m.to_string(),
1724                                    attr: Crypto::NAME.to_string(),
1725                                });
1726                            };
1727
1728                            let Ok(len) = p.1.parse::<u32>() else {
1729                                return Err(AttributeError::InvalidParamValue {
1730                                    param: "Length".to_string(),
1731                                    val: m.to_string(),
1732                                    attr: Crypto::NAME.to_string(),
1733                                });
1734                            };
1735                            Some((mki, len))
1736                        } else {
1737                            return Err(AttributeError::ParamNotFound {
1738                                param: "MKI and Length".to_string(),
1739                                attr: Crypto::NAME.to_string(),
1740                            });
1741                        }
1742                    } else {
1743                        None
1744                    };
1745
1746                    (lifetime, mki_and_length)
1747                }
1748            }
1749        } else {
1750            (None, None)
1751        };
1752
1753        if let Some((_, len)) = mki_and_length {
1754            if !(1..=128).contains(&len) {
1755                return Err(AttributeError::InvalidParamValue {
1756                    param: "MKI length".to_string(),
1757                    val: len.to_string(),
1758                    attr: Crypto::NAME.to_string(),
1759                });
1760            }
1761        }
1762
1763        if let Some(unexpected) = k.next() {
1764            return Err(AttributeError::UnexpectedTrailingItem {
1765                val: unexpected.to_string(),
1766                attr: Crypto::NAME.to_string(),
1767            });
1768        }
1769
1770        Ok(Self {
1771            key_and_salt: key_and_salt.to_string(),
1772            lifetime,
1773            mki_and_length,
1774        })
1775    }
1776}
1777
1778impl Display for SrtpKeyParam {
1779    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1780        write!(f, "inline:{}", self.key_and_salt)?;
1781        if let Some(lifetime) = self.lifetime {
1782            if lifetime.is_power_of_two() {
1783                write!(f, "|2^{}", lifetime.trailing_zeros())?;
1784            } else {
1785                write!(f, "|{lifetime}")?;
1786            }
1787        }
1788        if let Some((mki, length)) = self.mki_and_length {
1789            write!(f, "|{mki}:{length}")?;
1790        }
1791        Ok(())
1792    }
1793}
1794
1795/// Cryptographic information for the media
1796///
1797/// See [RFC 4568 Section 4](https://datatracker.ietf.org/doc/html/rfc4568#section-4)
1798#[derive(Debug, PartialEq, Eq, Clone)]
1799#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1800pub struct Crypto {
1801    pub tag: u32,
1802    pub crypto_suite: CryptoSuite,
1803    pub key_params: Vec<SrtpKeyParam>,
1804    pub session_params: Vec<SrtpSessionParam>,
1805}
1806
1807impl Crypto {
1808    pub fn new(tag: u32, crypto_suite: CryptoSuite) -> Self {
1809        Crypto {
1810            tag,
1811            crypto_suite,
1812            key_params: vec![],
1813            session_params: vec![],
1814        }
1815    }
1816
1817    pub fn add_key_param(&mut self, key_param: SrtpKeyParam) {
1818        self.key_params.push(key_param)
1819    }
1820
1821    pub fn add_key_params(&mut self, key_params: impl IntoIterator<Item = SrtpKeyParam>) {
1822        self.key_params.extend(key_params)
1823    }
1824
1825    pub fn add_session_param(&mut self, session_param: SrtpSessionParam) {
1826        self.session_params.push(session_param)
1827    }
1828
1829    pub fn add_session_params(
1830        &mut self,
1831        session_params: impl IntoIterator<Item = SrtpSessionParam>,
1832    ) {
1833        self.session_params.extend(session_params)
1834    }
1835}
1836
1837impl FromStr for Crypto {
1838    type Err = AttributeError;
1839
1840    fn from_str(s: &str) -> Result<Self, Self::Err> {
1841        let mut i = s.split(' ');
1842
1843        let Some(tag) = i.next() else {
1844            return Err(AttributeError::ParamNotFound {
1845                param: "Tag".to_string(),
1846                attr: <Self as TypedAttribute>::NAME.to_string(),
1847            });
1848        };
1849
1850        let Ok(tag) = tag.parse::<u32>() else {
1851            return Err(AttributeError::InvalidParamValue {
1852                param: "Tag".to_string(),
1853                val: tag.to_string(),
1854                attr: <Self as TypedAttribute>::NAME.to_string(),
1855            });
1856        };
1857
1858        let Some(crypto_suite) = i.next() else {
1859            return Err(AttributeError::ParamNotFound {
1860                param: "CryptoSuite".to_string(),
1861                attr: <Self as TypedAttribute>::NAME.to_string(),
1862            });
1863        };
1864
1865        let crypto_suite = CryptoSuite::new(crypto_suite);
1866
1867        let Some(key_params_str) = i.next() else {
1868            return Err(AttributeError::ParamNotFound {
1869                param: "Key params".to_string(),
1870                attr: <Self as TypedAttribute>::NAME.to_string(),
1871            });
1872        };
1873
1874        let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1875
1876        for key_param in key_params_str.split(';') {
1877            let key_param = SrtpKeyParam::from_str(key_param)?;
1878            key_params.push(key_param);
1879        }
1880
1881        let mut session_params: Vec<SrtpSessionParam> = Vec::new();
1882        for s in &mut i {
1883            let param = if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("KDR=")) {
1884                let kdr_val = &s[4..];
1885                let Ok(kdr_val) = kdr_val.parse::<u8>() else {
1886                    return Err(AttributeError::InvalidParamValue {
1887                        param: "KDR".to_string(),
1888                        val: kdr_val.to_string(),
1889                        attr: <Self as TypedAttribute>::NAME.to_string(),
1890                    });
1891                };
1892
1893                // Note: the range for KDR value is conflicting in the spec,
1894                // rfc4568#section-6.3.1 says the range should be 1,2,...24 and
1895                // the grammar in rfc4568#section-9.2 says it should be 0..24.
1896                // So using the bigger range i.e., 0..24 for now
1897                if !(0..=24).contains(&kdr_val) {
1898                    return Err(AttributeError::InvalidParamValue {
1899                        param: "KDR".to_string(),
1900                        val: format!("{kdr_val}(expected range 0..24)"),
1901                        attr: <Self as TypedAttribute>::NAME.to_string(),
1902                    });
1903                }
1904                SrtpSessionParam::Kdr(kdr_val)
1905            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTCP") {
1906                SrtpSessionParam::UnencryptedSrtcp
1907            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTP") {
1908                SrtpSessionParam::UnencryptedSrtp
1909            } else if s.eq_ignore_ascii_case("UNAUTHENTICATED_SRTP") {
1910                SrtpSessionParam::UnauthenticatedSrtp
1911            } else if s
1912                .get(..10)
1913                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_ORDER="))
1914            {
1915                let fec_ord = &s[10..];
1916                if fec_ord.eq_ignore_ascii_case("FEC_SRTP") {
1917                    SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
1918                } else if fec_ord.eq_ignore_ascii_case("SRTP_FEC") {
1919                    SrtpSessionParam::FecOrder(FecOrder::SrtpFec)
1920                } else {
1921                    return Err(AttributeError::InvalidParamValue {
1922                        param: "FEC order".to_string(),
1923                        val: s.to_string(),
1924                        attr: <Self as TypedAttribute>::NAME.to_string(),
1925                    });
1926                }
1927            } else if s
1928                .get(..8)
1929                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_KEY="))
1930            {
1931                let key_params_str = &s[8..];
1932                let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1933
1934                for key_param in key_params_str.split(';') {
1935                    let key_param = SrtpKeyParam::from_str(key_param)?;
1936                    key_params.push(key_param);
1937                }
1938                SrtpSessionParam::FecKey(key_params)
1939            } else if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("WSH=")) {
1940                let wsh_val = &s[4..];
1941                let Ok(wsh_val) = wsh_val.parse::<u8>() else {
1942                    return Err(AttributeError::InvalidParamValue {
1943                        param: "WSH".to_string(),
1944                        val: wsh_val.to_string(),
1945                        attr: <Self as TypedAttribute>::NAME.to_string(),
1946                    });
1947                };
1948
1949                if wsh_val < 64 {
1950                    return Err(AttributeError::InvalidParamValue {
1951                        param: "WSH".to_string(),
1952                        val: wsh_val.to_string(),
1953                        attr: <Self as TypedAttribute>::NAME.to_string(),
1954                    });
1955                }
1956                SrtpSessionParam::Wsh(wsh_val)
1957            } else {
1958                // Extension
1959                SrtpSessionParam::Extension(s.to_string())
1960            };
1961            session_params.push(param);
1962        }
1963
1964        Ok(Self {
1965            tag,
1966            key_params,
1967            crypto_suite,
1968            session_params,
1969        })
1970    }
1971}
1972
1973impl Display for Crypto {
1974    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1975        write!(f, "{} {}", self.tag, self.crypto_suite.as_str())?;
1976
1977        for (i, key_param) in self.key_params.iter().enumerate() {
1978            if i == 0 {
1979                f.write_char(' ')?;
1980            } else {
1981                f.write_char(';')?;
1982            }
1983
1984            write!(f, "{}", key_param)?;
1985        }
1986
1987        for session_param in &self.session_params {
1988            match session_param {
1989                SrtpSessionParam::Kdr(kdr) => write!(f, " KDR={kdr}")?,
1990                SrtpSessionParam::UnencryptedSrtp => write!(f, " UNENCRYPTED_SRTP")?,
1991                SrtpSessionParam::UnencryptedSrtcp => write!(f, " UNENCRYPTED_SRTCP")?,
1992                SrtpSessionParam::UnauthenticatedSrtp => write!(f, " UNAUTHENTICATED_SRTP")?,
1993                SrtpSessionParam::FecOrder(fec_order) => {
1994                    let order = match fec_order {
1995                        FecOrder::FecSrtp => "FEC_SRTP",
1996                        FecOrder::SrtpFec => "SRTP_FEC",
1997                    };
1998                    write!(f, " FEC_ORDER={order}")?;
1999                }
2000                SrtpSessionParam::FecKey(srtp_key_params) => {
2001                    write!(f, " FEC_KEY")?;
2002                    for (i, key_param) in srtp_key_params.iter().enumerate() {
2003                        if i == 0 {
2004                            f.write_char('=')?;
2005                        } else {
2006                            f.write_char(';')?;
2007                        }
2008
2009                        write!(f, "{}", key_param)?;
2010                    }
2011                }
2012                SrtpSessionParam::Wsh(wsh) => write!(f, " WSH={wsh}")?,
2013                SrtpSessionParam::Extension(extn) => {
2014                    f.write_char(' ')?;
2015                    f.write_str(extn)?;
2016                }
2017            }
2018        }
2019        Ok(())
2020    }
2021}
2022
2023impl TypedAttribute for Crypto {
2024    const NAME: &'static str = "crypto";
2025}
2026
2027/// ICE Candidate attribute of the media
2028///
2029/// See [RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)
2030#[derive(Debug, PartialEq, Eq, Clone)]
2031#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2032pub struct Candidate {
2033    /// Arbitrary string used in the freezing algorithm to group similar candidates
2034    /// See [RFC 8445 Section 5.1.1.3](https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.1.3)
2035    pub foundation: String,
2036    /// Identifies the specific component of the data stream
2037    /// 1 for RTP and 2 for RTCP
2038    pub component_id: u32,
2039    /// Transport protocol of the candidate
2040    pub transport: String,
2041    /// Candidate's priority
2042    pub priority: u64,
2043    /// IP address of the candidate
2044    /// IPv4, IPv6 addresses and FQDN allowed
2045    pub address: CandidateAddress,
2046    /// Port of the candidate
2047    pub port: u16,
2048    /// Type of the candidate
2049    pub typ: CandidateType,
2050    /// Address related to the candidate
2051    /// Required for srflx, prflx and relay type candidates
2052    pub rel_addr: Option<IpAddr>,
2053    /// Port related to the candidate
2054    /// Required for srflx, prflx and relay type candidates
2055    pub rel_port: Option<u16>,
2056    /// Extensions
2057    pub extensions: Vec<(String, String)>,
2058}
2059
2060impl Candidate {
2061    pub fn new(
2062        foundation: impl ToString,
2063        component_id: u32,
2064        transport: impl ToString,
2065        priority: u64,
2066        address: CandidateAddress,
2067        port: u16,
2068        typ: CandidateType,
2069    ) -> Self {
2070        Candidate {
2071            foundation: foundation.to_string(),
2072            component_id,
2073            transport: transport.to_string(),
2074            priority,
2075            address,
2076            port,
2077            typ,
2078            rel_addr: None,
2079            rel_port: None,
2080            extensions: vec![],
2081        }
2082    }
2083
2084    #[allow(clippy::too_many_arguments)]
2085    pub fn with_rel_addr(
2086        foundation: impl ToString,
2087        component_id: u32,
2088        transport: impl ToString,
2089        priority: u64,
2090        address: CandidateAddress,
2091        port: u16,
2092        typ: CandidateType,
2093        rel_addr: impl Into<IpAddr>,
2094    ) -> Self {
2095        Candidate {
2096            foundation: foundation.to_string(),
2097            component_id,
2098            transport: transport.to_string(),
2099            priority,
2100            address,
2101            port,
2102            typ,
2103            rel_addr: Some(rel_addr.into()),
2104            rel_port: None,
2105            extensions: vec![],
2106        }
2107    }
2108
2109    #[allow(clippy::too_many_arguments)]
2110    pub fn with_rel_addr_and_port(
2111        foundation: impl ToString,
2112        component_id: u32,
2113        transport: impl ToString,
2114        priority: u64,
2115        address: CandidateAddress,
2116        port: u16,
2117        typ: CandidateType,
2118        rel_addr: impl Into<IpAddr>,
2119        rel_port: u16,
2120    ) -> Self {
2121        Candidate {
2122            foundation: foundation.to_string(),
2123            component_id,
2124            transport: transport.to_string(),
2125            priority,
2126            address,
2127            port,
2128            typ,
2129            rel_addr: Some(rel_addr.into()),
2130            rel_port: Some(rel_port),
2131            extensions: vec![],
2132        }
2133    }
2134
2135    pub fn set_rel_addr(&mut self, rel_addr: impl Into<IpAddr>) {
2136        self.rel_addr = Some(rel_addr.into());
2137    }
2138
2139    pub fn set_rel_port(&mut self, rel_port: u16) {
2140        self.rel_port = Some(rel_port);
2141    }
2142
2143    pub fn add_extension(&mut self, name: impl ToString, value: impl ToString) {
2144        self.extensions.push((name.to_string(), value.to_string()))
2145    }
2146}
2147
2148impl FromStr for Candidate {
2149    type Err = AttributeError;
2150
2151    fn from_str(s: &str) -> Result<Self, Self::Err> {
2152        let mut i = s.split(' ');
2153
2154        let Some(foundation) = i.next() else {
2155            return Err(AttributeError::ParamNotFound {
2156                param: "Foundation".to_string(),
2157                attr: <Self as TypedAttribute>::NAME.to_string(),
2158            });
2159        };
2160
2161        let Some(comp_id) = i.next() else {
2162            return Err(AttributeError::ParamNotFound {
2163                param: "Component id".to_string(),
2164                attr: <Self as TypedAttribute>::NAME.to_string(),
2165            });
2166        };
2167
2168        let Ok(comp_id) = comp_id.parse::<u32>() else {
2169            return Err(AttributeError::InvalidParamValue {
2170                param: "Component id".to_string(),
2171                val: comp_id.to_string(),
2172                attr: <Self as TypedAttribute>::NAME.to_string(),
2173            });
2174        };
2175
2176        let Some(transport) = i.next() else {
2177            return Err(AttributeError::ParamNotFound {
2178                param: "Transport".to_string(),
2179                attr: <Self as TypedAttribute>::NAME.to_string(),
2180            });
2181        };
2182
2183        let Some(priority) = i.next() else {
2184            return Err(AttributeError::ParamNotFound {
2185                param: "Priority".to_string(),
2186                attr: <Self as TypedAttribute>::NAME.to_string(),
2187            });
2188        };
2189
2190        let Ok(priority) = priority.parse::<u64>() else {
2191            return Err(AttributeError::InvalidParamValue {
2192                param: "Priority".to_string(),
2193                val: priority.to_string(),
2194                attr: <Self as TypedAttribute>::NAME.to_string(),
2195            });
2196        };
2197
2198        let Some(address) = i.next() else {
2199            return Err(AttributeError::ParamNotFound {
2200                param: "Address".to_string(),
2201                attr: <Self as TypedAttribute>::NAME.to_string(),
2202            });
2203        };
2204
2205        let address = match address.parse::<IpAddr>() {
2206            Ok(a) => CandidateAddress::IpAddr(a),
2207            Err(_) => CandidateAddress::FQDN(address.to_string()),
2208        };
2209
2210        let Some(port) = i.next() else {
2211            return Err(AttributeError::ParamNotFound {
2212                param: "Port".to_string(),
2213                attr: <Self as TypedAttribute>::NAME.to_string(),
2214            });
2215        };
2216
2217        let Ok(port) = port.parse::<u16>() else {
2218            return Err(AttributeError::InvalidParamValue {
2219                param: "Port".to_string(),
2220                val: port.to_string(),
2221                attr: <Self as TypedAttribute>::NAME.to_string(),
2222            });
2223        };
2224
2225        let Some(typ_str) = i.next() else {
2226            return Err(AttributeError::ParamNotFound {
2227                param: "'typ' string".to_string(),
2228                attr: <Self as TypedAttribute>::NAME.to_string(),
2229            });
2230        };
2231
2232        if !typ_str.eq_ignore_ascii_case("typ") {
2233            return Err(AttributeError::ParamNotFound {
2234                param: "'typ' string".to_string(),
2235                attr: <Self as TypedAttribute>::NAME.to_string(),
2236            });
2237        }
2238
2239        let Some(cand_type) = i.next() else {
2240            return Err(AttributeError::ParamNotFound {
2241                param: "Candidate type".to_string(),
2242                attr: <Self as TypedAttribute>::NAME.to_string(),
2243            });
2244        };
2245
2246        let cand_type = CandidateType::new(cand_type);
2247
2248        let mut rel_addr: Option<IpAddr> = None;
2249        let mut rel_port: Option<u16> = None;
2250        let mut exts: Vec<(String, String)> = Vec::new();
2251
2252        while let Some(key) = i.next() {
2253            if key.eq_ignore_ascii_case("raddr") {
2254                let Some(raddr) = i.next() else {
2255                    return Err(AttributeError::ParamNotFound {
2256                        param: "Relative address".to_string(),
2257                        attr: <Self as TypedAttribute>::NAME.to_string(),
2258                    });
2259                };
2260
2261                if let Ok(raddr) = raddr.parse::<IpAddr>() {
2262                    rel_addr = Some(raddr);
2263                } else {
2264                    return Err(AttributeError::InvalidParamValue {
2265                        param: "Relative address".to_string(),
2266                        val: raddr.to_string(),
2267                        attr: <Self as TypedAttribute>::NAME.to_string(),
2268                    });
2269                };
2270            } else if key.eq_ignore_ascii_case("rport") {
2271                let Some(rport) = i.next() else {
2272                    return Err(AttributeError::ParamNotFound {
2273                        param: "Relative port".to_string(),
2274                        attr: <Self as TypedAttribute>::NAME.to_string(),
2275                    });
2276                };
2277
2278                if let Ok(rport) = rport.parse::<u16>() {
2279                    rel_port = Some(rport);
2280                } else {
2281                    return Err(AttributeError::InvalidParamValue {
2282                        param: "Relative port".to_string(),
2283                        val: rport.to_string(),
2284                        attr: <Self as TypedAttribute>::NAME.to_string(),
2285                    });
2286                }
2287            } else {
2288                let Some(val) = i.next() else {
2289                    return Err(AttributeError::Other {
2290                        error: format!("No val for the extension {key}"),
2291                        attr: <Self as TypedAttribute>::NAME.to_string(),
2292                    });
2293                };
2294
2295                exts.push((key.to_string(), val.to_string()));
2296            }
2297        }
2298
2299        Ok(Self {
2300            foundation: foundation.to_string(),
2301            component_id: comp_id,
2302            transport: transport.to_string(),
2303            priority,
2304            address,
2305            port,
2306            typ: cand_type,
2307            rel_addr,
2308            rel_port,
2309            extensions: exts,
2310        })
2311    }
2312}
2313
2314impl Display for Candidate {
2315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2316        let candidate_addr = match &self.address {
2317            CandidateAddress::IpAddr(a) => a.to_string(),
2318            CandidateAddress::FQDN(d) => d.clone(),
2319        };
2320        write!(
2321            f,
2322            "{} {} {} {} {} {} typ {}",
2323            self.foundation,
2324            self.component_id,
2325            self.transport,
2326            self.priority,
2327            candidate_addr,
2328            self.port,
2329            self.typ.as_str(),
2330        )?;
2331        if let Some(rel_addr) = self.rel_addr {
2332            write!(f, " raddr {rel_addr}")?;
2333        }
2334        if let Some(rel_port) = self.rel_port {
2335            write!(f, " rport {rel_port}")?;
2336        }
2337        for (key, val) in &self.extensions {
2338            write!(f, " {key} {val}")?;
2339        }
2340        Ok(())
2341    }
2342}
2343
2344impl TypedAttribute for Candidate {
2345    const NAME: &'static str = "candidate";
2346}
2347
2348/// Restricted identification line (`a=rid`) attribute
2349///
2350/// Represents the send/recv restriction that defines a unique RTP payload
2351/// configuration, see
2352/// [RFC 8851 Section 4](https://datatracker.ietf.org/doc/html/rfc8851#section-4).
2353/// Restriction parameters (`pt=`, `max-width=`, `max-br=`, ...) are kept as raw
2354/// `name[=value]` tokens, in order, without interpretation. The optional `pt=`
2355/// payload format list is the first parameter when present.
2356#[derive(Debug, Clone, PartialEq, Eq)]
2357#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2358pub struct Rid {
2359    /// The restriction identifier, referenced by `a=extmap` and SDES items.
2360    pub id: String,
2361    /// Whether the restriction applies to a `send` or `recv` stream.
2362    pub direction: RidDirection,
2363    /// Restriction parameters, e.g. `pt=99,102` or `max-br=64000`.
2364    pub params: Vec<String>,
2365}
2366
2367impl Rid {
2368    pub fn new(id: impl ToString, direction: RidDirection) -> Self {
2369        Rid {
2370            id: id.to_string(),
2371            direction,
2372            params: Vec::new(),
2373        }
2374    }
2375
2376    pub fn add_param(&mut self, param: impl ToString) {
2377        self.params.push(param.to_string());
2378    }
2379
2380    pub fn with_params(
2381        id: impl ToString,
2382        direction: RidDirection,
2383        params: impl IntoIterator<Item = impl ToString>,
2384    ) -> Self {
2385        Rid {
2386            id: id.to_string(),
2387            direction,
2388            params: params.into_iter().map(|p| p.to_string()).collect(),
2389        }
2390    }
2391}
2392
2393impl FromStr for Rid {
2394    type Err = AttributeError;
2395
2396    fn from_str(s: &str) -> Result<Self, Self::Err> {
2397        let attr = <Self as TypedAttribute>::NAME.to_string();
2398        let (id, rest) = s
2399            .split_once(' ')
2400            .ok_or_else(|| AttributeError::ParamNotFound {
2401                param: "Direction".to_string(),
2402                attr: attr.clone(),
2403            })?;
2404
2405        let id = id.trim();
2406        if id.is_empty() {
2407            return Err(AttributeError::ParamNotFound {
2408                param: "ID".to_string(),
2409                attr: attr.clone(),
2410            });
2411        }
2412
2413        // RFC 8851, section 10: rid-id = 1*(alpha-numeric / "-" / "_"),
2414        // with alpha-numeric = ALPHA / DIGIT (RFC 8866, section 8).
2415        if !id
2416            .bytes()
2417            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
2418        {
2419            return Err(AttributeError::InvalidParamValue {
2420                param: "ID".to_string(),
2421                val: id.to_string(),
2422                attr,
2423            });
2424        }
2425
2426        // The direction is optionally followed by a single space and the
2427        // restriction parameter list.
2428        let (direction_str, params_str) = match rest.trim().split_once(' ') {
2429            Some((d, p)) => (d, p),
2430            None => (rest.trim(), ""),
2431        };
2432
2433        let direction = direction_str
2434            .parse()
2435            .map_err(|_| AttributeError::InvalidParamValue {
2436                param: "Direction".to_string(),
2437                val: direction_str.to_string(),
2438                attr: attr.clone(),
2439            })?;
2440
2441        let params = if params_str.trim().is_empty() {
2442            Vec::new()
2443        } else {
2444            params_str
2445                .trim()
2446                .split(';')
2447                .map(|p| p.trim())
2448                .filter(|p| !p.is_empty())
2449                .map(|p| {
2450                    if p.contains(char::is_whitespace) {
2451                        Err(AttributeError::InvalidParamValue {
2452                            param: "Parameter".to_string(),
2453                            val: p.to_string(),
2454                            attr: attr.clone(),
2455                        })
2456                    } else {
2457                        Ok(p.to_string())
2458                    }
2459                })
2460                .collect::<Result<Vec<_>, _>>()?
2461        };
2462
2463        Ok(Rid {
2464            id: id.to_string(),
2465            direction,
2466            params,
2467        })
2468    }
2469}
2470
2471impl Display for Rid {
2472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2473        write!(f, "{} {}", self.id, self.direction)?;
2474        if !self.params.is_empty() {
2475            write!(f, " {}", self.params.join(";"))?;
2476        }
2477        Ok(())
2478    }
2479}
2480
2481impl TypedAttribute for Rid {
2482    const NAME: &'static str = "rid";
2483}
2484
2485/// Key management protocol of the [`KeyMgmt`] attribute.
2486///
2487/// The identifiers (KMPIDs) are case-sensitive, registered at IANA in the
2488/// "SDP/RTSP key management protocol identifiers" subregistry
2489/// ([RFC 4567 Section 9.3](https://datatracker.ietf.org/doc/html/rfc4567#section-9.3));
2490/// `mikey` is the only value defined so far.
2491#[derive(Debug, Clone, PartialEq, Eq)]
2492#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2493pub enum KeyMgmtMethod {
2494    /// Multimedia Internet KEYing ([RFC 3830](https://datatracker.ietf.org/doc/html/rfc3830)).
2495    Mikey,
2496    /// Any other, possibly unregistered method, kept verbatim.
2497    Other(String),
2498}
2499
2500impl From<&str> for KeyMgmtMethod {
2501    fn from(s: &str) -> Self {
2502        match s {
2503            "mikey" => KeyMgmtMethod::Mikey,
2504            other => KeyMgmtMethod::Other(other.to_string()),
2505        }
2506    }
2507}
2508
2509impl std::str::FromStr for KeyMgmtMethod {
2510    // FIXME use the never type when it is stable
2511    type Err = ();
2512
2513    fn from_str(s: &str) -> Result<Self, Self::Err> {
2514        Ok(s.into())
2515    }
2516}
2517
2518impl Display for KeyMgmtMethod {
2519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2520        match self {
2521            KeyMgmtMethod::Mikey => f.write_str("mikey"),
2522            KeyMgmtMethod::Other(other) => f.write_str(other),
2523        }
2524    }
2525}
2526
2527/// Key management attribute (`a=key-mgmt`) attribute
2528///
2529/// See [RFC 4567 Section 4.1](https://datatracker.ietf.org/doc/html/rfc4567#section-4.1).
2530/// The key management data is kept verbatim; for the `mikey` method it is
2531/// the base64-encoded MIKEY message (RFC 3830, RFC 4567 section 7).
2532///
2533/// Media-level `key-mgmt` attributes override session-level ones, which
2534/// consumers have to resolve themselves.
2535#[derive(Debug, Clone, PartialEq, Eq)]
2536#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2537pub struct KeyMgmt {
2538    /// Key management protocol.
2539    pub method: KeyMgmtMethod,
2540    /// Key management data, if any.
2541    pub data: Option<String>,
2542}
2543
2544impl KeyMgmt {
2545    pub fn new(method: KeyMgmtMethod) -> Self {
2546        KeyMgmt { method, data: None }
2547    }
2548
2549    pub fn with_data(method: KeyMgmtMethod, data: impl ToString) -> Self {
2550        KeyMgmt {
2551            method,
2552            data: Some(data.to_string()),
2553        }
2554    }
2555}
2556
2557impl FromStr for KeyMgmt {
2558    type Err = AttributeError;
2559
2560    fn from_str(s: &str) -> Result<Self, Self::Err> {
2561        let s = s.trim();
2562        if s.is_empty() {
2563            return Err(AttributeError::ParamNotFound {
2564                param: "Method".to_string(),
2565                attr: <Self as TypedAttribute>::NAME.to_string(),
2566            });
2567        }
2568
2569        let (method, data) = match s.split_once(char::is_whitespace) {
2570            Some((method, data)) => (method, Some(data.trim().to_string())),
2571            None => (s, None),
2572        };
2573
2574        // RFC 4567, section 3: KMPID = 1*(ALPHA / DIGIT).
2575        if !method.bytes().all(|b| b.is_ascii_alphanumeric()) {
2576            return Err(AttributeError::InvalidParamValue {
2577                param: "Method".to_string(),
2578                val: method.to_string(),
2579                attr: <Self as TypedAttribute>::NAME.to_string(),
2580            });
2581        }
2582
2583        Ok(KeyMgmt {
2584            method: method.into(),
2585            data,
2586        })
2587    }
2588}
2589
2590impl Display for KeyMgmt {
2591    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2592        match &self.data {
2593            Some(data) => write!(f, "{} {}", self.method, data),
2594            None => write!(f, "{}", self.method),
2595        }
2596    }
2597}
2598
2599impl TypedAttribute for KeyMgmt {
2600    const NAME: &'static str = "key-mgmt";
2601}
2602
2603/// Remote candidate for one component of a data stream
2604///
2605/// A single entry of the [`RemoteCandidates`] attribute, in
2606/// `component-id connection-address port` form
2607/// ([RFC 8839 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8839#section-5.2)).
2608///
2609/// The address must be an IP address: agents generating candidates MUST NOT
2610/// use FQDNs and agents receiving them MUST ignore them
2611/// ([RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)).
2612#[derive(Debug, Clone, PartialEq, Eq)]
2613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2614pub struct RemoteCandidate {
2615    /// Component identifier of the data stream, 1 for RTP and 2 for RTCP.
2616    pub component_id: u32,
2617    /// Remote address (IPv4 or IPv6).
2618    pub address: IpAddr,
2619    /// Remote port.
2620    pub port: u16,
2621}
2622
2623impl RemoteCandidate {
2624    pub fn new(component_id: u32, address: impl Into<IpAddr>, port: u16) -> Self {
2625        RemoteCandidate {
2626            component_id,
2627            address: address.into(),
2628            port,
2629        }
2630    }
2631}
2632
2633impl FromStr for RemoteCandidate {
2634    type Err = AttributeError;
2635
2636    fn from_str(s: &str) -> Result<Self, Self::Err> {
2637        let attr = <RemoteCandidates as TypedAttribute>::NAME.to_string();
2638        let mut parts = s.split_whitespace();
2639
2640        let component_id_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2641            param: "Component ID".to_string(),
2642            attr: attr.clone(),
2643        })?;
2644        let component_id =
2645            component_id_token
2646                .parse()
2647                .map_err(|_| AttributeError::InvalidParamValue {
2648                    param: "Component ID".to_string(),
2649                    val: component_id_token.to_string(),
2650                    attr: attr.clone(),
2651                })?;
2652
2653        let address_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2654            param: "Address".to_string(),
2655            attr: attr.clone(),
2656        })?;
2657        let address = address_token
2658            .parse()
2659            .map_err(|_| AttributeError::InvalidParamValue {
2660                param: "Address".to_string(),
2661                val: address_token.to_string(),
2662                attr: attr.clone(),
2663            })?;
2664
2665        let port_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2666            param: "Port".to_string(),
2667            attr: attr.clone(),
2668        })?;
2669        let port = port_token
2670            .parse()
2671            .map_err(|_| AttributeError::InvalidParamValue {
2672                param: "Port".to_string(),
2673                val: port_token.to_string(),
2674                attr: attr.clone(),
2675            })?;
2676
2677        if let Some(val) = parts.next() {
2678            return Err(AttributeError::UnexpectedTrailingItem {
2679                val: val.to_string(),
2680                attr,
2681            });
2682        }
2683
2684        Ok(RemoteCandidate {
2685            component_id,
2686            address,
2687            port,
2688        })
2689    }
2690}
2691
2692impl Display for RemoteCandidate {
2693    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2694        write!(f, "{} {} {}", self.component_id, self.address, self.port)
2695    }
2696}
2697
2698/// Remote candidates attribute (`a=remote-candidates`)
2699///
2700/// Lists the remote ICE candidates corresponding to the nominated pair for
2701/// each component of the data stream
2702/// ([RFC 8839 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8839#section-5.2)).
2703/// The ordering of the components is irrelevant.
2704///
2705/// The grammar allows multiple entries in a single attribute; implementations
2706/// also send one entry per `a=remote-candidates` line, which results in
2707/// multiple attributes on the wire.
2708#[derive(Debug, Clone, PartialEq, Eq)]
2709#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2710pub struct RemoteCandidates(pub Vec<RemoteCandidate>);
2711
2712impl RemoteCandidates {
2713    pub fn new(candidates: impl IntoIterator<Item = RemoteCandidate>) -> Self {
2714        RemoteCandidates(candidates.into_iter().collect())
2715    }
2716}
2717
2718impl FromStr for RemoteCandidates {
2719    type Err = AttributeError;
2720
2721    fn from_str(s: &str) -> Result<Self, Self::Err> {
2722        let attr = <Self as TypedAttribute>::NAME.to_string();
2723        let mut tokens = s.split_whitespace();
2724        let mut candidates = Vec::new();
2725
2726        // Every entry consists of exactly three tokens.
2727        loop {
2728            let triple: Vec<&str> = tokens.by_ref().take(3).collect();
2729            match triple.len() {
2730                0 => break,
2731                1..=2 => {
2732                    return Err(AttributeError::UnexpectedTrailingItem {
2733                        val: triple.join(" "),
2734                        attr,
2735                    })
2736                }
2737                _ => candidates.push(triple.join(" ").parse()?),
2738            }
2739        }
2740
2741        if candidates.is_empty() {
2742            return Err(AttributeError::ParamNotFound {
2743                param: "Candidate".to_string(),
2744                attr,
2745            });
2746        }
2747
2748        Ok(RemoteCandidates(candidates))
2749    }
2750}
2751
2752impl Display for RemoteCandidates {
2753    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2754        for (i, candidate) in self.0.iter().enumerate() {
2755            if i > 0 {
2756                f.write_char(' ')?;
2757            }
2758            write!(f, "{}", candidate)?;
2759        }
2760        Ok(())
2761    }
2762}
2763
2764impl TypedAttribute for RemoteCandidates {
2765    const NAME: &'static str = "remote-candidates";
2766}
2767
2768/// Filter mode of the [`SourceFilter`] (`a=source-filter`) attribute.
2769///
2770/// See [RFC 4570 Section 4](https://datatracker.ietf.org/doc/html/rfc4570#section-4).
2771#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2772#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2773pub enum SourceFilterMode {
2774    /// Accept only packets sent by the listed sources.
2775    Incl,
2776    /// Reject packets sent by the listed sources.
2777    Excl,
2778}
2779
2780impl SourceFilterMode {
2781    pub fn as_str(&self) -> &'static str {
2782        match self {
2783            SourceFilterMode::Incl => "incl",
2784            SourceFilterMode::Excl => "excl",
2785        }
2786    }
2787}
2788
2789impl FromStr for SourceFilterMode {
2790    type Err = ParseEnumError;
2791
2792    fn from_str(s: &str) -> Result<Self, Self::Err> {
2793        if "incl".eq_ignore_ascii_case(s) {
2794            Ok(SourceFilterMode::Incl)
2795        } else if "excl".eq_ignore_ascii_case(s) {
2796            Ok(SourceFilterMode::Excl)
2797        } else {
2798            Err(ParseEnumError::Invalid(s.to_string()))
2799        }
2800    }
2801}
2802
2803impl Display for SourceFilterMode {
2804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2805        f.write_str(self.as_str())
2806    }
2807}
2808
2809/// Address type of the entries of the [`SourceFilter`] (`a=source-filter`)
2810/// attribute.
2811///
2812/// Models the `address-types` production, which extends the SDP `addrtype`
2813/// token with a `*` wildcard for FQDNs
2814/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2815#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2816#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2817pub enum SourceFilterAddrType {
2818    /// Any address family, only allowed with FQDN destinations.
2819    Any,
2820    /// IPv4 address
2821    Ip4,
2822    /// IPv6 address
2823    Ip6,
2824}
2825
2826impl SourceFilterAddrType {
2827    pub fn as_str(&self) -> &'static str {
2828        match self {
2829            SourceFilterAddrType::Any => "*",
2830            SourceFilterAddrType::Ip4 => "IP4",
2831            SourceFilterAddrType::Ip6 => "IP6",
2832        }
2833    }
2834}
2835
2836impl FromStr for SourceFilterAddrType {
2837    type Err = ParseEnumError;
2838
2839    fn from_str(s: &str) -> Result<Self, Self::Err> {
2840        if s == "*" {
2841            Ok(SourceFilterAddrType::Any)
2842        } else if "IP4".eq_ignore_ascii_case(s) {
2843            Ok(SourceFilterAddrType::Ip4)
2844        } else if "IP6".eq_ignore_ascii_case(s) {
2845            Ok(SourceFilterAddrType::Ip6)
2846        } else {
2847            Err(ParseEnumError::Invalid(s.to_string()))
2848        }
2849    }
2850}
2851
2852impl Display for SourceFilterAddrType {
2853    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2854        f.write_str(self.as_str())
2855    }
2856}
2857
2858/// Address of the [`SourceFilter`] (`a=source-filter`) attribute
2859///
2860/// Models the `unicast-address` and `basic-multicast-address` productions:
2861/// an IP address or an FQDN, but without the `/ttl` and `/number of
2862/// addresses` suffixes that a connection address may carry
2863/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2864#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2865#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2866pub enum SourceFilterAddress {
2867    /// IP address
2868    IpAddr(IpAddr),
2869    /// Fully qualified domain name
2870    FQDN(String),
2871}
2872
2873impl From<&str> for SourceFilterAddress {
2874    fn from(s: &str) -> Self {
2875        match s.parse::<IpAddr>() {
2876            Ok(address) => SourceFilterAddress::IpAddr(address),
2877            Err(_) => SourceFilterAddress::FQDN(s.to_string()),
2878        }
2879    }
2880}
2881
2882impl FromStr for SourceFilterAddress {
2883    // FIXME use the never type when it is stable
2884    type Err = ();
2885
2886    fn from_str(s: &str) -> Result<Self, Self::Err> {
2887        Ok(s.into())
2888    }
2889}
2890
2891impl Display for SourceFilterAddress {
2892    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2893        match self {
2894            SourceFilterAddress::IpAddr(address) => write!(f, "{}", address),
2895            SourceFilterAddress::FQDN(domain) => f.write_str(domain),
2896        }
2897    }
2898}
2899
2900/// Destination of the [`SourceFilter`] (`a=source-filter`) attribute
2901///
2902/// Models the `dest-address` production
2903/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2904#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2905#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2906pub enum SourceFilterDestination {
2907    /// The `"*"` wildcard, matching all of the session's connection addresses
2908    Wildcard,
2909    /// Unicast, multicast address or FQDN
2910    Address(SourceFilterAddress),
2911}
2912
2913impl FromStr for SourceFilterDestination {
2914    // FIXME use the never type when it is stable
2915    type Err = ();
2916
2917    fn from_str(s: &str) -> Result<Self, Self::Err> {
2918        if s == "*" {
2919            Ok(SourceFilterDestination::Wildcard)
2920        } else {
2921            Ok(SourceFilterDestination::Address(s.into()))
2922        }
2923    }
2924}
2925
2926impl Display for SourceFilterDestination {
2927    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2928        match self {
2929            SourceFilterDestination::Wildcard => f.write_str("*"),
2930            SourceFilterDestination::Address(address) => write!(f, "{}", address),
2931        }
2932    }
2933}
2934
2935/// Source filter attribute (`a=source-filter`)
2936///
2937/// Restricts the set of source addresses accepted for a destination
2938/// address, e.g. for source-specific multicast.
2939///
2940/// See [RFC 4570 Section 4](https://datatracker.ietf.org/doc/html/rfc4570#section-4).
2941/// The destination must be one of the session's connection addresses or the
2942/// `"*"` wildcard; unlike a connection address it never carries the `/ttl`
2943/// and `/number of addresses` suffixes (RFC 4570, section 3.1).
2944///
2945/// Media-level source filters completely override session-level ones, which
2946/// consumers have to resolve themselves.
2947#[derive(Debug, Clone, PartialEq, Eq)]
2948#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2949pub struct SourceFilter {
2950    /// Inclusion or exclusion filter mode.
2951    pub mode: SourceFilterMode,
2952    /// Network type, usually [`NetType::In`].
2953    pub nettype: NetType,
2954    /// Address family of the destination and source addresses.
2955    pub addrtype: SourceFilterAddrType,
2956    /// Destination address or the `"*"` wildcard.
2957    pub destination: SourceFilterDestination,
2958    /// Source addresses, at least one entry.
2959    pub sources: Vec<SourceFilterAddress>,
2960}
2961
2962impl FromStr for SourceFilter {
2963    type Err = AttributeError;
2964
2965    fn from_str(s: &str) -> Result<Self, Self::Err> {
2966        let attr = <Self as TypedAttribute>::NAME.to_string();
2967
2968        // Runs of spaces are a single separator, like in c= lines,
2969        // so that no field can end up empty.
2970        let mut i = s.split(' ').filter(|part| !part.is_empty());
2971
2972        let Some(mode) = i.next() else {
2973            return Err(AttributeError::ParamNotFound {
2974                param: "Filter mode".to_string(),
2975                attr,
2976            });
2977        };
2978        let mode = mode
2979            .parse()
2980            .map_err(|_| AttributeError::InvalidParamValue {
2981                param: "Filter mode".to_string(),
2982                val: mode.to_string(),
2983                attr: attr.clone(),
2984            })?;
2985
2986        let Some(nettype) = i.next() else {
2987            return Err(AttributeError::ParamNotFound {
2988                param: "Network type".to_string(),
2989                attr,
2990            });
2991        };
2992        // RFC 4570 reuses the SDP nettype token (RFC 8866, section 9).
2993        if !is_token(nettype.as_bytes()) {
2994            return Err(AttributeError::InvalidParamValue {
2995                param: "Network type".to_string(),
2996                val: nettype.to_string(),
2997                attr: attr.clone(),
2998            });
2999        }
3000        let nettype = NetType::from_str(nettype).expect("infallible");
3001
3002        let Some(addrtype) = i.next() else {
3003            return Err(AttributeError::ParamNotFound {
3004                param: "Address types".to_string(),
3005                attr,
3006            });
3007        };
3008        let addrtype = addrtype
3009            .parse()
3010            .map_err(|_| AttributeError::InvalidParamValue {
3011                param: "Address types".to_string(),
3012                val: addrtype.to_string(),
3013                attr: attr.clone(),
3014            })?;
3015
3016        let Some(destination) = i.next() else {
3017            return Err(AttributeError::ParamNotFound {
3018                param: "Destination address".to_string(),
3019                attr,
3020            });
3021        };
3022
3023        // RFC 4570, section 3.1: a multicast destination MUST NOT include
3024        // the /ttl and /number of addresses sub-fields.
3025        if destination.contains('/') {
3026            return Err(AttributeError::InvalidParamValue {
3027                param: "Destination address".to_string(),
3028                val: destination.to_string(),
3029                attr,
3030            });
3031        }
3032        let destination = SourceFilterDestination::from_str(destination).expect("infallible");
3033        // RFC 4570, section 3.1: the "*" address type MUST only be used with
3034        // FQDN destinations.
3035        if addrtype == SourceFilterAddrType::Any
3036            && matches!(
3037                destination,
3038                SourceFilterDestination::Address(SourceFilterAddress::IpAddr(_))
3039            )
3040        {
3041            return Err(AttributeError::InvalidParamValue {
3042                param: "Destination address".to_string(),
3043                val: destination.to_string(),
3044                attr,
3045            });
3046        }
3047
3048        let sources: Vec<&str> = i.collect();
3049        if sources.is_empty() {
3050            return Err(AttributeError::ParamNotFound {
3051                param: "Source list".to_string(),
3052                attr,
3053            });
3054        }
3055        // RFC 4570, section 3.1: only the destination allows the "*" wildcard.
3056        if let Some(source) = sources.iter().find(|source| source == &&"*") {
3057            return Err(AttributeError::InvalidParamValue {
3058                param: "Source address".to_string(),
3059                val: source.to_string(),
3060                attr,
3061            });
3062        }
3063        let sources = sources.into_iter().map(SourceFilterAddress::from).collect();
3064
3065        Ok(SourceFilter {
3066            mode,
3067            nettype,
3068            addrtype,
3069            destination,
3070            sources,
3071        })
3072    }
3073}
3074
3075impl Display for SourceFilter {
3076    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3077        write!(
3078            f,
3079            "{} {} {} {}",
3080            self.mode, self.nettype, self.addrtype, self.destination
3081        )?;
3082        for source in &self.sources {
3083            write!(f, " {}", source)?;
3084        }
3085        Ok(())
3086    }
3087}
3088
3089impl TypedAttribute for SourceFilter {
3090    const NAME: &'static str = "source-filter";
3091}
3092
3093/// Media stream identification tag attribute (`a=mid`)
3094///
3095/// Assigns an identification tag to a media description, unique within
3096/// the session description, used to reference media descriptions from
3097/// grouping semantics such as BUNDLE.
3098///
3099/// See [RFC 5888 Section 4](https://datatracker.ietf.org/doc/html/rfc5888#section-4).
3100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3102pub struct Mid(pub String);
3103
3104impl FromStr for Mid {
3105    type Err = AttributeError;
3106
3107    fn from_str(s: &str) -> Result<Self, Self::Err> {
3108        if !is_token(s.as_bytes()) {
3109            return Err(AttributeError::InvalidParamValue {
3110                param: "Identification tag".to_string(),
3111                val: s.to_string(),
3112                attr: <Self as TypedAttribute>::NAME.to_string(),
3113            });
3114        }
3115
3116        Ok(Mid(s.to_string()))
3117    }
3118}
3119
3120impl Display for Mid {
3121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3122        f.write_str(&self.0)
3123    }
3124}
3125
3126impl TypedAttribute for Mid {
3127    const NAME: &'static str = "mid";
3128}
3129
3130/// RTP/RTCP multiplexing attribute (`a=rtcp-mux`)
3131///
3132/// Indicates that RTP and RTCP packets are sent to the same transport
3133/// address and port. It is a property attribute without a value, used
3134/// at the media level.
3135///
3136/// See [RFC 5761 Section 5](https://datatracker.ietf.org/doc/html/rfc5761#section-5).
3137#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3138#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3139pub struct RtcpMux;
3140
3141impl FromStr for RtcpMux {
3142    type Err = AttributeError;
3143
3144    fn from_str(s: &str) -> Result<Self, Self::Err> {
3145        if !s.is_empty() {
3146            return Err(AttributeError::UnsupportedFormat {
3147                val: s.to_string(),
3148                attr: <Self as TypedAttribute>::NAME.to_string(),
3149            });
3150        }
3151
3152        Ok(RtcpMux)
3153    }
3154}
3155
3156impl Display for RtcpMux {
3157    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3158        Ok(())
3159    }
3160}
3161
3162impl TypedAttribute for RtcpMux {
3163    const NAME: &'static str = "rtcp-mux";
3164}
3165
3166/// RTP/RTCP multiplexing-only attribute (`a=rtcp-mux-only`)
3167///
3168/// Indicates that the endpoint can only receive RTP/RTCP multiplexed
3169/// packets, i.e. it does not accept RTCP packets on a separate
3170/// transport address and port even if one is indicated. It is a
3171/// property attribute without a value, used at the media level.
3172///
3173/// See [RFC 8858 Section 3](https://datatracker.ietf.org/doc/html/rfc8858#section-3).
3174#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3176pub struct RtcpMuxOnly;
3177
3178impl FromStr for RtcpMuxOnly {
3179    type Err = AttributeError;
3180
3181    fn from_str(s: &str) -> Result<Self, Self::Err> {
3182        if !s.is_empty() {
3183            return Err(AttributeError::UnsupportedFormat {
3184                val: s.to_string(),
3185                attr: <Self as TypedAttribute>::NAME.to_string(),
3186            });
3187        }
3188
3189        Ok(RtcpMuxOnly)
3190    }
3191}
3192
3193impl Display for RtcpMuxOnly {
3194    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3195        Ok(())
3196    }
3197}
3198
3199impl TypedAttribute for RtcpMuxOnly {
3200    const NAME: &'static str = "rtcp-mux-only";
3201}
3202
3203/// Reduced-size RTCP attribute (`a=rtcp-rsize`)
3204///
3205/// Indicates that the endpoint supports the reduced-size RTCP
3206/// compound packet format for the media stream. It is a property
3207/// attribute without a value, used at the media level.
3208///
3209/// See [RFC 5506 Section 5](https://datatracker.ietf.org/doc/html/rfc5506#section-5).
3210#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3212pub struct RtcpRsize;
3213
3214impl FromStr for RtcpRsize {
3215    type Err = AttributeError;
3216
3217    fn from_str(s: &str) -> Result<Self, Self::Err> {
3218        if !s.is_empty() {
3219            return Err(AttributeError::UnsupportedFormat {
3220                val: s.to_string(),
3221                attr: <Self as TypedAttribute>::NAME.to_string(),
3222            });
3223        }
3224
3225        Ok(RtcpRsize)
3226    }
3227}
3228
3229impl Display for RtcpRsize {
3230    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3231        Ok(())
3232    }
3233}
3234
3235impl TypedAttribute for RtcpRsize {
3236    const NAME: &'static str = "rtcp-rsize";
3237}
3238
3239/// ICE-lite implementation attribute (`a=ice-lite`)
3240///
3241/// Indicates that the agent implements the ICE-lite implementation,
3242/// which does not run the full ICE connectivity checks. It is a
3243/// property attribute without a value, used at the session level.
3244///
3245/// See [RFC 5245 Section 15.3](https://datatracker.ietf.org/doc/html/rfc5245#section-15.3).
3246#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3247#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3248pub struct IceLite;
3249
3250impl FromStr for IceLite {
3251    type Err = AttributeError;
3252
3253    fn from_str(s: &str) -> Result<Self, Self::Err> {
3254        if !s.is_empty() {
3255            return Err(AttributeError::UnsupportedFormat {
3256                val: s.to_string(),
3257                attr: <Self as TypedAttribute>::NAME.to_string(),
3258            });
3259        }
3260
3261        Ok(IceLite)
3262    }
3263}
3264
3265impl Display for IceLite {
3266    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3267        Ok(())
3268    }
3269}
3270
3271impl TypedAttribute for IceLite {
3272    const NAME: &'static str = "ice-lite";
3273}
3274
3275/// ICE character of the `ice-char` production (RFC 5245, section 15.1).
3276fn is_ice_char(c: char) -> bool {
3277    matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '/')
3278}
3279
3280/// ICE username fragment attribute (`a=ice-ufrag`)
3281///
3282/// Conveys the username fragment used by ICE for message integrity,
3283/// 4 to 256 ICE characters. It can appear at the session level or the
3284/// media level, where the media level value takes precedence.
3285///
3286/// See [RFC 5245 Section 15.4](https://datatracker.ietf.org/doc/html/rfc5245#section-15.4).
3287#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3289pub struct IceUfrag(pub String);
3290
3291impl FromStr for IceUfrag {
3292    type Err = AttributeError;
3293
3294    fn from_str(s: &str) -> Result<Self, Self::Err> {
3295        // ICE characters are ASCII, so the byte length is the length.
3296        if !(4..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
3297            return Err(AttributeError::InvalidParamValue {
3298                param: "ICE username fragment".to_string(),
3299                val: s.to_string(),
3300                attr: <Self as TypedAttribute>::NAME.to_string(),
3301            });
3302        }
3303
3304        Ok(IceUfrag(s.to_string()))
3305    }
3306}
3307
3308impl Display for IceUfrag {
3309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3310        f.write_str(&self.0)
3311    }
3312}
3313
3314impl TypedAttribute for IceUfrag {
3315    const NAME: &'static str = "ice-ufrag";
3316}
3317
3318/// ICE password attribute (`a=ice-pwd`)
3319///
3320/// Conveys the password used by ICE for message integrity, 22 to 256
3321/// ICE characters. It can appear at the session level or the media
3322/// level, where the media level value takes precedence.
3323///
3324/// See [RFC 5245 Section 15.4](https://datatracker.ietf.org/doc/html/rfc5245#section-15.4).
3325#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3326#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3327pub struct IcePwd(pub String);
3328
3329impl FromStr for IcePwd {
3330    type Err = AttributeError;
3331
3332    fn from_str(s: &str) -> Result<Self, Self::Err> {
3333        // ICE characters are ASCII, so the byte length is the length.
3334        if !(22..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
3335            return Err(AttributeError::InvalidParamValue {
3336                param: "ICE password".to_string(),
3337                val: s.to_string(),
3338                attr: <Self as TypedAttribute>::NAME.to_string(),
3339            });
3340        }
3341
3342        Ok(IcePwd(s.to_string()))
3343    }
3344}
3345
3346impl Display for IcePwd {
3347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3348        f.write_str(&self.0)
3349    }
3350}
3351
3352impl TypedAttribute for IcePwd {
3353    const NAME: &'static str = "ice-pwd";
3354}
3355
3356/// ICE extension option tags attribute (`a=ice-options`)
3357///
3358/// Lists the ICE extension option tags supported by the agent, e.g.
3359/// `trickle` (RFC 8839). It is used at the session level.
3360///
3361/// See [RFC 5245 Section 15.5](https://datatracker.ietf.org/doc/html/rfc5245#section-15.5).
3362#[derive(Debug, Clone, PartialEq, Eq)]
3363#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3364pub struct IceOptions(pub Vec<String>);
3365
3366impl IceOptions {
3367    pub fn new(tags: impl IntoIterator<Item = impl ToString>) -> Self {
3368        IceOptions(tags.into_iter().map(|tag| tag.to_string()).collect())
3369    }
3370}
3371
3372impl FromStr for IceOptions {
3373    type Err = AttributeError;
3374
3375    fn from_str(s: &str) -> Result<Self, Self::Err> {
3376        let attr = <Self as TypedAttribute>::NAME.to_string();
3377        let mut tags = Vec::new();
3378
3379        for tag in s.split_whitespace() {
3380            if !tag.chars().all(is_ice_char) {
3381                return Err(AttributeError::InvalidParamValue {
3382                    param: "ICE option tag".to_string(),
3383                    val: tag.to_string(),
3384                    attr,
3385                });
3386            }
3387
3388            tags.push(tag.to_string());
3389        }
3390
3391        if tags.is_empty() {
3392            return Err(AttributeError::ParamNotFound {
3393                param: "ICE option tags".to_string(),
3394                attr,
3395            });
3396        }
3397
3398        Ok(IceOptions(tags))
3399    }
3400}
3401
3402impl Display for IceOptions {
3403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3404        for (i, tag) in self.0.iter().enumerate() {
3405            if i > 0 {
3406                f.write_char(' ')?;
3407            }
3408            f.write_str(tag)?;
3409        }
3410        Ok(())
3411    }
3412}
3413
3414impl TypedAttribute for IceOptions {
3415    const NAME: &'static str = "ice-options";
3416}
3417
3418/// End of ICE candidates attribute (`a=end-of-candidates`)
3419///
3420/// Indicates that the sending agent will not trickle further ICE
3421/// candidates. It is a property attribute without a value, used at the
3422/// session level or the media level.
3423///
3424/// See [RFC 8840 Section 8.1](https://datatracker.ietf.org/doc/html/rfc8840#section-8.1).
3425#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3426#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3427pub struct EndOfCandidates;
3428
3429impl FromStr for EndOfCandidates {
3430    type Err = AttributeError;
3431
3432    fn from_str(s: &str) -> Result<Self, Self::Err> {
3433        if !s.is_empty() {
3434            return Err(AttributeError::UnsupportedFormat {
3435                val: s.to_string(),
3436                attr: <Self as TypedAttribute>::NAME.to_string(),
3437            });
3438        }
3439
3440        Ok(EndOfCandidates)
3441    }
3442}
3443
3444impl Display for EndOfCandidates {
3445    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3446        Ok(())
3447    }
3448}
3449
3450impl TypedAttribute for EndOfCandidates {
3451    const NAME: &'static str = "end-of-candidates";
3452}
3453
3454/// MediaStream identification attribute (`a=msid`)
3455///
3456/// Associates a media description with a MediaStream and identifies the
3457/// MediaStreamTrack it conveys in the application data. Both fields are
3458/// tokens of 1 to 64 characters.
3459///
3460/// See [RFC 8830 Section 2](https://datatracker.ietf.org/doc/html/rfc8830#section-2).
3461#[derive(Debug, Clone, PartialEq, Eq)]
3462#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3463pub struct Msid {
3464    /// Identifier of the MediaStream this media description belongs to.
3465    pub id: String,
3466    /// Application data, the identifier of the MediaStreamTrack.
3467    pub appdata: Option<String>,
3468}
3469
3470impl Msid {
3471    pub fn new(id: impl ToString, appdata: Option<impl ToString>) -> Self {
3472        Msid {
3473            id: id.to_string(),
3474            appdata: appdata.map(|appdata| appdata.to_string()),
3475        }
3476    }
3477}
3478
3479impl FromStr for Msid {
3480    type Err = AttributeError;
3481
3482    fn from_str(s: &str) -> Result<Self, Self::Err> {
3483        let attr = <Self as TypedAttribute>::NAME.to_string();
3484        let mut tokens = s.split_whitespace();
3485
3486        let id = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3487            param: "MSID id".to_string(),
3488            attr: attr.clone(),
3489        })?;
3490        let appdata = tokens.next();
3491
3492        let trailing: Vec<_> = tokens.collect();
3493        if !trailing.is_empty() {
3494            return Err(AttributeError::UnexpectedTrailingItem {
3495                val: trailing.join(" "),
3496                attr,
3497            });
3498        }
3499
3500        // Both fields are 1*64 token-chars (RFC 8830, section 2).
3501        if id.len() > 64 || !is_token(id.as_bytes()) {
3502            return Err(AttributeError::InvalidParamValue {
3503                param: "MSID id".to_string(),
3504                val: id.to_string(),
3505                attr,
3506            });
3507        }
3508
3509        if let Some(appdata) = appdata {
3510            if appdata.len() > 64 || !is_token(appdata.as_bytes()) {
3511                return Err(AttributeError::InvalidParamValue {
3512                    param: "MSID appdata".to_string(),
3513                    val: appdata.to_string(),
3514                    attr,
3515                });
3516            }
3517        }
3518
3519        Ok(Msid {
3520            id: id.to_string(),
3521            appdata: appdata.map(ToString::to_string),
3522        })
3523    }
3524}
3525
3526impl Display for Msid {
3527    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3528        f.write_str(&self.id)?;
3529        if let Some(appdata) = &self.appdata {
3530            f.write_char(' ')?;
3531            f.write_str(appdata)?;
3532        }
3533        Ok(())
3534    }
3535}
3536
3537impl TypedAttribute for Msid {
3538    const NAME: &'static str = "msid";
3539}
3540
3541/// SCTP port attribute (`a=sctp-port`)
3542///
3543/// Indicates the SCTP port number of the association used by the media
3544/// description, used with the "UDP/DTLS/SCTP" and "TCP/DTLS/SCTP"
3545/// proto values at the media level. The port range is 0 to 65535 and
3546/// leading zeroes are not used.
3547///
3548/// See [RFC 8841 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8841#section-5.2).
3549#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3550#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3551pub struct SctpPort(pub u16);
3552
3553/// Parses `1*5DIGIT` without leading zeroes into a `u16` port or count.
3554fn parse_1_5_digits(s: &str) -> Option<u16> {
3555    if s.is_empty()
3556        || s.len() > 5
3557        || !s.chars().all(|c| c.is_ascii_digit())
3558        || (s.len() > 1 && s.starts_with('0'))
3559    {
3560        return None;
3561    }
3562
3563    s.parse().ok()
3564}
3565
3566impl FromStr for SctpPort {
3567    type Err = AttributeError;
3568
3569    fn from_str(s: &str) -> Result<Self, Self::Err> {
3570        let invalid = || AttributeError::InvalidParamValue {
3571            param: "SCTP port".to_string(),
3572            val: s.to_string(),
3573            attr: <Self as TypedAttribute>::NAME.to_string(),
3574        };
3575
3576        // sctp-port-value = 1*5DIGIT, leading zeroes must not be used.
3577        let port = parse_1_5_digits(s).ok_or_else(invalid)?;
3578        Ok(SctpPort(port))
3579    }
3580}
3581
3582impl Display for SctpPort {
3583    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3584        write!(f, "{}", self.0)
3585    }
3586}
3587
3588impl TypedAttribute for SctpPort {
3589    const NAME: &'static str = "sctp-port";
3590}
3591
3592/// Maximum SCTP user message size attribute (`a=max-message-size`)
3593///
3594/// Indicates the maximum SCTP user message size, in bytes, that the
3595/// endpoint is willing to receive, where zero means any size. It is
3596/// used at the media level and defaults to 64 KiB when absent. Leading
3597/// zeroes are not used.
3598///
3599/// See [RFC 8841 Section 6.2](https://datatracker.ietf.org/doc/html/rfc8841#section-6.2).
3600#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3601#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3602pub struct MaxMessageSize(pub u64);
3603
3604impl FromStr for MaxMessageSize {
3605    type Err = AttributeError;
3606
3607    fn from_str(s: &str) -> Result<Self, Self::Err> {
3608        let invalid = || AttributeError::InvalidParamValue {
3609            param: "Max message size".to_string(),
3610            val: s.to_string(),
3611            attr: <Self as TypedAttribute>::NAME.to_string(),
3612        };
3613
3614        // max-message-size-value = 1*DIGIT, leading zeroes must not be used.
3615        let size = parse_decimal_no_leading_zeroes(s).ok_or_else(invalid)?;
3616        Ok(MaxMessageSize(size))
3617    }
3618}
3619
3620impl Display for MaxMessageSize {
3621    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3622        write!(f, "{}", self.0)
3623    }
3624}
3625
3626impl TypedAttribute for MaxMessageSize {
3627    const NAME: &'static str = "max-message-size";
3628}
3629
3630/// SCTP port map attribute (`a=sctpmap`)
3631///
3632/// Legacy association of the SCTP port with the application running on
3633/// the association and the number of streams, sent as
3634/// `a=sctpmap:<port> <app> [<streams>]`. It was defined only in
3635/// `draft-ietf-mmusic-sctp-sdp`, never published as an RFC, and is
3636/// superseded by `a=sctp-port` (RFC 8841, section 5).
3637///
3638/// See [draft-ietf-mmusic-sctp-sdp](https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-sctp-sdp).
3639#[derive(Debug, Clone, PartialEq, Eq)]
3640#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3641pub struct SctpMap {
3642    /// SCTP port number, in the range 0 to 65535.
3643    pub number: u16,
3644    /// Application running on top of the association, e.g.
3645    /// `webrtc-datachannel`.
3646    pub app: String,
3647    /// Number of streams supported by each side of the association.
3648    pub streams: Option<u16>,
3649}
3650
3651impl SctpMap {
3652    pub fn new(number: u16, app: impl ToString, streams: Option<u16>) -> Self {
3653        SctpMap {
3654            number,
3655            app: app.to_string(),
3656            streams,
3657        }
3658    }
3659}
3660
3661impl FromStr for SctpMap {
3662    type Err = AttributeError;
3663
3664    fn from_str(s: &str) -> Result<Self, Self::Err> {
3665        let attr = <Self as TypedAttribute>::NAME.to_string();
3666        let mut tokens = s.split_whitespace();
3667
3668        let number = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3669            param: "SCTP port number".to_string(),
3670            attr: attr.clone(),
3671        })?;
3672        let number = parse_1_5_digits(number).ok_or_else(|| AttributeError::InvalidParamValue {
3673            param: "SCTP port number".to_string(),
3674            val: number.to_string(),
3675            attr: attr.clone(),
3676        })?;
3677
3678        let app = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3679            param: "Application".to_string(),
3680            attr: attr.clone(),
3681        })?;
3682        if !is_token(app.as_bytes()) {
3683            return Err(AttributeError::InvalidParamValue {
3684                param: "Application".to_string(),
3685                val: app.to_string(),
3686                attr,
3687            });
3688        }
3689
3690        let streams = match tokens.next() {
3691            Some(streams) => Some(parse_1_5_digits(streams).ok_or_else(|| {
3692                AttributeError::InvalidParamValue {
3693                    param: "Stream count".to_string(),
3694                    val: streams.to_string(),
3695                    attr: attr.clone(),
3696                }
3697            })?),
3698            None => None,
3699        };
3700
3701        let trailing: Vec<_> = tokens.collect();
3702        if !trailing.is_empty() {
3703            return Err(AttributeError::UnexpectedTrailingItem {
3704                val: trailing.join(" "),
3705                attr,
3706            });
3707        }
3708
3709        Ok(SctpMap {
3710            number,
3711            app: app.to_string(),
3712            streams,
3713        })
3714    }
3715}
3716
3717impl Display for SctpMap {
3718    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3719        write!(f, "{} {}", self.number, self.app)?;
3720        if let Some(streams) = self.streams {
3721            write!(f, " {}", streams)?;
3722        }
3723        Ok(())
3724    }
3725}
3726
3727impl TypedAttribute for SctpMap {
3728    const NAME: &'static str = "sctpmap";
3729}
3730
3731#[cfg(test)]
3732mod tests {
3733    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3734
3735    use super::*;
3736    use crate::*;
3737
3738    #[test]
3739    fn direction_parse() {
3740        assert_eq!("sendonly".parse::<Direction>(), Ok(Direction::SendOnly));
3741        assert_eq!("recvonly".parse::<Direction>(), Ok(Direction::RecvOnly));
3742        assert_eq!("sendrecv".parse::<Direction>(), Ok(Direction::SendRecv));
3743        assert_eq!("inactive".parse::<Direction>(), Ok(Direction::Inactive));
3744        assert!("invalid".parse::<Direction>().is_err());
3745    }
3746
3747    #[test]
3748    fn direction_display() {
3749        assert_eq!(Direction::SendOnly.to_string(), "sendonly");
3750        assert_eq!(Direction::RecvOnly.to_string(), "recvonly");
3751        assert_eq!(Direction::SendRecv.to_string(), "sendrecv");
3752        assert_eq!(Direction::Inactive.to_string(), "inactive");
3753    }
3754
3755    #[test]
3756    fn parse_rtcp_fb() {
3757        let sdp = "v=0\r
3758o=alice 3203093520 3203093520 IN IP4 host.example.com\r
3759s=Multicast video with feedback\r
3760t=3203130148 3203137348\r
3761m=audio 49170 RTP/AVP 0\r
3762c=IN IP4 224.2.1.183\r
3763a=rtpmap:0 PCMU/8000\r
3764m=video 51372 RTP/AVPF 98 99\r
3765c=IN IP4 224.2.1.184\r
3766a=rtpmap:98 H263-1998/90000\r
3767a=rtpmap:99 H261/90000\r
3768a=rtcp-fb:* nack\r
3769a=rtcp-fb:98 nack rpsi\r
3770a=rtcp-fb:* trr-int 1000\r
3771a=rtcp-fb:98 ccm vbcm 1 2\r
3772a=rtcp-fb:* ccm tmmbr smaxpr=120\r
3773";
3774
3775        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3776        let mut written = vec![];
3777        parsed.write(&mut written).unwrap();
3778
3779        let v = fallible_iterator::convert(parsed.medias[1].attributes_typed::<RtcpFb>())
3780            .collect::<Vec<_>>()
3781            .expect("Valid vector of attributes");
3782        assert_eq!(v[0].pt, RtcpFbPt::Wildcard);
3783        assert_eq!(v[1].val, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)));
3784        assert_eq!(v[2].val, RtcpFbVal::TrrInt(1000));
3785        assert_eq!(v[3].val, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(vec![1, 2])));
3786        assert_eq!(
3787            v[4].val,
3788            RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(Some("smaxpr=120".to_string())))
3789        );
3790    }
3791
3792    #[test]
3793    fn parse_group_attribute() {
3794        let sdp = "v=0\r
3795o=Laura 289083124 289083124 IN IP4 two.example.com\r
3796c=IN IP4 233.252.0.1/127\r
3797t=0 0\r
3798a=group:LS 1 2\r
3799m=audio 30000 RTP/AVP 0\r
3800a=mid:1\r
3801m=video 30002 RTP/AVP 31\r
3802a=mid:2\r
3803m=audio 30004 RTP/AVP 0\r
3804i=This media stream contains the Spanish translation\r
3805a=mid:3\r
3806";
3807        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3808
3809        let g = parsed.attributes_typed::<Group>().collect::<Vec<_>>();
3810        assert_eq!(g.len(), 1);
3811        assert_eq!(g[0].as_ref().unwrap().semantics, GroupSemantics::LS);
3812        assert_eq!(
3813            g[0].as_ref().unwrap().mid_tags,
3814            vec!["1".to_string(), "2".to_string()]
3815        );
3816    }
3817
3818    #[test]
3819    fn parse_setup_attribute() {
3820        let sdp = "v=0\r
3821m=image 54111 TCP t38\r
3822c=IN IP4 192.0.2.2\r
3823a=setup:actpass\r
3824a=connection:new\r
3825";
3826        let media = Session::parse(sdp.as_bytes()).unwrap().medias;
3827
3828        let s = media[0].attributes_typed::<Setup>().collect::<Vec<_>>();
3829
3830        assert_eq!(s.len(), 1);
3831        assert_eq!(s[0].as_ref().unwrap().to_owned(), Setup::ActPass);
3832    }
3833
3834    #[test]
3835    fn parse_ssrc_attributes() {
3836        let sdp = "v=0\r
3837o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
3838m=video 49174 RTP/AVPF 96 98\r
3839a=rtpmap:98 rtx/90000\r
3840a=fmtp:98 apt=96;rtx-time=3000\r
3841a=ssrc-group:FID 11111 22222\r
3842a=ssrc:11111 cname:user3@example.com\r
3843a=ssrc:22222 fmtp:0 0-15\r
3844a=ssrc-group:FID 33333 44444\r
3845a=ssrc:33333 cname:user3@example.com\r
3846a=ssrc:44444 cname:user3@example.com\r
3847a=ssrc:1698359993 rtcp:5003 IN IP4 127.0.0.1
3848";
3849
3850        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3851        let m = &parsed.medias[0];
3852
3853        let ssrcs = m
3854            .attributes_typed::<Ssrc>()
3855            .filter(|s| {
3856                let Ok(ssrc) = s else { return false };
3857                ssrc.attribute == SsrcAttribute::Fmtp || ssrc.attribute == SsrcAttribute::Rtcp
3858            })
3859            .collect::<Vec<_>>();
3860
3861        let ssrc_id = ssrcs[0].as_ref().unwrap().ssrc_id;
3862
3863        let ssrc_groups = m
3864            .attributes_typed::<SsrcGroup>()
3865            .filter(|s| {
3866                let Ok(ssrc_group) = s else { return false };
3867
3868                ssrc_group.ssrc_ids[1] == ssrc_id
3869            })
3870            .collect::<Vec<_>>();
3871
3872        assert_eq!(
3873            ssrc_groups[0].as_ref().unwrap().semantics,
3874            GroupSemantics::FID
3875        );
3876
3877        assert_eq!(ssrcs[1].as_ref().unwrap().attribute, SsrcAttribute::Rtcp);
3878    }
3879
3880    #[test]
3881    fn parse_crypto_attributes() {
3882        let sdp = "v=0\r
3883o=sam 2890844526 2890842807 IN IP4 10.47.16.5\r
3884s=SRTP Discussion\r
3885i=A discussion of Secure RTP\r
3886u=http://www.example.com/seminars/srtp.pdf\r
3887e=marge@example.com (Marge Simpson)\r
3888c=IN IP4 168.2.17.12\r
3889t=2873397496 2873404696\r
3890m=audio 49170 RTP/SAVP 0\r
3891a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC\r
3892a=crypto:2 F8_128_HMAC_SHA1_80 inline:MTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5QUJjZGVm|2^20|1:4;inline:QUJjZGVmMTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5|2^20|2:4 FEC_ORDER=FEC_SRTP\r
3893m=video 51372 RTP/SAVP 31\r
3894a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2|1066:4\r
3895";
3896
3897        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3898        let a = &parsed.medias[0];
3899
3900        let audio_cryptos = a.attributes_typed::<Crypto>().collect::<Vec<_>>();
3901
3902        assert_eq!(
3903            audio_cryptos[0].as_ref().unwrap().crypto_suite,
3904            CryptoSuite::AesCm128HmacSha1_80
3905        );
3906        assert_eq!(audio_cryptos[1].as_ref().unwrap().key_params.len(), 2);
3907
3908        assert_eq!(
3909            audio_cryptos[1].as_ref().unwrap().key_params[1].mki_and_length,
3910            Some((2, 4))
3911        );
3912
3913        assert_eq!(
3914            audio_cryptos[1].as_ref().unwrap().session_params[0],
3915            SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
3916        );
3917
3918        let v = &parsed.medias[1];
3919
3920        let video_cryptos = v
3921            .attributes_typed::<Crypto>()
3922            .filter(|c| {
3923                let Ok(crypto) = c else { return false };
3924
3925                crypto.tag == 1
3926            })
3927            .collect::<Vec<_>>();
3928
3929        let test_crypto = Crypto {
3930            tag: 1,
3931            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
3932            key_params: vec![SrtpKeyParam {
3933                key_and_salt: "YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2".to_string(),
3934                lifetime: None,
3935                mki_and_length: Some((1066, 4)),
3936            }],
3937            session_params: Vec::new(),
3938        };
3939
3940        assert_eq!(&test_crypto, video_cryptos[0].as_ref().unwrap());
3941    }
3942
3943    #[test]
3944    fn write_crypto_attribute() {
3945        let crypto = Crypto {
3946            tag: 1,
3947            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
3948            key_params: vec![
3949                SrtpKeyParam {
3950                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
3951                    lifetime: Some(1048576),
3952                    mki_and_length: Some((1, 4)),
3953                },
3954                SrtpKeyParam {
3955                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
3956                    lifetime: Some(1048576),
3957                    mki_and_length: Some((1, 4)),
3958                },
3959            ],
3960            session_params: vec![SrtpSessionParam::FecOrder(FecOrder::SrtpFec)],
3961        };
3962
3963        assert_eq!(
3964            crypto.to_string(),
3965            "1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4;inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC"
3966        );
3967    }
3968
3969    #[test]
3970    fn parse_srtp_key_param_lifetime() {
3971        // RFC 4568 allows lifetimes up to 2^48, which doesn't fit into u32.
3972        let param = SrtpKeyParam::from_str("inline:YWJj|2^48").unwrap();
3973        assert_eq!(param.lifetime, Some(1 << 48));
3974        assert_eq!(param.to_string(), "inline:YWJj|2^48");
3975
3976        let param = SrtpKeyParam::from_str("inline:YWJj|1000000000000").unwrap();
3977        assert_eq!(param.lifetime, Some(1_000_000_000_000));
3978        assert_eq!(param.to_string(), "inline:YWJj|1000000000000");
3979
3980        for invalid in [
3981            // larger than the maximum of 2^48
3982            "inline:YWJj|2^49",
3983            "inline:YWJj|2^100",
3984            "inline:YWJj|281474976710657",
3985            // zero is not a positive integer
3986            "inline:YWJj|0",
3987            // leading zeroes and signs are not allowed
3988            "inline:YWJj|007",
3989            "inline:YWJj|0123",
3990            "inline:YWJj|2^007",
3991            "inline:YWJj|+1048576",
3992            "inline:YWJj|2^+20",
3993        ] {
3994            assert!(
3995                SrtpKeyParam::from_str(invalid).is_err(),
3996                "{} should be invalid",
3997                invalid
3998            );
3999        }
4000    }
4001
4002    #[test]
4003    fn parse_candidate_attributes() {
4004        use std::net::{Ipv4Addr, Ipv6Addr};
4005
4006        let sdp = "v=0\r
4007o=- 2890844526 2890842807 IN IP4 192.168.1.1\r
4008s=-\r
4009c=IN IP4 192.168.1.1\r
4010t=0 0\r
4011m=audio 49152 RTP/AVP 0\r
4012a=candidate:1 1 UDP 2130706432 192.168.1.1 49152 typ host raddr 10.0.1.1 rport 49153 generation 0\r
4013a=candidate:2 1 UDP 1692467200 10.0.1.1 49152 typ srflx raddr 192.168.1.1 rport 49153\r
4014a=candidate:3 2 UDP 1692467184 192.168.1.1 49153 typ host\r
4015a=candidate:4 1 UDP 100 2001:db8::1 49152 typ host\r
4016a=candidate:5 1 UDP 50 192.168.1.1 49154 typ prflx\r
4017a=candidate:6 1 UDP 25 192.168.1.1 49155 typ relay raddr 10.0.0.1 rport 49156\r
4018a=candidate:7 1 UDP 10 192.168.1.1 49157 typ unknown_type\r
4019";
4020
4021        let session = Session::parse(sdp.as_bytes()).unwrap();
4022        let candidates: Vec<Candidate> =
4023            fallible_iterator::convert(session.medias[0].attributes_typed::<Candidate>())
4024                .collect::<Vec<_>>()
4025                .expect("Valid vector of candidates");
4026
4027        assert_eq!(candidates.len(), 7);
4028
4029        assert_eq!(candidates[0].foundation, "1");
4030        assert_eq!(candidates[0].component_id, 1);
4031        assert_eq!(
4032            candidates[0].address,
4033            CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
4034        );
4035        assert_eq!(candidates[0].port, 49152);
4036        assert_eq!(candidates[0].typ, CandidateType::Host);
4037        assert_eq!(
4038            candidates[0].rel_addr,
4039            Some(IpAddr::V4(Ipv4Addr::new(10, 0, 1, 1)))
4040        );
4041        assert_eq!(candidates[0].rel_port, Some(49153));
4042        assert_eq!(
4043            candidates[0].extensions,
4044            vec![("generation".to_string(), "0".to_string())]
4045        );
4046
4047        assert_eq!(candidates[1].foundation, "2");
4048        assert_eq!(candidates[1].typ, CandidateType::Srflx);
4049        assert_eq!(
4050            candidates[1].rel_addr,
4051            Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
4052        );
4053        assert_eq!(candidates[1].rel_port, Some(49153));
4054
4055        assert_eq!(candidates[2].foundation, "3");
4056        assert_eq!(candidates[2].component_id, 2);
4057        assert_eq!(candidates[2].typ, CandidateType::Host);
4058
4059        assert_eq!(candidates[3].foundation, "4");
4060        assert_eq!(
4061            candidates[3].address,
4062            CandidateAddress::IpAddr(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)))
4063        );
4064        assert_eq!(candidates[3].typ, CandidateType::Host);
4065
4066        assert_eq!(candidates[4].foundation, "5");
4067        assert_eq!(candidates[4].typ, CandidateType::Prflx);
4068
4069        assert_eq!(candidates[5].foundation, "6");
4070        assert_eq!(candidates[5].typ, CandidateType::Relay);
4071
4072        assert_eq!(candidates[6].foundation, "7");
4073        assert_eq!(
4074            candidates[6].typ,
4075            CandidateType::Other("unknown_type".to_string())
4076        );
4077    }
4078
4079    #[test]
4080    fn write_candidate() {
4081        use std::net::Ipv4Addr;
4082
4083        let candidate = Candidate {
4084            foundation: "abcd/1234".into(),
4085            component_id: 1,
4086            transport: "UDP".into(),
4087            priority: 2130706432,
4088            address: CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
4089            port: 49152,
4090            typ: CandidateType::Srflx,
4091            rel_addr: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
4092            rel_port: Some(49153),
4093            extensions: vec![("tcptype".to_string(), "active".to_string())],
4094        };
4095
4096        assert_eq!(
4097            candidate.to_string(),
4098            "abcd/1234 1 UDP 2130706432 192.168.0.1 49152 typ srflx raddr 10.0.0.1 rport 49153 tcptype active"
4099        );
4100    }
4101
4102    #[test]
4103    fn test_attribute_errors() {
4104        // Test RtpMap error paths
4105        assert_eq!(
4106            "99".parse::<RtpMap>().unwrap_err(),
4107            AttributeError::UnsupportedFormat {
4108                val: "99".to_string(),
4109                attr: "rtpmap".to_string()
4110            }
4111        );
4112        assert_eq!(
4113            "abc 90000".parse::<RtpMap>().unwrap_err(),
4114            AttributeError::InvalidParamValue {
4115                param: "Payload type".to_string(),
4116                val: "abc".to_string(),
4117                attr: "rtpmap".to_string()
4118            }
4119        );
4120        assert_eq!(
4121            "200 enc/90000".parse::<RtpMap>().unwrap_err(),
4122            AttributeError::InvalidParamValue {
4123                param: "Payload type".to_string(),
4124                val: "200(expected 0-127)".to_string(),
4125                attr: "rtpmap".to_string()
4126            }
4127        );
4128        assert_eq!(
4129            "99 ".parse::<RtpMap>().unwrap_err(),
4130            AttributeError::ParamNotFound {
4131                param: "Clock rate".to_string(),
4132                attr: "rtpmap".to_string()
4133            }
4134        );
4135        assert_eq!(
4136            "99 /".parse::<RtpMap>().unwrap_err(),
4137            AttributeError::InvalidParamValue {
4138                param: "Clock rate".to_string(),
4139                val: "".to_string(),
4140                attr: "rtpmap".to_string()
4141            }
4142        );
4143
4144        assert_eq!(
4145            "invalid".parse::<Fmtp>().unwrap_err(),
4146            AttributeError::UnsupportedFormat {
4147                val: "invalid".to_string(),
4148                attr: "fmtp".to_string()
4149            }
4150        );
4151        assert_eq!(
4152            "abc profile=1".parse::<Fmtp>().unwrap_err(),
4153            AttributeError::InvalidParamValue {
4154                param: "fmtp".to_string(),
4155                val: "abc".to_string(),
4156                attr: "fmtp".to_string()
4157            }
4158        );
4159
4160        // Test Rtcp error paths
4161        assert_eq!(
4162            "".parse::<Rtcp>().unwrap_err(),
4163            AttributeError::ParamNotFound {
4164                param: "Port".to_string(),
4165                attr: "rtcp".to_string()
4166            }
4167        );
4168        assert_eq!(
4169            "abc IN IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4170            AttributeError::InvalidParamValue {
4171                param: "Port".to_string(),
4172                val: "abc".to_string(),
4173                attr: "rtcp".to_string()
4174            }
4175        );
4176
4177        // Test Fingerprint error paths
4178        assert_eq!(
4179            "".parse::<Fingerprint>().unwrap_err(),
4180            AttributeError::ParamNotFound {
4181                param: "Hash value".to_string(),
4182                attr: "fingerprint".to_string()
4183            }
4184        );
4185        assert_eq!(
4186            "SHA-1".parse::<Fingerprint>().unwrap_err(),
4187            AttributeError::ParamNotFound {
4188                param: "Hash value".to_string(),
4189                attr: "fingerprint".to_string()
4190            }
4191        );
4192
4193        // Test Candidate error paths
4194        assert_eq!(
4195            "".parse::<Candidate>().unwrap_err(),
4196            AttributeError::ParamNotFound {
4197                param: "Component id".to_string(),
4198                attr: "candidate".to_string()
4199            }
4200        );
4201        assert_eq!(
4202            "1 1 UDP 100".parse::<Candidate>().unwrap_err(),
4203            AttributeError::ParamNotFound {
4204                param: "Address".to_string(),
4205                attr: "candidate".to_string()
4206            }
4207        );
4208
4209        // Test ExtMap error paths
4210        assert_eq!(
4211            "".parse::<ExtMap>().unwrap_err(),
4212            AttributeError::InvalidParamValue {
4213                param: "Id".to_string(),
4214                val: "".to_string(),
4215                attr: "extmap".to_string()
4216            }
4217        );
4218        assert_eq!(
4219            "999999 http://example.com".parse::<ExtMap>().unwrap_err(),
4220            AttributeError::InvalidParamValue {
4221                param: "Id".to_string(),
4222                val: "999999".to_string(),
4223                attr: "extmap".to_string()
4224            }
4225        );
4226
4227        // Test Group error paths
4228        assert_eq!(
4229            "".parse::<Group>().unwrap_err(),
4230            AttributeError::ParamNotFound {
4231                param: "Media identification tags".to_string(),
4232                attr: "group".to_string()
4233            }
4234        );
4235        assert_eq!(
4236            "LS".parse::<Group>().unwrap_err(),
4237            AttributeError::ParamNotFound {
4238                param: "Media identification tags".to_string(),
4239                attr: "group".to_string()
4240            }
4241        );
4242
4243        // Test Ssrc error paths
4244        assert_eq!(
4245            "".parse::<Ssrc>().unwrap_err(),
4246            AttributeError::ParamNotFound {
4247                param: "Ssrc id".to_string(),
4248                attr: "ssrc".to_string()
4249            }
4250        );
4251        assert_eq!(
4252            "abc".parse::<Ssrc>().unwrap_err(),
4253            AttributeError::ParamNotFound {
4254                param: "Ssrc id".to_string(),
4255                attr: "ssrc".to_string()
4256            }
4257        );
4258
4259        // Test Setup error paths
4260        let setup_err = "foo".parse::<Setup>().err().unwrap();
4261        assert!(matches!(setup_err, AttributeError::Other { .. }));
4262        assert_eq!(format!("{}", setup_err), "setup: Invalid Setup value foo");
4263
4264        // Test Crypto error paths
4265        assert_eq!(
4266            "".parse::<Crypto>().unwrap_err(),
4267            AttributeError::InvalidParamValue {
4268                param: "Tag".to_string(),
4269                val: "".to_string(),
4270                attr: "crypto".to_string()
4271            }
4272        );
4273        assert_eq!(
4274            "abc AES_CM_128_HMAC_SHA1_32 inline:key"
4275                .parse::<Crypto>()
4276                .unwrap_err(),
4277            AttributeError::InvalidParamValue {
4278                param: "Tag".to_string(),
4279                val: "abc".to_string(),
4280                attr: "crypto".to_string()
4281            }
4282        );
4283
4284        // Test RtcpFb error paths
4285        assert_eq!(
4286            "".parse::<RtcpFb>().unwrap_err(),
4287            AttributeError::InvalidParamValue {
4288                param: "Payload format".to_string(),
4289                val: "".to_string(),
4290                attr: "rtcp-fb".to_string()
4291            }
4292        );
4293        assert_eq!(
4294            "*".parse::<RtcpFb>().unwrap_err(),
4295            AttributeError::ParamNotFound {
4296                param: "Rtcp feedback value".to_string(),
4297                attr: "rtcp-fb".to_string()
4298            }
4299        );
4300        assert_eq!(
4301            "1 ack ccfb".parse::<RtcpFb>().unwrap_err(),
4302            AttributeError::InvalidParamValue {
4303                param: "Payload type of Congestion control feedback (ccfb)".to_string(),
4304                val: "1(expected wildcard (*))".to_string(),
4305                attr: "rtcp-fb".to_string()
4306            }
4307        );
4308
4309        // Test attribute_typed with missing value
4310        let media = Media {
4311            media: "video".into(),
4312            port: 1234,
4313            num_ports: None,
4314            proto: "RTP/SAVPF".into(),
4315            fmt: "".into(),
4316            media_title: None,
4317            connections: vec![],
4318            bandwidths: vec![],
4319            key: None,
4320            attributes: vec![Attribute {
4321                attribute: "rtpmap".into(),
4322                value: None,
4323            }],
4324        };
4325        assert_eq!(
4326            media
4327                .attributes_typed::<RtpMap>()
4328                .collect::<Vec<Result<RtpMap, AttributeError>>>()
4329                .remove(0)
4330                .unwrap_err(),
4331            AttributeError::UnsupportedFormat {
4332                val: String::new(),
4333                attr: "rtpmap".to_string()
4334            }
4335        );
4336    }
4337
4338    #[test]
4339    #[allow(deprecated)]
4340    fn parse_rtcp_address() {
4341        let sdp = "v=0\r
4342o=alice 3203093520 3203093520 IN IP4 host.example.com\r
4343s=parse rtcp attribute address test\r
4344a=rtcp:5000 IN IP4 127.0.0.1\r
4345a=rtcp:5000 IN IP4 127.0.0.0/24\r
4346a=rtcp:5000 IN IP6 ::1\r
4347a=rtcp:5000 IN NONIP non-IP\r
4348a=rtcp:53020\r
4349";
4350
4351        let parsed = Session::parse(sdp.as_bytes()).unwrap();
4352        let mut rtcp_attr_iter = parsed.attributes_typed::<Rtcp>();
4353
4354        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4355        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
4356        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.1"));
4357        assert_eq!(
4358            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4359            IpAddr::V4(Ipv4Addr::LOCALHOST),
4360        );
4361
4362        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4363        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
4364        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.0/24"));
4365        // The `/24` suffix is parsed as the TTL of RFC 8866 multicast
4366        // notation, not as part of the address, see [`Rtcp::try_address`].
4367        assert_eq!(
4368            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4369            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 0)),
4370        );
4371        let address = rtcp.try_address().unwrap().unwrap();
4372        assert_eq!(address.ttl, Some(24));
4373        assert_eq!(address.count, None);
4374        assert!(!rtcp.is_multicast());
4375
4376        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4377        assert_eq!(rtcp.addrtype, Some(AddrType::Ip6));
4378        assert_eq!(rtcp.connection_address.as_deref(), Some("::1"));
4379        assert_eq!(
4380            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4381            IpAddr::V6(Ipv6Addr::LOCALHOST),
4382        );
4383
4384        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4385        assert_eq!(rtcp.addrtype, Some(AddrType::Other("NONIP".to_string())));
4386        assert_eq!(rtcp.connection_address.as_deref(), Some("non-IP"));
4387        assert_eq!(
4388            rtcp.try_parse_connection_ip_address().unwrap().unwrap_err(),
4389            "non-IP",
4390        );
4391
4392        // RFC 3605: the address part is optional.
4393        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4394        assert_eq!(rtcp.port, 53020);
4395        assert_eq!(rtcp.nettype, None);
4396        assert_eq!(rtcp.addrtype, None);
4397        assert_eq!(rtcp.connection_address, None);
4398        assert_eq!(rtcp.try_parse_connection_ip_address(), None);
4399        assert_eq!(rtcp.try_address(), None);
4400        assert!(!rtcp.is_multicast());
4401        assert_eq!(rtcp.to_string(), "53020");
4402    }
4403
4404    #[test]
4405    fn rtcp_multicast_address_helpers() {
4406        // RFC 3605: a=rtcp reuses the SDP connection-address token, so the
4407        // multicast `address/ttl[/count]` notation of RFC 8866, section 5.7
4408        // may also be used here. Parsing the notation itself is covered by
4409        // `connection_try_address`, here only its use through `a=rtcp` and
4410        // the address family inference that is specific to `a=rtcp`.
4411        let rtcp = "5000 IN IP4 233.252.0.1/127/3".parse::<Rtcp>().unwrap();
4412        assert!(rtcp.is_multicast());
4413        assert_eq!(
4414            rtcp.try_address().unwrap().unwrap(),
4415            ConnectionAddress {
4416                ip: Ipv4Addr::new(233, 252, 0, 1).into(),
4417                ttl: Some(127),
4418                count: Some(3),
4419            }
4420        );
4421
4422        // Without `addrtype` it is inferred from the address: IPv4 gets a
4423        // TTL, IPv6 the address count.
4424        let mut rtcp = Rtcp::with_port(5000);
4425        rtcp.connection_address = Some("224.2.1.1/16".to_string());
4426        let address = rtcp.try_address().unwrap().unwrap();
4427        assert_eq!(address.ttl, Some(16));
4428        assert_eq!(address.count, None);
4429
4430        let mut rtcp = Rtcp::with_port(5000);
4431        rtcp.connection_address = Some("ff12::5/3".to_string());
4432        let address = rtcp.try_address().unwrap().unwrap();
4433        assert_eq!(address.ttl, None);
4434        assert_eq!(address.count, Some(3));
4435
4436        let rtcp = "5000 IN IP6 ff12::5/3".parse::<Rtcp>().unwrap();
4437        assert!(rtcp.is_ipv6());
4438        assert!(!rtcp.is_ipv4());
4439        assert_eq!(rtcp.try_is_multicast(), Some(Ok(true)));
4440
4441        // Without the address part the multicast and family helpers report
4442        // `None`/`false`, `a=rtcp` only carries the port in that case.
4443        let rtcp = Rtcp::with_port(5000);
4444        assert_eq!(rtcp.try_is_multicast(), None);
4445        assert!(!rtcp.is_multicast());
4446        assert!(!rtcp.is_ipv4());
4447        assert!(!rtcp.is_ipv6());
4448
4449        // FQDN addresses are not parseable as `IpAddr`.
4450        let rtcp = "5000 IN IP4 rtcp.example.com".parse::<Rtcp>().unwrap();
4451        assert!(!rtcp.is_multicast());
4452        assert!(!rtcp.is_ipv4());
4453        assert_eq!(
4454            rtcp.try_is_multicast(),
4455            Some(Err(ConnectionAddressError::InvalidAddress(
4456                "rtcp.example.com".to_string()
4457            )))
4458        );
4459    }
4460
4461    #[test]
4462    fn parse_rtcp_attribute_whitespace() {
4463        // Runs of spaces are a single separator, like in o= and c= lines,
4464        // and empty fields are rejected instead of ending up as e.g. an
4465        // empty `nettype` or `addrtype`.
4466        assert_eq!(
4467            "6000  IN IP4 127.0.0.1".parse::<Rtcp>().unwrap(),
4468            Rtcp::new(6000, NetType::In, AddrType::Ip4, "127.0.0.1")
4469        );
4470        assert_eq!(
4471            "6001 IN  IP4 127.0.0.1 ".parse::<Rtcp>().unwrap(),
4472            Rtcp::new(6001, NetType::In, AddrType::Ip4, "127.0.0.1")
4473        );
4474        assert_eq!("53020 ".parse::<Rtcp>().unwrap(), Rtcp::with_port(53020));
4475
4476        assert!("6002 IN  127.0.0.1".parse::<Rtcp>().is_err());
4477        assert!("6003 IN IP4 127.0.0.1 extra".parse::<Rtcp>().is_err());
4478    }
4479
4480    #[test]
4481    fn parse_rtcp_nettype_and_addrtype_must_be_tokens() {
4482        // RFC 3605 reuses the SDP nettype and addrtype tokens (RFC 8866,
4483        // section 9), so reject other values like the o= and c= parsers do.
4484        assert_eq!(
4485            "6000 a<b IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4486            AttributeError::InvalidParamValue {
4487                param: "Network type".to_string(),
4488                val: "a<b".to_string(),
4489                attr: "rtcp".to_string(),
4490            }
4491        );
4492        assert_eq!(
4493            "6000 IN IP4/x 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4494            AttributeError::InvalidParamValue {
4495                param: "Address type".to_string(),
4496                val: "IP4/x".to_string(),
4497                attr: "rtcp".to_string(),
4498            }
4499        );
4500    }
4501
4502    #[test]
4503    fn parse_rid_attribute() {
4504        // RFC 8851 examples.
4505        let rid: Rid = "5 send pt=99,102;max-br=64000".parse().unwrap();
4506        assert_eq!(
4507            rid,
4508            Rid::with_params("5", RidDirection::Send, ["pt=99,102", "max-br=64000"])
4509        );
4510        assert_eq!(rid.to_string(), "5 send pt=99,102;max-br=64000");
4511
4512        let rid: Rid = "6 send pt=100,97,101,102".parse().unwrap();
4513        assert_eq!(
4514            rid,
4515            Rid::with_params("6", RidDirection::Send, ["pt=100,97,101,102"])
4516        );
4517
4518        let rid: Rid = "f recv max-br=19.5;apt=100".parse().unwrap();
4519        assert_eq!(
4520            rid,
4521            Rid::with_params("f", RidDirection::Recv, ["max-br=19.5", "apt=100"])
4522        );
4523        assert_eq!(rid.to_string(), "f recv max-br=19.5;apt=100");
4524
4525        // Without parameters, and with a valueless parameter.
4526        let rid: Rid = "1 recv".parse().unwrap();
4527        assert_eq!(rid.direction, RidDirection::Recv);
4528        assert!(rid.params.is_empty());
4529        assert_eq!(rid.to_string(), "1 recv");
4530        assert_eq!(
4531            "2 send max-width".parse::<Rid>().unwrap(),
4532            Rid::with_params("2", RidDirection::Send, ["max-width"])
4533        );
4534
4535        // Trailing separators are tolerated, direction is case-insensitive.
4536        assert_eq!(
4537            "2 SEND pt=99;".parse::<Rid>().unwrap(),
4538            Rid::with_params("2", RidDirection::Send, ["pt=99"])
4539        );
4540
4541        for invalid in [
4542            "",
4543            "1",
4544            " send",
4545            // The ID is restricted to alphanumeric, "-" and "_"
4546            // (RFC 8851, section 10):
4547            "1! send",
4548            "id:1 recv",
4549            "1 up",
4550            "1 send x y",
4551            "1 send pt=1;max br=2",
4552        ] {
4553            assert!(
4554                invalid.parse::<Rid>().is_err(),
4555                "expected error for {:?}",
4556                invalid
4557            );
4558        }
4559    }
4560
4561    #[test]
4562    fn parse_key_mgmt_attribute() {
4563        let key_mgmt: KeyMgmt = "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0".parse().unwrap();
4564        assert_eq!(
4565            key_mgmt,
4566            KeyMgmt::with_data(KeyMgmtMethod::Mikey, "AAAAIklkAAAAdGV4dHRleHR0ZXh0")
4567        );
4568        assert_eq!(key_mgmt.to_string(), "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0");
4569
4570        // Unknown methods are kept verbatim; KMPIDs are case-sensitive
4571        // (RFC 4567 section 3), so `MIKEY` is not the `mikey` method.
4572        for method in ["rawurl", "MIKEY", "internal"] {
4573            let key_mgmt: KeyMgmt = format!("{} data", method).parse().unwrap();
4574            assert_eq!(key_mgmt.method, KeyMgmtMethod::Other(method.to_string()));
4575            assert_eq!(key_mgmt.data.as_deref(), Some("data"));
4576            assert_eq!(key_mgmt.to_string(), format!("{} data", method));
4577        }
4578
4579        // Method without data.
4580        let key_mgmt: KeyMgmt = "internal".parse().unwrap();
4581        assert_eq!(
4582            key_mgmt,
4583            KeyMgmt::new(KeyMgmtMethod::Other("internal".to_string()))
4584        );
4585        assert_eq!(key_mgmt.to_string(), "internal");
4586
4587        // Data is kept verbatim, including inner whitespace.
4588        let key_mgmt: KeyMgmt = "mikey bar  baz".parse().unwrap();
4589        assert_eq!(key_mgmt.data.as_deref(), Some("bar  baz"));
4590        assert_eq!(key_mgmt.to_string(), "mikey bar  baz");
4591
4592        assert!("".parse::<KeyMgmt>().is_err());
4593        assert!("   ".parse::<KeyMgmt>().is_err());
4594        // The method is restricted to alphanumeric (RFC 4567, section 3):
4595        assert!("mi!key data".parse::<KeyMgmt>().is_err());
4596        assert!("-mikey".parse::<KeyMgmt>().is_err());
4597    }
4598
4599    #[test]
4600    fn parse_remote_candidate_attribute() {
4601        let candidates: RemoteCandidates = "1 192.168.1.1 50000".parse().unwrap();
4602        assert_eq!(
4603            candidates,
4604            RemoteCandidates::new([RemoteCandidate::new(
4605                1,
4606                "192.168.1.1".parse::<IpAddr>().unwrap(),
4607                50000
4608            )])
4609        );
4610        assert_eq!(candidates.to_string(), "1 192.168.1.1 50000");
4611
4612        // Multiple entries in a single attribute (RFC 8839, section 5.2).
4613        let candidates: RemoteCandidates = "1 192.0.2.3 45664 2 192.0.2.3 45665".parse().unwrap();
4614        assert_eq!(candidates.0.len(), 2);
4615        assert_eq!(candidates.0[1].component_id, 2);
4616        assert_eq!(
4617            candidates.to_string(),
4618            "1 192.0.2.3 45664 2 192.0.2.3 45665"
4619        );
4620
4621        // IPv6 and the value parsed on its own.
4622        let candidate: RemoteCandidate = "1 2001:db8::1 9".parse().unwrap();
4623        assert_eq!(candidate.address, "2001:db8::1".parse::<IpAddr>().unwrap());
4624        assert_eq!(candidate.to_string(), "1 2001:db8::1 9");
4625
4626        for invalid in [
4627            "",
4628            "1",
4629            "1 10.0.0.1",
4630            "x 10.0.0.1 50000",
4631            "1 x 50000",
4632            // FQDNs must not be used (RFC 8839, section 5.1).
4633            "1 example.com 50000",
4634            "1 10.0.0.1 70000",
4635            "1 10.0.0.1 50000 2",
4636        ] {
4637            assert!(
4638                invalid.parse::<RemoteCandidates>().is_err(),
4639                "expected error for {:?}",
4640                invalid
4641            );
4642        }
4643    }
4644
4645    #[test]
4646    fn parse_source_filter_attribute() {
4647        // RFC 4570, section 3.2 examples.
4648        let filter: SourceFilter = "incl IN IP4 232.3.4.5 192.0.2.10".parse().unwrap();
4649        assert_eq!(filter.mode, SourceFilterMode::Incl);
4650        assert_eq!(filter.nettype, NetType::In);
4651        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip4);
4652        assert_eq!(
4653            filter.destination,
4654            SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
4655                "232.3.4.5".parse().unwrap()
4656            ))
4657        );
4658        assert_eq!(
4659            filter.sources,
4660            ["192.0.2.10".parse::<SourceFilterAddress>().unwrap()]
4661        );
4662        assert_eq!(filter.to_string(), "incl IN IP4 232.3.4.5 192.0.2.10");
4663
4664        // Unicast destination.
4665        let filter: SourceFilter = "excl IN IP4 192.0.2.11 192.0.2.10".parse().unwrap();
4666        assert_eq!(filter.mode, SourceFilterMode::Excl);
4667        assert_eq!(filter.to_string(), "excl IN IP4 192.0.2.11 192.0.2.10");
4668
4669        // Destination wildcard.
4670        let filter: SourceFilter = "incl IN IP4 * 192.0.2.10".parse().unwrap();
4671        assert_eq!(filter.destination, SourceFilterDestination::Wildcard);
4672        assert_eq!(filter.to_string(), "incl IN IP4 * 192.0.2.10");
4673
4674        // Multiple space-separated sources.
4675        let filter: SourceFilter = "incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
4676            .parse()
4677            .unwrap();
4678        assert_eq!(filter.sources.len(), 2);
4679        assert_eq!(
4680            filter.to_string(),
4681            "incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
4682        );
4683
4684        // IPv6.
4685        let filter: SourceFilter = "incl IN IP6 ff0e::11a 2001:db8::1".parse().unwrap();
4686        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
4687        assert_eq!(
4688            filter.destination,
4689            SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
4690                "ff0e::11a".parse().unwrap()
4691            ))
4692        );
4693        assert_eq!(filter.to_string(), "incl IN IP6 ff0e::11a 2001:db8::1");
4694
4695        // FQDNs with the "*" address type.
4696        let filter: SourceFilter = "incl IN * channel-1.example.com src-1.example.com"
4697            .parse()
4698            .unwrap();
4699        assert_eq!(filter.addrtype, SourceFilterAddrType::Any);
4700        assert_eq!(
4701            filter.destination,
4702            SourceFilterDestination::Address(SourceFilterAddress::FQDN(
4703                "channel-1.example.com".to_string()
4704            ))
4705        );
4706        assert_eq!(
4707            filter.sources,
4708            [SourceFilterAddress::FQDN("src-1.example.com".to_string())]
4709        );
4710        assert_eq!(
4711            filter.to_string(),
4712            "incl IN * channel-1.example.com src-1.example.com"
4713        );
4714
4715        // Filter mode and address type matching is case-insensitive.
4716        let filter: SourceFilter = "EXCL IN ip6 ff0e::11a 2001:db8::1".parse().unwrap();
4717        assert_eq!(filter.mode, SourceFilterMode::Excl);
4718        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
4719
4720        for invalid in [
4721            "",
4722            "incl",
4723            "incl IN",
4724            "incl IN IP4",
4725            "incl IN IP4 232.3.4.5",
4726            // No /ttl or /number of addresses suffixes on the destination
4727            // (RFC 4570 section 3.1):
4728            "incl IN IP4 232.3.4.5/127 192.0.2.10",
4729            // The "*" address type only with FQDN destinations (section 3.1):
4730            "incl IN * 232.3.4.5 192.0.2.10",
4731            // The "*" wildcard is not allowed in the source list (appendix A):
4732            "incl IN IP4 232.3.4.5 *",
4733            "unknown IN IP4 232.3.4.5 192.0.2.10",
4734            // The network type is a token (RFC 4570 appendix A):
4735            "incl IN(IP4 232.3.4.5 192.0.2.10",
4736            "incl IN IP8 232.3.4.5 192.0.2.10",
4737        ] {
4738            assert!(
4739                invalid.parse::<SourceFilter>().is_err(),
4740                "expected error for {:?}",
4741                invalid
4742            );
4743        }
4744    }
4745
4746    #[test]
4747    fn parse_mid_attribute() {
4748        let mid: Mid = "1".parse().unwrap();
4749        assert_eq!(mid, Mid("1".to_string()));
4750        assert_eq!(mid.to_string(), "1");
4751
4752        let attribute: Attribute = mid.clone().into();
4753        assert_eq!(attribute.attribute, "mid");
4754        assert_eq!(attribute.value.as_deref(), Some("1"));
4755
4756        // Media descriptions commonly use opaque, browser-style tags.
4757        let mid: Mid = "v2R1d1".parse().unwrap();
4758        assert_eq!(mid.to_string(), "v2R1d1");
4759
4760        for invalid in [
4761            "", " ", // The identification tag is a token (RFC 8866, section 5).
4762            "1 2", "a:b",
4763        ] {
4764            assert!(
4765                invalid.parse::<Mid>().is_err(),
4766                "expected error for {:?}",
4767                invalid
4768            );
4769        }
4770    }
4771
4772    #[test]
4773    fn parse_rtcp_mux_attribute() {
4774        assert_eq!("".parse::<RtcpMux>().unwrap(), RtcpMux);
4775
4776        let attribute: Attribute = RtcpMux.into();
4777        assert_eq!(attribute.attribute, "rtcp-mux");
4778        assert_eq!(attribute.value, None);
4779
4780        // A property attribute must not carry a value.
4781        assert!("1".parse::<RtcpMux>().is_err());
4782
4783        let sdp = "v=0\r
4784o=alice 3203093520 3203093520 IN IP4 host.example.com\r
4785s=Property attribute test\r
4786t=3203130148 3203137348\r
4787m=audio 49170 RTP/AVP 0\r
4788a=rtcp-mux\r
4789";
4790        let parsed = Session::parse(sdp.as_bytes()).unwrap();
4791        assert_eq!(
4792            parsed.medias[0].get_first_attribute_typed::<RtcpMux>(),
4793            Some(Ok(RtcpMux))
4794        );
4795    }
4796
4797    #[test]
4798    fn parse_rtcp_mux_only_attribute() {
4799        assert!(matches!("".parse::<RtcpMuxOnly>(), Ok(RtcpMuxOnly)));
4800
4801        let attribute: Attribute = RtcpMuxOnly.into();
4802        assert_eq!(attribute.attribute, "rtcp-mux-only");
4803        assert_eq!(attribute.value, None);
4804
4805        // A property attribute must not carry a value.
4806        assert!("1".parse::<RtcpMuxOnly>().is_err());
4807    }
4808
4809    #[test]
4810    fn parse_rtcp_rsize_attribute() {
4811        assert!(matches!("".parse::<RtcpRsize>(), Ok(RtcpRsize)));
4812
4813        let attribute: Attribute = RtcpRsize.into();
4814        assert_eq!(attribute.attribute, "rtcp-rsize");
4815        assert_eq!(attribute.value, None);
4816
4817        // A property attribute must not carry a value.
4818        assert!("1".parse::<RtcpRsize>().is_err());
4819    }
4820
4821    #[test]
4822    fn parse_ice_lite_attribute() {
4823        assert!(matches!("".parse::<IceLite>(), Ok(IceLite)));
4824
4825        let attribute: Attribute = IceLite.into();
4826        assert_eq!(attribute.attribute, "ice-lite");
4827        assert_eq!(attribute.value, None);
4828
4829        // A property attribute must not carry a value.
4830        assert!("1".parse::<IceLite>().is_err());
4831    }
4832
4833    #[test]
4834    fn parse_ice_ufrag_attribute() {
4835        let ufrag: IceUfrag = "EpGh".parse().unwrap();
4836        assert_eq!(ufrag, IceUfrag("EpGh".to_string()));
4837        assert_eq!(ufrag.to_string(), "EpGh");
4838
4839        let attribute: Attribute = ufrag.into();
4840        assert_eq!(attribute.attribute, "ice-ufrag");
4841        assert_eq!(attribute.value.as_deref(), Some("EpGh"));
4842
4843        // All ICE characters are allowed, including "+" and "/" (RFC 5245,
4844        // section 15.1).
4845        assert_eq!("a+Z/9".parse::<IceUfrag>().unwrap().to_string(), "a+Z/9");
4846
4847        // The 256 characters upper bound.
4848        assert!("a".repeat(256).parse::<IceUfrag>().is_ok());
4849
4850        let too_long = "a".repeat(257);
4851        for invalid in [
4852            "",
4853            "abc",             // shorter than 4 characters
4854            too_long.as_str(), // longer than 256 characters
4855            "Ep-Gh",           // "-" is not an ICE character
4856            "Ep Gh",           // neither is the space
4857            "\u{30b0}pGh",     // non-ASCII
4858        ] {
4859            assert!(
4860                invalid.parse::<IceUfrag>().is_err(),
4861                "expected error for {:?}",
4862                invalid
4863            );
4864        }
4865    }
4866
4867    #[test]
4868    fn parse_ice_pwd_attribute() {
4869        let password = "aq1s2dq3w4er5ty6ui7p8o9i0u";
4870        let pwd: IcePwd = password.parse().unwrap();
4871        assert_eq!(pwd, IcePwd(password.to_string()));
4872        assert_eq!(pwd.to_string(), password);
4873
4874        let attribute: Attribute = pwd.into();
4875        assert_eq!(attribute.attribute, "ice-pwd");
4876        assert_eq!(attribute.value.as_deref(), Some(password));
4877
4878        // The shortest and longest valid passwords (RFC 5245, section 15.4).
4879        let shortest = "a".repeat(22);
4880        assert_eq!(shortest.parse::<IcePwd>().unwrap().to_string(), shortest);
4881        assert!("a".repeat(256).parse::<IcePwd>().is_ok());
4882
4883        let too_long = "a".repeat(257);
4884        for invalid in [
4885            "",
4886            "a".repeat(21).as_str(),          // shorter than 22 characters
4887            too_long.as_str(),                // longer than 256 characters
4888            "abcdefghijklmnopqr-stu",         // "-" is not an ICE character
4889            "\u{30b0}bcdefghijklmnopqrstuvw", // non-ASCII
4890        ] {
4891            assert!(
4892                invalid.parse::<IcePwd>().is_err(),
4893                "expected error for {:?}",
4894                invalid
4895            );
4896        }
4897    }
4898
4899    #[test]
4900    fn parse_ice_options_attribute() {
4901        let options: IceOptions = "trickle".parse().unwrap();
4902        assert_eq!(options, IceOptions::new(["trickle"]));
4903        assert_eq!(options.to_string(), "trickle");
4904
4905        // Multiple tags in a single attribute (RFC 5245, section 15.5).
4906        let options: IceOptions = "trickle renomination".parse().unwrap();
4907        assert_eq!(options, IceOptions::new(["trickle", "renomination"]));
4908        assert_eq!(options.to_string(), "trickle renomination");
4909
4910        let attribute: Attribute = options.into();
4911        assert_eq!(attribute.attribute, "ice-options");
4912        assert_eq!(attribute.value.as_deref(), Some("trickle renomination"));
4913
4914        for invalid in ["", "   ", "tr-ickle"] {
4915            assert!(
4916                invalid.parse::<IceOptions>().is_err(),
4917                "expected error for {:?}",
4918                invalid
4919            );
4920        }
4921    }
4922
4923    #[test]
4924    fn parse_end_of_candidates_attribute() {
4925        assert!(matches!("".parse::<EndOfCandidates>(), Ok(EndOfCandidates)));
4926
4927        let attribute: Attribute = EndOfCandidates.into();
4928        assert_eq!(attribute.attribute, "end-of-candidates");
4929        assert_eq!(attribute.value, None);
4930
4931        // A property attribute must not carry a value.
4932        assert!("1".parse::<EndOfCandidates>().is_err());
4933    }
4934
4935    #[test]
4936    fn parse_msid_attribute() {
4937        // RFC 8830, section 2 example.
4938        let msid: Msid = "examplefoo examplebar".parse().unwrap();
4939        assert_eq!(msid, Msid::new("examplefoo", Some("examplebar")));
4940        assert_eq!(msid.to_string(), "examplefoo examplebar");
4941
4942        let attribute: Attribute = msid.into();
4943        assert_eq!(attribute.attribute, "msid");
4944        assert_eq!(attribute.value.as_deref(), Some("examplefoo examplebar"));
4945
4946        // The appdata is optional; "-" means "no MediaStream" and is a valid
4947        // token (RFC 8830, section 3).
4948        let msid: Msid = "-".parse().unwrap();
4949        assert_eq!(msid, Msid::new("-", None::<&str>));
4950        assert_eq!(msid.to_string(), "-");
4951
4952        let too_long = "a".repeat(65);
4953        for invalid in [
4954            "",
4955            too_long.as_str(), // longer than 64 characters
4956            "foo \u{fffd}",    // non-token character
4957            "foo bar baz",     // more than two fields
4958        ] {
4959            assert!(
4960                invalid.parse::<Msid>().is_err(),
4961                "expected error for {:?}",
4962                invalid
4963            );
4964        }
4965    }
4966
4967    #[test]
4968    fn parse_sctp_port_attribute() {
4969        // RFC 8841, section 5.2 example.
4970        let port: SctpPort = "5000".parse().unwrap();
4971        assert_eq!(port, SctpPort(5000));
4972        assert_eq!(port.to_string(), "5000");
4973
4974        let attribute: Attribute = port.into();
4975        assert_eq!(attribute.attribute, "sctp-port");
4976        assert_eq!(attribute.value.as_deref(), Some("5000"));
4977
4978        // The port range includes 0 and 65535 (RFC 8841, section 5.2).
4979        assert_eq!("0".parse::<SctpPort>().unwrap(), SctpPort(0));
4980        assert_eq!("65535".parse::<SctpPort>().unwrap(), SctpPort(65535));
4981
4982        for invalid in [
4983            "", "65536",  // out of range
4984            "99999",  // 5 digits but out of range
4985            "05000",  // leading zeroes must not be used
4986            "500000", // more than 5 digits
4987            "abc", "50a",
4988        ] {
4989            assert!(
4990                invalid.parse::<SctpPort>().is_err(),
4991                "expected error for {:?}",
4992                invalid
4993            );
4994        }
4995    }
4996
4997    #[test]
4998    fn parse_max_message_size_attribute() {
4999        // RFC 8841, section 6.2 example.
5000        let size: MaxMessageSize = "100000".parse().unwrap();
5001        assert_eq!(size, MaxMessageSize(100000));
5002        assert_eq!(size.to_string(), "100000");
5003
5004        let attribute: Attribute = size.into();
5005        assert_eq!(attribute.attribute, "max-message-size");
5006        assert_eq!(attribute.value.as_deref(), Some("100000"));
5007
5008        // Zero means messages of any size (RFC 8841, section 6.1).
5009        assert_eq!("0".parse::<MaxMessageSize>().unwrap(), MaxMessageSize(0));
5010
5011        let too_large = "1".repeat(21);
5012        for invalid in [
5013            "",
5014            "01", // leading zeroes must not be used
5015            "1_000",
5016            "abc",
5017            "10a",
5018            too_large.as_str(), // larger than u64::MAX
5019        ] {
5020            assert!(
5021                invalid.parse::<MaxMessageSize>().is_err(),
5022                "expected error for {:?}",
5023                invalid
5024            );
5025        }
5026    }
5027
5028    #[test]
5029    fn parse_sctpmap_attribute() {
5030        // Real-world legacy syntax.
5031        let map: SctpMap = "5000 webrtc-datachannel 256".parse().unwrap();
5032        assert_eq!(map, SctpMap::new(5000, "webrtc-datachannel", Some(256)));
5033        assert_eq!(map.to_string(), "5000 webrtc-datachannel 256");
5034
5035        let attribute: Attribute = map.into();
5036        assert_eq!(attribute.attribute, "sctpmap");
5037        assert_eq!(
5038            attribute.value.as_deref(),
5039            Some("5000 webrtc-datachannel 256")
5040        );
5041
5042        // The stream count is optional and zero is allowed.
5043        let map: SctpMap = "9 webrtc-datachannel".parse().unwrap();
5044        assert_eq!(map.streams, None);
5045        assert_eq!(map.to_string(), "9 webrtc-datachannel");
5046        assert_eq!("0 app 0".parse::<SctpMap>().unwrap().streams, Some(0));
5047
5048        for invalid in [
5049            "",
5050            "5000",               // application missing
5051            "65536 app",          // port out of range
5052            "05000 app",          // leading zeroes
5053            "app app",            // port not a number
5054            "5000 app 70000",     // stream count out of range
5055            "5000 a:pp",          // application not a token
5056            "5000 app 256 extra", // trailing item
5057        ] {
5058            assert!(
5059                invalid.parse::<SctpMap>().is_err(),
5060                "expected error for {:?}",
5061                invalid
5062            );
5063        }
5064    }
5065}