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///
1197/// The list of identification tags is optional in the grammar, so a group
1198/// without any tag is valid and simply groups no media description. Grouping
1199/// semantics that need at least one tag, such as BUNDLE (RFC 8843, section 5),
1200/// have to be rejected by the application.
1201///
1202/// The grammar separates the semantics and the tags with a single space, but
1203/// any whitespace is accepted when parsing and a single space is written back.
1204#[derive(Debug, Clone, PartialEq, Eq)]
1205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1206pub struct Group {
1207    pub semantics: GroupSemantics,
1208    pub mid_tags: Vec<String>,
1209}
1210
1211impl Group {
1212    pub fn new(semantics: GroupSemantics) -> Self {
1213        Group {
1214            semantics,
1215            mid_tags: vec![],
1216        }
1217    }
1218
1219    pub fn add_mid_tag(&mut self, mid_tag: impl ToString) {
1220        self.mid_tags.push(mid_tag.to_string())
1221    }
1222
1223    pub fn add_mid_tags(&mut self, mid_tags: impl IntoIterator<Item = impl ToString>) {
1224        self.mid_tags
1225            .extend(mid_tags.into_iter().map(|i| i.to_string()))
1226    }
1227}
1228
1229impl FromStr for Group {
1230    type Err = AttributeError;
1231
1232    fn from_str(s: &str) -> Result<Self, Self::Err> {
1233        // Runs of whitespace are a single separator, so that no tag can end up
1234        // empty. Identification tags are tokens, so they cannot contain
1235        // whitespace.
1236        let mut i = s.split_ascii_whitespace();
1237
1238        let Some(semantics) = i.next() else {
1239            return Err(AttributeError::ParamNotFound {
1240                param: "Semantics".to_string(),
1241                attr: <Self as TypedAttribute>::NAME.to_string(),
1242            });
1243        };
1244
1245        let semantics = GroupSemantics::new(semantics);
1246
1247        let mut mid_tags = vec![];
1248        for mid in i {
1249            mid_tags.push(mid.to_string());
1250        }
1251
1252        Ok(Self {
1253            semantics,
1254            mid_tags,
1255        })
1256    }
1257}
1258
1259impl Display for Group {
1260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1261        f.write_str(self.semantics.as_str())?;
1262        for m in &self.mid_tags {
1263            f.write_char(' ')?;
1264            f.write_str(m)?;
1265        }
1266        Ok(())
1267    }
1268}
1269
1270impl TypedAttribute for Group {
1271    const NAME: &'static str = "group";
1272}
1273
1274/// Setup attribute for the session or media.
1275///
1276/// See [RFC 4145 Section 4](https://datatracker.ietf.org/doc/html/rfc4145#section-4) for more details.
1277#[derive(Debug, Clone, PartialEq, Eq)]
1278#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1279pub enum Setup {
1280    /// Initiator of the connection.
1281    Active,
1282    /// Acceptor of the connection.
1283    Passive,
1284    /// Act as either initiator or acceptor of the connection.
1285    ActPass,
1286    /// Do not establish a connection.
1287    HoldConn,
1288}
1289
1290impl FromStr for Setup {
1291    type Err = AttributeError;
1292
1293    fn from_str(s: &str) -> Result<Self, Self::Err> {
1294        if "active".eq_ignore_ascii_case(s) {
1295            Ok(Setup::Active)
1296        } else if "passive".eq_ignore_ascii_case(s) {
1297            Ok(Setup::Passive)
1298        } else if "actpass".eq_ignore_ascii_case(s) {
1299            Ok(Setup::ActPass)
1300        } else if "holdconn".eq_ignore_ascii_case(s) {
1301            Ok(Setup::HoldConn)
1302        } else {
1303            Err(AttributeError::Other {
1304                error: format!("Invalid Setup value {s}"),
1305                attr: <Self as TypedAttribute>::NAME.to_string(),
1306            })
1307        }
1308    }
1309}
1310
1311impl Display for Setup {
1312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1313        let s = match self {
1314            Setup::Active => "active",
1315            Setup::Passive => "passive",
1316            Setup::ActPass => "actpass",
1317            Setup::HoldConn => "holdconn",
1318        };
1319        f.write_str(s)
1320    }
1321}
1322
1323impl TypedAttribute for Setup {
1324    const NAME: &'static str = "setup";
1325}
1326
1327/// SSRC media attribute.
1328///
1329/// See [RFC 5576 Section 4.1](https://datatracker.ietf.org/doc/html/rfc5576#section-4.1)
1330#[derive(Debug, Clone, PartialEq, Eq)]
1331#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1332pub struct Ssrc {
1333    pub ssrc_id: u32,
1334    pub attribute: SsrcAttribute,
1335    pub value: Option<String>,
1336}
1337
1338impl Ssrc {
1339    pub fn new(ssrc_id: u32, attribute: SsrcAttribute) -> Self {
1340        Ssrc {
1341            ssrc_id,
1342            attribute,
1343            value: None,
1344        }
1345    }
1346
1347    pub fn with_typed_attribute(ssrc_id: u32, attribute: impl TypedAttribute) -> Self {
1348        let value = attribute.to_string();
1349        Ssrc {
1350            ssrc_id,
1351            attribute: SsrcAttribute::from(attribute),
1352            value: Some(value),
1353        }
1354    }
1355
1356    pub fn with_value(ssrc_id: u32, attribute: SsrcAttribute, value: impl ToString) -> Self {
1357        Ssrc {
1358            ssrc_id,
1359            attribute,
1360            value: Some(value.to_string()),
1361        }
1362    }
1363
1364    pub fn set_value(&mut self, value: impl ToString) {
1365        self.value = Some(value.to_string());
1366    }
1367
1368    /// Gets the inner attribute as a `TypedAttribute`.
1369    ///
1370    /// # Errors
1371    ///
1372    /// * `AttributeError::Other` if the inner attribute doesn't match
1373    ///   the specified `TypedAttribute` or if the value is empty.
1374    /// * a specific `AttributeError` if the typed attribute couldn't be built.
1375    pub fn get_typed<T: TypedAttribute>(&self) -> Result<T, AttributeError> {
1376        if self.attribute.as_str() != T::NAME {
1377            return Err(AttributeError::Other {
1378                error: format!("Attribute type mismatch (requested {})", T::NAME),
1379                attr: self.attribute.as_str().to_string(),
1380            });
1381        }
1382
1383        // Property attributes have no value; they parse the empty string,
1384        // consistent with Session::attributes_typed().
1385        T::from_str(self.value.as_deref().unwrap_or_default())
1386    }
1387}
1388
1389impl FromStr for Ssrc {
1390    type Err = AttributeError;
1391
1392    fn from_str(s: &str) -> Result<Self, Self::Err> {
1393        let Some((ssrc_id_str, rest)) = s.split_once(' ') else {
1394            return Err(AttributeError::ParamNotFound {
1395                param: "Ssrc id".to_string(),
1396                attr: <Self as TypedAttribute>::NAME.to_string(),
1397            });
1398        };
1399
1400        let Ok(ssrc_id) = ssrc_id_str.parse::<u32>() else {
1401            return Err(AttributeError::InvalidParamValue {
1402                param: "Ssrc id".to_string(),
1403                val: ssrc_id_str.to_string(),
1404                attr: <Self as TypedAttribute>::NAME.to_string(),
1405            });
1406        };
1407
1408        let (attr, value) = if let Some((attr_str, value)) = rest.split_once(':') {
1409            (attr_str, Some(value.to_string()))
1410        } else {
1411            (rest, None)
1412        };
1413
1414        Ok(Self {
1415            ssrc_id,
1416            attribute: SsrcAttribute::new(attr),
1417            value,
1418        })
1419    }
1420}
1421
1422impl Display for Ssrc {
1423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1424        use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
1425
1426        let attr_str = match &self.attribute {
1427            SsrcAttribute::Cname => "cname",
1428            SsrcAttribute::PreviousSsrc => "previous-ssrc",
1429            SsrcAttribute::Fmtp => <Fmtp as TypedAttribute>::NAME,
1430            SsrcAttribute::Rtcp => <Rtcp as TypedAttribute>::NAME,
1431            SsrcAttribute::ReferenceClock => <ReferenceClock as TypedAttribute>::NAME,
1432            SsrcAttribute::MediaClockSource => <MediaClockSource as TypedAttribute>::NAME,
1433            SsrcAttribute::Other(other) => other.as_str(),
1434        };
1435        write!(f, "{} {attr_str}", self.ssrc_id)?;
1436
1437        if let Some(value) = &self.value {
1438            f.write_char(':')?;
1439            f.write_str(value)?;
1440        }
1441
1442        Ok(())
1443    }
1444}
1445
1446impl TypedAttribute for Ssrc {
1447    const NAME: &'static str = "ssrc";
1448}
1449
1450/// SSRC group attribute
1451///
1452/// See [RFC 5576 Section 4.2](https://datatracker.ietf.org/doc/html/rfc5576#section-4.2)
1453#[derive(Debug, Clone, PartialEq, Eq)]
1454#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1455pub struct SsrcGroup {
1456    pub semantics: GroupSemantics,
1457    pub ssrc_ids: Vec<u32>,
1458}
1459
1460impl SsrcGroup {
1461    pub fn new(semantics: GroupSemantics) -> Self {
1462        SsrcGroup {
1463            semantics,
1464            ssrc_ids: vec![],
1465        }
1466    }
1467
1468    pub fn add_ssrc_id(&mut self, ssrc_id: u32) {
1469        self.ssrc_ids.push(ssrc_id)
1470    }
1471
1472    pub fn add_ssrc_ids(&mut self, ssrc_ids: impl IntoIterator<Item = u32>) {
1473        self.ssrc_ids.extend(ssrc_ids)
1474    }
1475}
1476
1477impl FromStr for SsrcGroup {
1478    type Err = AttributeError;
1479
1480    fn from_str(s: &str) -> Result<Self, Self::Err> {
1481        // Like the group attribute, treat runs of whitespace as a single
1482        // separator.
1483        let mut i = s.split_ascii_whitespace();
1484
1485        let Some(semantics) = i.next() else {
1486            return Err(AttributeError::ParamNotFound {
1487                param: "Semantics".to_string(),
1488                attr: <Self as TypedAttribute>::NAME.to_string(),
1489            });
1490        };
1491
1492        let semantics = if "FEC".eq_ignore_ascii_case(semantics) {
1493            GroupSemantics::FEC
1494        } else if "FID".eq_ignore_ascii_case(semantics) {
1495            GroupSemantics::FID
1496        } else {
1497            // The initial defined semantics for ssrc-group attribute are FID and FEC
1498            // The other registered group semantics are not useful for source grouping
1499            // But keep this open for any other new semantics that are not part of GroupSemantics
1500            GroupSemantics::Other(semantics.to_string())
1501        };
1502
1503        let mut ssrc_ids = vec![];
1504        for ssrc_id in i {
1505            let Ok(ssrc_id) = ssrc_id.parse::<u32>() else {
1506                return Err(AttributeError::InvalidParamValue {
1507                    param: "Ssrc id".to_string(),
1508                    val: ssrc_id.to_string(),
1509                    attr: <Self as TypedAttribute>::NAME.to_string(),
1510                });
1511            };
1512            ssrc_ids.push(ssrc_id);
1513        }
1514
1515        if ssrc_ids.is_empty() {
1516            return Err(AttributeError::ParamNotFound {
1517                param: "ssrc_id".to_string(),
1518                attr: <Self as TypedAttribute>::NAME.to_string(),
1519            });
1520        }
1521
1522        Ok(Self {
1523            semantics,
1524            ssrc_ids,
1525        })
1526    }
1527}
1528
1529impl Display for SsrcGroup {
1530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1531        // Semantics other than FEC and FID are not useful for source grouping
1532        // but still displayed for debugging purpose.
1533        f.write_str(self.semantics.as_str())?;
1534        for ssrc_id in &self.ssrc_ids {
1535            f.write_char(' ')?;
1536            write!(f, "{ssrc_id}")?;
1537        }
1538
1539        Ok(())
1540    }
1541}
1542
1543impl TypedAttribute for SsrcGroup {
1544    const NAME: &'static str = "ssrc-group";
1545}
1546
1547/// SRTP Key parameter
1548///
1549/// See [RFC 4568 Section 6.1](https://datatracker.ietf.org/doc/html/rfc4568#section-6.1)
1550#[derive(Debug, PartialEq, Eq, Clone)]
1551#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1552pub struct SrtpKeyParam {
1553    /// Concatenated key and salt, base64 encoded
1554    pub key_and_salt: String,
1555    /// Master key lifetime (max number of SRTP or SRTCP packets using this master key)
1556    ///
1557    /// The value must not exceed 2^48 (RFC 4568, section 8.1).
1558    pub lifetime: Option<u64>,
1559    /// MKI (Master Key Identifier) and length of the MKI field in SRTP packets
1560    pub mki_and_length: Option<(u32, u32)>,
1561}
1562
1563impl SrtpKeyParam {
1564    pub fn new(key_and_salt: impl ToString) -> Self {
1565        SrtpKeyParam {
1566            key_and_salt: key_and_salt.to_string(),
1567            lifetime: None,
1568            mki_and_length: None,
1569        }
1570    }
1571
1572    pub fn with_lifetime(key_and_salt: impl ToString, lifetime: u64) -> Self {
1573        SrtpKeyParam {
1574            key_and_salt: key_and_salt.to_string(),
1575            lifetime: Some(lifetime),
1576            mki_and_length: None,
1577        }
1578    }
1579
1580    pub fn with_lifetime_and_mki_and_length(
1581        key_and_salt: impl ToString,
1582        lifetime: u64,
1583        mki: u32,
1584        length: u32,
1585    ) -> Self {
1586        SrtpKeyParam {
1587            key_and_salt: key_and_salt.to_string(),
1588            lifetime: Some(lifetime),
1589            mki_and_length: Some((mki, length)),
1590        }
1591    }
1592
1593    pub fn set_lifetime(&mut self, lifetime: u64) {
1594        self.lifetime = Some(lifetime);
1595    }
1596
1597    pub fn set_mki_and_length(&mut self, mki: u32, length: u32) {
1598        self.mki_and_length = Some((mki, length));
1599    }
1600}
1601
1602// Parse a plain decimal integer as used in the crypto attribute: digits
1603// only, no sign and no leading zeroes, except for the value 0 itself
1604// (RFC 4568, section 8.1).
1605fn parse_decimal_no_leading_zeroes(s: &str) -> Option<u64> {
1606    if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
1607        return None;
1608    }
1609    if s.len() > 1 && s.starts_with('0') {
1610        return None;
1611    }
1612    s.parse().ok()
1613}
1614
1615impl FromStr for SrtpKeyParam {
1616    type Err = AttributeError;
1617    fn from_str(key_param: &str) -> Result<Self, Self::Err> {
1618        let mut k = key_param.split('|');
1619
1620        let Some(key_and_salt_with_method) = k.next() else {
1621            return Err(AttributeError::ParamNotFound {
1622                param: "Srtp Key and Salt".to_string(),
1623                attr: Crypto::NAME.to_string(),
1624            });
1625        };
1626
1627        let key_and_salt = if key_and_salt_with_method
1628            .get(..7)
1629            .is_some_and(|p| p.eq_ignore_ascii_case("inline:"))
1630        {
1631            &key_and_salt_with_method[7..]
1632        } else {
1633            return Err(AttributeError::InvalidParamValue {
1634                param: "Srtp Key and Salt".to_string(),
1635                val: key_and_salt_with_method.to_string(),
1636                attr: Crypto::NAME.to_string(),
1637            });
1638        };
1639
1640        let (lifetime, mki_and_length) = if let Some(next_param) = k.next() {
1641            match next_param.split_once(':') {
1642                Some(mki_and_length) => {
1643                    // lifetime is not specified, but only MKI and its length
1644                    let Ok(mki) = mki_and_length.0.parse::<u32>() else {
1645                        return Err(AttributeError::InvalidParamValue {
1646                            param: "MKI".to_string(),
1647                            val: next_param.to_string(),
1648                            attr: Crypto::NAME.to_string(),
1649                        });
1650                    };
1651
1652                    let Ok(len) = mki_and_length.1.parse::<u32>() else {
1653                        return Err(AttributeError::InvalidParamValue {
1654                            param: "Length".to_string(),
1655                            val: next_param.to_string(),
1656                            attr: Crypto::NAME.to_string(),
1657                        });
1658                    };
1659                    (None, Some((mki, len)))
1660                }
1661                None => {
1662                    // lifetime is specified
1663                    // The maximum master key lifetime is 2^48 packets for all
1664                    // crypto suites (RFC 4568, section 8.1).
1665                    const MAX_LIFETIME: u64 = 1 << 48;
1666                    let lifetime = match next_param.strip_prefix("2^") {
1667                        Some(exp) => {
1668                            let Some(exp) = parse_decimal_no_leading_zeroes(exp)
1669                                .and_then(|exp| u32::try_from(exp).ok())
1670                            else {
1671                                return Err(AttributeError::InvalidParamValue {
1672                                    param: "Lifetime".to_string(),
1673                                    val: next_param.to_string(),
1674                                    attr: Crypto::NAME.to_string(),
1675                                });
1676                            };
1677                            let Some(lifetime) = 1u64.checked_shl(exp) else {
1678                                return Err(AttributeError::InvalidParamValue {
1679                                    param: "Lifetime".to_string(),
1680                                    val: next_param.to_string(),
1681                                    attr: Crypto::NAME.to_string(),
1682                                });
1683                            };
1684                            // 1u64.checked_shl() never returns zero
1685                            if lifetime > MAX_LIFETIME {
1686                                return Err(AttributeError::InvalidParamValue {
1687                                    param: "Lifetime".to_string(),
1688                                    val: next_param.to_string(),
1689                                    attr: Crypto::NAME.to_string(),
1690                                });
1691                            }
1692                            Some(lifetime)
1693                        }
1694                        None => {
1695                            let Some(lifetime) = parse_decimal_no_leading_zeroes(next_param) else {
1696                                return Err(AttributeError::InvalidParamValue {
1697                                    param: "Lifetime".to_string(),
1698                                    val: next_param.to_string(),
1699                                    attr: Crypto::NAME.to_string(),
1700                                });
1701                            };
1702                            if lifetime == 0 || lifetime > MAX_LIFETIME {
1703                                return Err(AttributeError::InvalidParamValue {
1704                                    param: "Lifetime".to_string(),
1705                                    val: next_param.to_string(),
1706                                    attr: Crypto::NAME.to_string(),
1707                                });
1708                            }
1709                            Some(lifetime)
1710                        }
1711                    };
1712
1713                    // now parse the MKI and length
1714                    let mki_and_length = if let Some(m) = k.next() {
1715                        if let Some(p) = m.split_once(':') {
1716                            let Ok(mki) = p.0.parse::<u32>() else {
1717                                return Err(AttributeError::InvalidParamValue {
1718                                    param: "MKI".to_string(),
1719                                    val: m.to_string(),
1720                                    attr: Crypto::NAME.to_string(),
1721                                });
1722                            };
1723
1724                            let Ok(len) = p.1.parse::<u32>() else {
1725                                return Err(AttributeError::InvalidParamValue {
1726                                    param: "Length".to_string(),
1727                                    val: m.to_string(),
1728                                    attr: Crypto::NAME.to_string(),
1729                                });
1730                            };
1731                            Some((mki, len))
1732                        } else {
1733                            return Err(AttributeError::ParamNotFound {
1734                                param: "MKI and Length".to_string(),
1735                                attr: Crypto::NAME.to_string(),
1736                            });
1737                        }
1738                    } else {
1739                        None
1740                    };
1741
1742                    (lifetime, mki_and_length)
1743                }
1744            }
1745        } else {
1746            (None, None)
1747        };
1748
1749        if let Some((_, len)) = mki_and_length {
1750            if !(1..=128).contains(&len) {
1751                return Err(AttributeError::InvalidParamValue {
1752                    param: "MKI length".to_string(),
1753                    val: len.to_string(),
1754                    attr: Crypto::NAME.to_string(),
1755                });
1756            }
1757        }
1758
1759        if let Some(unexpected) = k.next() {
1760            return Err(AttributeError::UnexpectedTrailingItem {
1761                val: unexpected.to_string(),
1762                attr: Crypto::NAME.to_string(),
1763            });
1764        }
1765
1766        Ok(Self {
1767            key_and_salt: key_and_salt.to_string(),
1768            lifetime,
1769            mki_and_length,
1770        })
1771    }
1772}
1773
1774impl Display for SrtpKeyParam {
1775    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1776        write!(f, "inline:{}", self.key_and_salt)?;
1777        if let Some(lifetime) = self.lifetime {
1778            if lifetime.is_power_of_two() {
1779                write!(f, "|2^{}", lifetime.trailing_zeros())?;
1780            } else {
1781                write!(f, "|{lifetime}")?;
1782            }
1783        }
1784        if let Some((mki, length)) = self.mki_and_length {
1785            write!(f, "|{mki}:{length}")?;
1786        }
1787        Ok(())
1788    }
1789}
1790
1791/// Cryptographic information for the media
1792///
1793/// See [RFC 4568 Section 4](https://datatracker.ietf.org/doc/html/rfc4568#section-4)
1794#[derive(Debug, PartialEq, Eq, Clone)]
1795#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1796pub struct Crypto {
1797    pub tag: u32,
1798    pub crypto_suite: CryptoSuite,
1799    pub key_params: Vec<SrtpKeyParam>,
1800    pub session_params: Vec<SrtpSessionParam>,
1801}
1802
1803impl Crypto {
1804    pub fn new(tag: u32, crypto_suite: CryptoSuite) -> Self {
1805        Crypto {
1806            tag,
1807            crypto_suite,
1808            key_params: vec![],
1809            session_params: vec![],
1810        }
1811    }
1812
1813    pub fn add_key_param(&mut self, key_param: SrtpKeyParam) {
1814        self.key_params.push(key_param)
1815    }
1816
1817    pub fn add_key_params(&mut self, key_params: impl IntoIterator<Item = SrtpKeyParam>) {
1818        self.key_params.extend(key_params)
1819    }
1820
1821    pub fn add_session_param(&mut self, session_param: SrtpSessionParam) {
1822        self.session_params.push(session_param)
1823    }
1824
1825    pub fn add_session_params(
1826        &mut self,
1827        session_params: impl IntoIterator<Item = SrtpSessionParam>,
1828    ) {
1829        self.session_params.extend(session_params)
1830    }
1831}
1832
1833impl FromStr for Crypto {
1834    type Err = AttributeError;
1835
1836    fn from_str(s: &str) -> Result<Self, Self::Err> {
1837        let mut i = s.split(' ');
1838
1839        let Some(tag) = i.next() else {
1840            return Err(AttributeError::ParamNotFound {
1841                param: "Tag".to_string(),
1842                attr: <Self as TypedAttribute>::NAME.to_string(),
1843            });
1844        };
1845
1846        let Ok(tag) = tag.parse::<u32>() else {
1847            return Err(AttributeError::InvalidParamValue {
1848                param: "Tag".to_string(),
1849                val: tag.to_string(),
1850                attr: <Self as TypedAttribute>::NAME.to_string(),
1851            });
1852        };
1853
1854        let Some(crypto_suite) = i.next() else {
1855            return Err(AttributeError::ParamNotFound {
1856                param: "CryptoSuite".to_string(),
1857                attr: <Self as TypedAttribute>::NAME.to_string(),
1858            });
1859        };
1860
1861        let crypto_suite = CryptoSuite::new(crypto_suite);
1862
1863        let Some(key_params_str) = i.next() else {
1864            return Err(AttributeError::ParamNotFound {
1865                param: "Key params".to_string(),
1866                attr: <Self as TypedAttribute>::NAME.to_string(),
1867            });
1868        };
1869
1870        let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1871
1872        for key_param in key_params_str.split(';') {
1873            let key_param = SrtpKeyParam::from_str(key_param)?;
1874            key_params.push(key_param);
1875        }
1876
1877        let mut session_params: Vec<SrtpSessionParam> = Vec::new();
1878        for s in &mut i {
1879            let param = if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("KDR=")) {
1880                let kdr_val = &s[4..];
1881                let Ok(kdr_val) = kdr_val.parse::<u8>() else {
1882                    return Err(AttributeError::InvalidParamValue {
1883                        param: "KDR".to_string(),
1884                        val: kdr_val.to_string(),
1885                        attr: <Self as TypedAttribute>::NAME.to_string(),
1886                    });
1887                };
1888
1889                // Note: the range for KDR value is conflicting in the spec,
1890                // rfc4568#section-6.3.1 says the range should be 1,2,...24 and
1891                // the grammar in rfc4568#section-9.2 says it should be 0..24.
1892                // So using the bigger range i.e., 0..24 for now
1893                if !(0..=24).contains(&kdr_val) {
1894                    return Err(AttributeError::InvalidParamValue {
1895                        param: "KDR".to_string(),
1896                        val: format!("{kdr_val}(expected range 0..24)"),
1897                        attr: <Self as TypedAttribute>::NAME.to_string(),
1898                    });
1899                }
1900                SrtpSessionParam::Kdr(kdr_val)
1901            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTCP") {
1902                SrtpSessionParam::UnencryptedSrtcp
1903            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTP") {
1904                SrtpSessionParam::UnencryptedSrtp
1905            } else if s.eq_ignore_ascii_case("UNAUTHENTICATED_SRTP") {
1906                SrtpSessionParam::UnauthenticatedSrtp
1907            } else if s
1908                .get(..10)
1909                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_ORDER="))
1910            {
1911                let fec_ord = &s[10..];
1912                if fec_ord.eq_ignore_ascii_case("FEC_SRTP") {
1913                    SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
1914                } else if fec_ord.eq_ignore_ascii_case("SRTP_FEC") {
1915                    SrtpSessionParam::FecOrder(FecOrder::SrtpFec)
1916                } else {
1917                    return Err(AttributeError::InvalidParamValue {
1918                        param: "FEC order".to_string(),
1919                        val: s.to_string(),
1920                        attr: <Self as TypedAttribute>::NAME.to_string(),
1921                    });
1922                }
1923            } else if s
1924                .get(..8)
1925                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_KEY="))
1926            {
1927                let key_params_str = &s[8..];
1928                let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1929
1930                for key_param in key_params_str.split(';') {
1931                    let key_param = SrtpKeyParam::from_str(key_param)?;
1932                    key_params.push(key_param);
1933                }
1934                SrtpSessionParam::FecKey(key_params)
1935            } else if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("WSH=")) {
1936                let wsh_val = &s[4..];
1937                let Ok(wsh_val) = wsh_val.parse::<u8>() else {
1938                    return Err(AttributeError::InvalidParamValue {
1939                        param: "WSH".to_string(),
1940                        val: wsh_val.to_string(),
1941                        attr: <Self as TypedAttribute>::NAME.to_string(),
1942                    });
1943                };
1944
1945                if wsh_val < 64 {
1946                    return Err(AttributeError::InvalidParamValue {
1947                        param: "WSH".to_string(),
1948                        val: wsh_val.to_string(),
1949                        attr: <Self as TypedAttribute>::NAME.to_string(),
1950                    });
1951                }
1952                SrtpSessionParam::Wsh(wsh_val)
1953            } else {
1954                // Extension
1955                SrtpSessionParam::Extension(s.to_string())
1956            };
1957            session_params.push(param);
1958        }
1959
1960        Ok(Self {
1961            tag,
1962            key_params,
1963            crypto_suite,
1964            session_params,
1965        })
1966    }
1967}
1968
1969impl Display for Crypto {
1970    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1971        write!(f, "{} {}", self.tag, self.crypto_suite.as_str())?;
1972
1973        for (i, key_param) in self.key_params.iter().enumerate() {
1974            if i == 0 {
1975                f.write_char(' ')?;
1976            } else {
1977                f.write_char(';')?;
1978            }
1979
1980            write!(f, "{}", key_param)?;
1981        }
1982
1983        for session_param in &self.session_params {
1984            match session_param {
1985                SrtpSessionParam::Kdr(kdr) => write!(f, " KDR={kdr}")?,
1986                SrtpSessionParam::UnencryptedSrtp => write!(f, " UNENCRYPTED_SRTP")?,
1987                SrtpSessionParam::UnencryptedSrtcp => write!(f, " UNENCRYPTED_SRTCP")?,
1988                SrtpSessionParam::UnauthenticatedSrtp => write!(f, " UNAUTHENTICATED_SRTP")?,
1989                SrtpSessionParam::FecOrder(fec_order) => {
1990                    let order = match fec_order {
1991                        FecOrder::FecSrtp => "FEC_SRTP",
1992                        FecOrder::SrtpFec => "SRTP_FEC",
1993                    };
1994                    write!(f, " FEC_ORDER={order}")?;
1995                }
1996                SrtpSessionParam::FecKey(srtp_key_params) => {
1997                    write!(f, " FEC_KEY")?;
1998                    for (i, key_param) in srtp_key_params.iter().enumerate() {
1999                        if i == 0 {
2000                            f.write_char('=')?;
2001                        } else {
2002                            f.write_char(';')?;
2003                        }
2004
2005                        write!(f, "{}", key_param)?;
2006                    }
2007                }
2008                SrtpSessionParam::Wsh(wsh) => write!(f, " WSH={wsh}")?,
2009                SrtpSessionParam::Extension(extn) => {
2010                    f.write_char(' ')?;
2011                    f.write_str(extn)?;
2012                }
2013            }
2014        }
2015        Ok(())
2016    }
2017}
2018
2019impl TypedAttribute for Crypto {
2020    const NAME: &'static str = "crypto";
2021}
2022
2023/// ICE Candidate attribute of the media
2024///
2025/// See [RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)
2026#[derive(Debug, PartialEq, Eq, Clone)]
2027#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2028pub struct Candidate {
2029    /// Arbitrary string used in the freezing algorithm to group similar candidates
2030    /// See [RFC 8445 Section 5.1.1.3](https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.1.3)
2031    pub foundation: String,
2032    /// Identifies the specific component of the data stream
2033    /// 1 for RTP and 2 for RTCP
2034    pub component_id: u32,
2035    /// Transport protocol of the candidate
2036    pub transport: String,
2037    /// Candidate's priority
2038    pub priority: u64,
2039    /// IP address of the candidate
2040    /// IPv4, IPv6 addresses and FQDN allowed
2041    pub address: CandidateAddress,
2042    /// Port of the candidate
2043    pub port: u16,
2044    /// Type of the candidate
2045    pub typ: CandidateType,
2046    /// Address related to the candidate
2047    /// Required for srflx, prflx and relay type candidates
2048    pub rel_addr: Option<IpAddr>,
2049    /// Port related to the candidate
2050    /// Required for srflx, prflx and relay type candidates
2051    pub rel_port: Option<u16>,
2052    /// Extensions
2053    pub extensions: Vec<(String, String)>,
2054}
2055
2056impl Candidate {
2057    pub fn new(
2058        foundation: impl ToString,
2059        component_id: u32,
2060        transport: impl ToString,
2061        priority: u64,
2062        address: CandidateAddress,
2063        port: u16,
2064        typ: CandidateType,
2065    ) -> Self {
2066        Candidate {
2067            foundation: foundation.to_string(),
2068            component_id,
2069            transport: transport.to_string(),
2070            priority,
2071            address,
2072            port,
2073            typ,
2074            rel_addr: None,
2075            rel_port: None,
2076            extensions: vec![],
2077        }
2078    }
2079
2080    #[allow(clippy::too_many_arguments)]
2081    pub fn with_rel_addr(
2082        foundation: impl ToString,
2083        component_id: u32,
2084        transport: impl ToString,
2085        priority: u64,
2086        address: CandidateAddress,
2087        port: u16,
2088        typ: CandidateType,
2089        rel_addr: impl Into<IpAddr>,
2090    ) -> Self {
2091        Candidate {
2092            foundation: foundation.to_string(),
2093            component_id,
2094            transport: transport.to_string(),
2095            priority,
2096            address,
2097            port,
2098            typ,
2099            rel_addr: Some(rel_addr.into()),
2100            rel_port: None,
2101            extensions: vec![],
2102        }
2103    }
2104
2105    #[allow(clippy::too_many_arguments)]
2106    pub fn with_rel_addr_and_port(
2107        foundation: impl ToString,
2108        component_id: u32,
2109        transport: impl ToString,
2110        priority: u64,
2111        address: CandidateAddress,
2112        port: u16,
2113        typ: CandidateType,
2114        rel_addr: impl Into<IpAddr>,
2115        rel_port: u16,
2116    ) -> Self {
2117        Candidate {
2118            foundation: foundation.to_string(),
2119            component_id,
2120            transport: transport.to_string(),
2121            priority,
2122            address,
2123            port,
2124            typ,
2125            rel_addr: Some(rel_addr.into()),
2126            rel_port: Some(rel_port),
2127            extensions: vec![],
2128        }
2129    }
2130
2131    pub fn set_rel_addr(&mut self, rel_addr: impl Into<IpAddr>) {
2132        self.rel_addr = Some(rel_addr.into());
2133    }
2134
2135    pub fn set_rel_port(&mut self, rel_port: u16) {
2136        self.rel_port = Some(rel_port);
2137    }
2138
2139    pub fn add_extension(&mut self, name: impl ToString, value: impl ToString) {
2140        self.extensions.push((name.to_string(), value.to_string()))
2141    }
2142}
2143
2144impl FromStr for Candidate {
2145    type Err = AttributeError;
2146
2147    fn from_str(s: &str) -> Result<Self, Self::Err> {
2148        let mut i = s.split(' ');
2149
2150        let Some(foundation) = i.next() else {
2151            return Err(AttributeError::ParamNotFound {
2152                param: "Foundation".to_string(),
2153                attr: <Self as TypedAttribute>::NAME.to_string(),
2154            });
2155        };
2156
2157        let Some(comp_id) = i.next() else {
2158            return Err(AttributeError::ParamNotFound {
2159                param: "Component id".to_string(),
2160                attr: <Self as TypedAttribute>::NAME.to_string(),
2161            });
2162        };
2163
2164        let Ok(comp_id) = comp_id.parse::<u32>() else {
2165            return Err(AttributeError::InvalidParamValue {
2166                param: "Component id".to_string(),
2167                val: comp_id.to_string(),
2168                attr: <Self as TypedAttribute>::NAME.to_string(),
2169            });
2170        };
2171
2172        let Some(transport) = i.next() else {
2173            return Err(AttributeError::ParamNotFound {
2174                param: "Transport".to_string(),
2175                attr: <Self as TypedAttribute>::NAME.to_string(),
2176            });
2177        };
2178
2179        let Some(priority) = i.next() else {
2180            return Err(AttributeError::ParamNotFound {
2181                param: "Priority".to_string(),
2182                attr: <Self as TypedAttribute>::NAME.to_string(),
2183            });
2184        };
2185
2186        let Ok(priority) = priority.parse::<u64>() else {
2187            return Err(AttributeError::InvalidParamValue {
2188                param: "Priority".to_string(),
2189                val: priority.to_string(),
2190                attr: <Self as TypedAttribute>::NAME.to_string(),
2191            });
2192        };
2193
2194        let Some(address) = i.next() else {
2195            return Err(AttributeError::ParamNotFound {
2196                param: "Address".to_string(),
2197                attr: <Self as TypedAttribute>::NAME.to_string(),
2198            });
2199        };
2200
2201        let address = match address.parse::<IpAddr>() {
2202            Ok(a) => CandidateAddress::IpAddr(a),
2203            Err(_) => CandidateAddress::FQDN(address.to_string()),
2204        };
2205
2206        let Some(port) = i.next() else {
2207            return Err(AttributeError::ParamNotFound {
2208                param: "Port".to_string(),
2209                attr: <Self as TypedAttribute>::NAME.to_string(),
2210            });
2211        };
2212
2213        let Ok(port) = port.parse::<u16>() else {
2214            return Err(AttributeError::InvalidParamValue {
2215                param: "Port".to_string(),
2216                val: port.to_string(),
2217                attr: <Self as TypedAttribute>::NAME.to_string(),
2218            });
2219        };
2220
2221        let Some(typ_str) = i.next() else {
2222            return Err(AttributeError::ParamNotFound {
2223                param: "'typ' string".to_string(),
2224                attr: <Self as TypedAttribute>::NAME.to_string(),
2225            });
2226        };
2227
2228        if !typ_str.eq_ignore_ascii_case("typ") {
2229            return Err(AttributeError::ParamNotFound {
2230                param: "'typ' string".to_string(),
2231                attr: <Self as TypedAttribute>::NAME.to_string(),
2232            });
2233        }
2234
2235        let Some(cand_type) = i.next() else {
2236            return Err(AttributeError::ParamNotFound {
2237                param: "Candidate type".to_string(),
2238                attr: <Self as TypedAttribute>::NAME.to_string(),
2239            });
2240        };
2241
2242        let cand_type = CandidateType::new(cand_type);
2243
2244        let mut rel_addr: Option<IpAddr> = None;
2245        let mut rel_port: Option<u16> = None;
2246        let mut exts: Vec<(String, String)> = Vec::new();
2247
2248        while let Some(key) = i.next() {
2249            if key.eq_ignore_ascii_case("raddr") {
2250                let Some(raddr) = i.next() else {
2251                    return Err(AttributeError::ParamNotFound {
2252                        param: "Relative address".to_string(),
2253                        attr: <Self as TypedAttribute>::NAME.to_string(),
2254                    });
2255                };
2256
2257                if let Ok(raddr) = raddr.parse::<IpAddr>() {
2258                    rel_addr = Some(raddr);
2259                } else {
2260                    return Err(AttributeError::InvalidParamValue {
2261                        param: "Relative address".to_string(),
2262                        val: raddr.to_string(),
2263                        attr: <Self as TypedAttribute>::NAME.to_string(),
2264                    });
2265                };
2266            } else if key.eq_ignore_ascii_case("rport") {
2267                let Some(rport) = i.next() else {
2268                    return Err(AttributeError::ParamNotFound {
2269                        param: "Relative port".to_string(),
2270                        attr: <Self as TypedAttribute>::NAME.to_string(),
2271                    });
2272                };
2273
2274                if let Ok(rport) = rport.parse::<u16>() {
2275                    rel_port = Some(rport);
2276                } else {
2277                    return Err(AttributeError::InvalidParamValue {
2278                        param: "Relative port".to_string(),
2279                        val: rport.to_string(),
2280                        attr: <Self as TypedAttribute>::NAME.to_string(),
2281                    });
2282                }
2283            } else {
2284                let Some(val) = i.next() else {
2285                    return Err(AttributeError::Other {
2286                        error: format!("No val for the extension {key}"),
2287                        attr: <Self as TypedAttribute>::NAME.to_string(),
2288                    });
2289                };
2290
2291                exts.push((key.to_string(), val.to_string()));
2292            }
2293        }
2294
2295        Ok(Self {
2296            foundation: foundation.to_string(),
2297            component_id: comp_id,
2298            transport: transport.to_string(),
2299            priority,
2300            address,
2301            port,
2302            typ: cand_type,
2303            rel_addr,
2304            rel_port,
2305            extensions: exts,
2306        })
2307    }
2308}
2309
2310impl Display for Candidate {
2311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2312        let candidate_addr = match &self.address {
2313            CandidateAddress::IpAddr(a) => a.to_string(),
2314            CandidateAddress::FQDN(d) => d.clone(),
2315        };
2316        write!(
2317            f,
2318            "{} {} {} {} {} {} typ {}",
2319            self.foundation,
2320            self.component_id,
2321            self.transport,
2322            self.priority,
2323            candidate_addr,
2324            self.port,
2325            self.typ.as_str(),
2326        )?;
2327        if let Some(rel_addr) = self.rel_addr {
2328            write!(f, " raddr {rel_addr}")?;
2329        }
2330        if let Some(rel_port) = self.rel_port {
2331            write!(f, " rport {rel_port}")?;
2332        }
2333        for (key, val) in &self.extensions {
2334            write!(f, " {key} {val}")?;
2335        }
2336        Ok(())
2337    }
2338}
2339
2340impl TypedAttribute for Candidate {
2341    const NAME: &'static str = "candidate";
2342}
2343
2344/// Restricted identification line (`a=rid`) attribute
2345///
2346/// Represents the send/recv restriction that defines a unique RTP payload
2347/// configuration, see
2348/// [RFC 8851 Section 4](https://datatracker.ietf.org/doc/html/rfc8851#section-4).
2349/// Restriction parameters (`pt=`, `max-width=`, `max-br=`, ...) are kept as raw
2350/// `name[=value]` tokens, in order, without interpretation. The optional `pt=`
2351/// payload format list is the first parameter when present.
2352#[derive(Debug, Clone, PartialEq, Eq)]
2353#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2354pub struct Rid {
2355    /// The restriction identifier, referenced by `a=extmap` and SDES items.
2356    pub id: String,
2357    /// Whether the restriction applies to a `send` or `recv` stream.
2358    pub direction: RidDirection,
2359    /// Restriction parameters, e.g. `pt=99,102` or `max-br=64000`.
2360    pub params: Vec<String>,
2361}
2362
2363impl Rid {
2364    pub fn new(id: impl ToString, direction: RidDirection) -> Self {
2365        Rid {
2366            id: id.to_string(),
2367            direction,
2368            params: Vec::new(),
2369        }
2370    }
2371
2372    pub fn add_param(&mut self, param: impl ToString) {
2373        self.params.push(param.to_string());
2374    }
2375
2376    pub fn with_params(
2377        id: impl ToString,
2378        direction: RidDirection,
2379        params: impl IntoIterator<Item = impl ToString>,
2380    ) -> Self {
2381        Rid {
2382            id: id.to_string(),
2383            direction,
2384            params: params.into_iter().map(|p| p.to_string()).collect(),
2385        }
2386    }
2387}
2388
2389impl FromStr for Rid {
2390    type Err = AttributeError;
2391
2392    fn from_str(s: &str) -> Result<Self, Self::Err> {
2393        let attr = <Self as TypedAttribute>::NAME.to_string();
2394        let (id, rest) = s
2395            .split_once(' ')
2396            .ok_or_else(|| AttributeError::ParamNotFound {
2397                param: "Direction".to_string(),
2398                attr: attr.clone(),
2399            })?;
2400
2401        let id = id.trim();
2402        if id.is_empty() {
2403            return Err(AttributeError::ParamNotFound {
2404                param: "ID".to_string(),
2405                attr: attr.clone(),
2406            });
2407        }
2408
2409        // RFC 8851, section 10: rid-id = 1*(alpha-numeric / "-" / "_"),
2410        // with alpha-numeric = ALPHA / DIGIT (RFC 8866, section 8).
2411        if !id
2412            .bytes()
2413            .all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
2414        {
2415            return Err(AttributeError::InvalidParamValue {
2416                param: "ID".to_string(),
2417                val: id.to_string(),
2418                attr,
2419            });
2420        }
2421
2422        // The direction is optionally followed by a single space and the
2423        // restriction parameter list.
2424        let (direction_str, params_str) = match rest.trim().split_once(' ') {
2425            Some((d, p)) => (d, p),
2426            None => (rest.trim(), ""),
2427        };
2428
2429        let direction = direction_str
2430            .parse()
2431            .map_err(|_| AttributeError::InvalidParamValue {
2432                param: "Direction".to_string(),
2433                val: direction_str.to_string(),
2434                attr: attr.clone(),
2435            })?;
2436
2437        let params = if params_str.trim().is_empty() {
2438            Vec::new()
2439        } else {
2440            params_str
2441                .trim()
2442                .split(';')
2443                .map(|p| p.trim())
2444                .filter(|p| !p.is_empty())
2445                .map(|p| {
2446                    if p.contains(char::is_whitespace) {
2447                        Err(AttributeError::InvalidParamValue {
2448                            param: "Parameter".to_string(),
2449                            val: p.to_string(),
2450                            attr: attr.clone(),
2451                        })
2452                    } else {
2453                        Ok(p.to_string())
2454                    }
2455                })
2456                .collect::<Result<Vec<_>, _>>()?
2457        };
2458
2459        Ok(Rid {
2460            id: id.to_string(),
2461            direction,
2462            params,
2463        })
2464    }
2465}
2466
2467impl Display for Rid {
2468    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2469        write!(f, "{} {}", self.id, self.direction)?;
2470        if !self.params.is_empty() {
2471            write!(f, " {}", self.params.join(";"))?;
2472        }
2473        Ok(())
2474    }
2475}
2476
2477impl TypedAttribute for Rid {
2478    const NAME: &'static str = "rid";
2479}
2480
2481/// Key management protocol of the [`KeyMgmt`] attribute.
2482///
2483/// The identifiers (KMPIDs) are case-sensitive, registered at IANA in the
2484/// "SDP/RTSP key management protocol identifiers" subregistry
2485/// ([RFC 4567 Section 9.3](https://datatracker.ietf.org/doc/html/rfc4567#section-9.3));
2486/// `mikey` is the only value defined so far.
2487#[derive(Debug, Clone, PartialEq, Eq)]
2488#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2489pub enum KeyMgmtMethod {
2490    /// Multimedia Internet KEYing ([RFC 3830](https://datatracker.ietf.org/doc/html/rfc3830)).
2491    Mikey,
2492    /// Any other, possibly unregistered method, kept verbatim.
2493    Other(String),
2494}
2495
2496impl From<&str> for KeyMgmtMethod {
2497    fn from(s: &str) -> Self {
2498        match s {
2499            "mikey" => KeyMgmtMethod::Mikey,
2500            other => KeyMgmtMethod::Other(other.to_string()),
2501        }
2502    }
2503}
2504
2505impl std::str::FromStr for KeyMgmtMethod {
2506    // FIXME use the never type when it is stable
2507    type Err = ();
2508
2509    fn from_str(s: &str) -> Result<Self, Self::Err> {
2510        Ok(s.into())
2511    }
2512}
2513
2514impl Display for KeyMgmtMethod {
2515    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2516        match self {
2517            KeyMgmtMethod::Mikey => f.write_str("mikey"),
2518            KeyMgmtMethod::Other(other) => f.write_str(other),
2519        }
2520    }
2521}
2522
2523/// Key management attribute (`a=key-mgmt`) attribute
2524///
2525/// See [RFC 4567 Section 4.1](https://datatracker.ietf.org/doc/html/rfc4567#section-4.1).
2526/// The key management data is kept verbatim; for the `mikey` method it is
2527/// the base64-encoded MIKEY message (RFC 3830, RFC 4567 section 7).
2528///
2529/// Media-level `key-mgmt` attributes override session-level ones, which
2530/// consumers have to resolve themselves.
2531#[derive(Debug, Clone, PartialEq, Eq)]
2532#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2533pub struct KeyMgmt {
2534    /// Key management protocol.
2535    pub method: KeyMgmtMethod,
2536    /// Key management data, if any.
2537    pub data: Option<String>,
2538}
2539
2540impl KeyMgmt {
2541    pub fn new(method: KeyMgmtMethod) -> Self {
2542        KeyMgmt { method, data: None }
2543    }
2544
2545    pub fn with_data(method: KeyMgmtMethod, data: impl ToString) -> Self {
2546        KeyMgmt {
2547            method,
2548            data: Some(data.to_string()),
2549        }
2550    }
2551}
2552
2553impl FromStr for KeyMgmt {
2554    type Err = AttributeError;
2555
2556    fn from_str(s: &str) -> Result<Self, Self::Err> {
2557        let s = s.trim();
2558        if s.is_empty() {
2559            return Err(AttributeError::ParamNotFound {
2560                param: "Method".to_string(),
2561                attr: <Self as TypedAttribute>::NAME.to_string(),
2562            });
2563        }
2564
2565        let (method, data) = match s.split_once(char::is_whitespace) {
2566            Some((method, data)) => (method, Some(data.trim().to_string())),
2567            None => (s, None),
2568        };
2569
2570        // RFC 4567, section 3: KMPID = 1*(ALPHA / DIGIT).
2571        if !method.bytes().all(|b| b.is_ascii_alphanumeric()) {
2572            return Err(AttributeError::InvalidParamValue {
2573                param: "Method".to_string(),
2574                val: method.to_string(),
2575                attr: <Self as TypedAttribute>::NAME.to_string(),
2576            });
2577        }
2578
2579        Ok(KeyMgmt {
2580            method: method.into(),
2581            data,
2582        })
2583    }
2584}
2585
2586impl Display for KeyMgmt {
2587    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2588        match &self.data {
2589            Some(data) => write!(f, "{} {}", self.method, data),
2590            None => write!(f, "{}", self.method),
2591        }
2592    }
2593}
2594
2595impl TypedAttribute for KeyMgmt {
2596    const NAME: &'static str = "key-mgmt";
2597}
2598
2599/// Remote candidate for one component of a data stream
2600///
2601/// A single entry of the [`RemoteCandidates`] attribute, in
2602/// `component-id connection-address port` form
2603/// ([RFC 8839 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8839#section-5.2)).
2604///
2605/// The address must be an IP address: agents generating candidates MUST NOT
2606/// use FQDNs and agents receiving them MUST ignore them
2607/// ([RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)).
2608#[derive(Debug, Clone, PartialEq, Eq)]
2609#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2610pub struct RemoteCandidate {
2611    /// Component identifier of the data stream, 1 for RTP and 2 for RTCP.
2612    pub component_id: u32,
2613    /// Remote address (IPv4 or IPv6).
2614    pub address: IpAddr,
2615    /// Remote port.
2616    pub port: u16,
2617}
2618
2619impl RemoteCandidate {
2620    pub fn new(component_id: u32, address: impl Into<IpAddr>, port: u16) -> Self {
2621        RemoteCandidate {
2622            component_id,
2623            address: address.into(),
2624            port,
2625        }
2626    }
2627}
2628
2629impl FromStr for RemoteCandidate {
2630    type Err = AttributeError;
2631
2632    fn from_str(s: &str) -> Result<Self, Self::Err> {
2633        let attr = <RemoteCandidates as TypedAttribute>::NAME.to_string();
2634        let mut parts = s.split_ascii_whitespace();
2635
2636        let component_id_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2637            param: "Component ID".to_string(),
2638            attr: attr.clone(),
2639        })?;
2640        let component_id =
2641            component_id_token
2642                .parse()
2643                .map_err(|_| AttributeError::InvalidParamValue {
2644                    param: "Component ID".to_string(),
2645                    val: component_id_token.to_string(),
2646                    attr: attr.clone(),
2647                })?;
2648
2649        let address_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2650            param: "Address".to_string(),
2651            attr: attr.clone(),
2652        })?;
2653        let address = address_token
2654            .parse()
2655            .map_err(|_| AttributeError::InvalidParamValue {
2656                param: "Address".to_string(),
2657                val: address_token.to_string(),
2658                attr: attr.clone(),
2659            })?;
2660
2661        let port_token = parts.next().ok_or_else(|| AttributeError::ParamNotFound {
2662            param: "Port".to_string(),
2663            attr: attr.clone(),
2664        })?;
2665        let port = port_token
2666            .parse()
2667            .map_err(|_| AttributeError::InvalidParamValue {
2668                param: "Port".to_string(),
2669                val: port_token.to_string(),
2670                attr: attr.clone(),
2671            })?;
2672
2673        if let Some(val) = parts.next() {
2674            return Err(AttributeError::UnexpectedTrailingItem {
2675                val: val.to_string(),
2676                attr,
2677            });
2678        }
2679
2680        Ok(RemoteCandidate {
2681            component_id,
2682            address,
2683            port,
2684        })
2685    }
2686}
2687
2688impl Display for RemoteCandidate {
2689    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2690        write!(f, "{} {} {}", self.component_id, self.address, self.port)
2691    }
2692}
2693
2694/// Remote candidates attribute (`a=remote-candidates`)
2695///
2696/// Lists the remote ICE candidates corresponding to the nominated pair for
2697/// each component of the data stream
2698/// ([RFC 8839 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8839#section-5.2)).
2699/// The ordering of the components is irrelevant.
2700///
2701/// The grammar allows multiple entries in a single attribute; implementations
2702/// also send one entry per `a=remote-candidates` line, which results in
2703/// multiple attributes on the wire.
2704#[derive(Debug, Clone, PartialEq, Eq)]
2705#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2706pub struct RemoteCandidates(pub Vec<RemoteCandidate>);
2707
2708impl RemoteCandidates {
2709    pub fn new(candidates: impl IntoIterator<Item = RemoteCandidate>) -> Self {
2710        RemoteCandidates(candidates.into_iter().collect())
2711    }
2712}
2713
2714impl FromStr for RemoteCandidates {
2715    type Err = AttributeError;
2716
2717    fn from_str(s: &str) -> Result<Self, Self::Err> {
2718        let attr = <Self as TypedAttribute>::NAME.to_string();
2719        let mut tokens = s.split_ascii_whitespace();
2720        let mut candidates = Vec::new();
2721
2722        // Every entry consists of exactly three tokens.
2723        loop {
2724            let triple: Vec<&str> = tokens.by_ref().take(3).collect();
2725            match triple.len() {
2726                0 => break,
2727                1..=2 => {
2728                    return Err(AttributeError::UnexpectedTrailingItem {
2729                        val: triple.join(" "),
2730                        attr,
2731                    })
2732                }
2733                _ => candidates.push(triple.join(" ").parse()?),
2734            }
2735        }
2736
2737        if candidates.is_empty() {
2738            return Err(AttributeError::ParamNotFound {
2739                param: "Candidate".to_string(),
2740                attr,
2741            });
2742        }
2743
2744        Ok(RemoteCandidates(candidates))
2745    }
2746}
2747
2748impl Display for RemoteCandidates {
2749    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2750        for (i, candidate) in self.0.iter().enumerate() {
2751            if i > 0 {
2752                f.write_char(' ')?;
2753            }
2754            write!(f, "{}", candidate)?;
2755        }
2756        Ok(())
2757    }
2758}
2759
2760impl TypedAttribute for RemoteCandidates {
2761    const NAME: &'static str = "remote-candidates";
2762}
2763
2764/// Filter mode of the [`SourceFilter`] (`a=source-filter`) attribute.
2765///
2766/// See [RFC 4570 Section 4](https://datatracker.ietf.org/doc/html/rfc4570#section-4).
2767#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2768#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2769pub enum SourceFilterMode {
2770    /// Accept only packets sent by the listed sources.
2771    Incl,
2772    /// Reject packets sent by the listed sources.
2773    Excl,
2774}
2775
2776impl SourceFilterMode {
2777    pub fn as_str(&self) -> &'static str {
2778        match self {
2779            SourceFilterMode::Incl => "incl",
2780            SourceFilterMode::Excl => "excl",
2781        }
2782    }
2783}
2784
2785impl FromStr for SourceFilterMode {
2786    type Err = ParseEnumError;
2787
2788    fn from_str(s: &str) -> Result<Self, Self::Err> {
2789        if "incl".eq_ignore_ascii_case(s) {
2790            Ok(SourceFilterMode::Incl)
2791        } else if "excl".eq_ignore_ascii_case(s) {
2792            Ok(SourceFilterMode::Excl)
2793        } else {
2794            Err(ParseEnumError::Invalid(s.to_string()))
2795        }
2796    }
2797}
2798
2799impl Display for SourceFilterMode {
2800    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2801        f.write_str(self.as_str())
2802    }
2803}
2804
2805/// Address type of the entries of the [`SourceFilter`] (`a=source-filter`)
2806/// attribute.
2807///
2808/// Models the `address-types` production, which extends the SDP `addrtype`
2809/// token with a `*` wildcard for FQDNs
2810/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2811#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2812#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2813pub enum SourceFilterAddrType {
2814    /// Any address family, only allowed with FQDN destinations.
2815    Any,
2816    /// IPv4 address
2817    Ip4,
2818    /// IPv6 address
2819    Ip6,
2820}
2821
2822impl SourceFilterAddrType {
2823    pub fn as_str(&self) -> &'static str {
2824        match self {
2825            SourceFilterAddrType::Any => "*",
2826            SourceFilterAddrType::Ip4 => "IP4",
2827            SourceFilterAddrType::Ip6 => "IP6",
2828        }
2829    }
2830}
2831
2832impl FromStr for SourceFilterAddrType {
2833    type Err = ParseEnumError;
2834
2835    fn from_str(s: &str) -> Result<Self, Self::Err> {
2836        match s {
2837            "*" => Ok(SourceFilterAddrType::Any),
2838            "IP4" => Ok(SourceFilterAddrType::Ip4),
2839            "IP6" => Ok(SourceFilterAddrType::Ip6),
2840            _ => Err(ParseEnumError::Invalid(s.to_string())),
2841        }
2842    }
2843}
2844
2845impl Display for SourceFilterAddrType {
2846    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2847        f.write_str(self.as_str())
2848    }
2849}
2850
2851/// Address of the [`SourceFilter`] (`a=source-filter`) attribute
2852///
2853/// Models the `unicast-address` and `basic-multicast-address` productions:
2854/// an IP address or an FQDN, but without the `/ttl` and `/number of
2855/// addresses` suffixes that a connection address may carry
2856/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2857#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2858#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2859pub enum SourceFilterAddress {
2860    /// IP address
2861    IpAddr(IpAddr),
2862    /// Fully qualified domain name
2863    FQDN(String),
2864}
2865
2866impl From<&str> for SourceFilterAddress {
2867    fn from(s: &str) -> Self {
2868        match s.parse::<IpAddr>() {
2869            Ok(address) => SourceFilterAddress::IpAddr(address),
2870            Err(_) => SourceFilterAddress::FQDN(s.to_string()),
2871        }
2872    }
2873}
2874
2875impl FromStr for SourceFilterAddress {
2876    // FIXME use the never type when it is stable
2877    type Err = ();
2878
2879    fn from_str(s: &str) -> Result<Self, Self::Err> {
2880        Ok(s.into())
2881    }
2882}
2883
2884impl Display for SourceFilterAddress {
2885    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2886        match self {
2887            SourceFilterAddress::IpAddr(address) => write!(f, "{}", address),
2888            SourceFilterAddress::FQDN(domain) => f.write_str(domain),
2889        }
2890    }
2891}
2892
2893/// Destination of the [`SourceFilter`] (`a=source-filter`) attribute
2894///
2895/// Models the `dest-address` production
2896/// ([RFC 4570 Appendix A](https://datatracker.ietf.org/doc/html/rfc4570#appendix-A)).
2897#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2898#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2899pub enum SourceFilterDestination {
2900    /// The `"*"` wildcard, matching all of the session's connection addresses
2901    Wildcard,
2902    /// Unicast, multicast address or FQDN
2903    Address(SourceFilterAddress),
2904}
2905
2906impl FromStr for SourceFilterDestination {
2907    // FIXME use the never type when it is stable
2908    type Err = ();
2909
2910    fn from_str(s: &str) -> Result<Self, Self::Err> {
2911        if s == "*" {
2912            Ok(SourceFilterDestination::Wildcard)
2913        } else {
2914            Ok(SourceFilterDestination::Address(s.into()))
2915        }
2916    }
2917}
2918
2919impl Display for SourceFilterDestination {
2920    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2921        match self {
2922            SourceFilterDestination::Wildcard => f.write_str("*"),
2923            SourceFilterDestination::Address(address) => write!(f, "{}", address),
2924        }
2925    }
2926}
2927
2928/// Source filter attribute (`a=source-filter`)
2929///
2930/// Restricts the set of source addresses accepted for a destination
2931/// address, e.g. for source-specific multicast.
2932///
2933/// See [RFC 4570 Section 4](https://datatracker.ietf.org/doc/html/rfc4570#section-4).
2934/// The destination must be one of the session's connection addresses or the
2935/// `"*"` wildcard; unlike a connection address it never carries the `/ttl`
2936/// and `/number of addresses` suffixes (RFC 4570, section 3.1).
2937///
2938/// Media-level source filters completely override session-level ones, which
2939/// consumers have to resolve themselves.
2940#[derive(Debug, Clone, PartialEq, Eq)]
2941#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2942pub struct SourceFilter {
2943    /// Inclusion or exclusion filter mode.
2944    pub mode: SourceFilterMode,
2945    /// Network type, usually [`NetType::In`].
2946    pub nettype: NetType,
2947    /// Address family of the destination and source addresses.
2948    pub addrtype: SourceFilterAddrType,
2949    /// Destination address or the `"*"` wildcard.
2950    pub destination: SourceFilterDestination,
2951    /// Source addresses, at least one entry.
2952    pub sources: Vec<SourceFilterAddress>,
2953}
2954
2955impl FromStr for SourceFilter {
2956    type Err = AttributeError;
2957
2958    fn from_str(s: &str) -> Result<Self, Self::Err> {
2959        let attr = <Self as TypedAttribute>::NAME.to_string();
2960
2961        // Runs of spaces are a single separator, like in c= lines,
2962        // so that no field can end up empty.
2963        let mut i = s.split(' ').filter(|part| !part.is_empty());
2964
2965        let Some(mode) = i.next() else {
2966            return Err(AttributeError::ParamNotFound {
2967                param: "Filter mode".to_string(),
2968                attr,
2969            });
2970        };
2971        let mode = mode
2972            .parse()
2973            .map_err(|_| AttributeError::InvalidParamValue {
2974                param: "Filter mode".to_string(),
2975                val: mode.to_string(),
2976                attr: attr.clone(),
2977            })?;
2978
2979        let Some(nettype) = i.next() else {
2980            return Err(AttributeError::ParamNotFound {
2981                param: "Network type".to_string(),
2982                attr,
2983            });
2984        };
2985        // RFC 4570 reuses the SDP nettype token (RFC 8866, section 9).
2986        if !is_token(nettype.as_bytes()) {
2987            return Err(AttributeError::InvalidParamValue {
2988                param: "Network type".to_string(),
2989                val: nettype.to_string(),
2990                attr: attr.clone(),
2991            });
2992        }
2993        let nettype = NetType::from_str(nettype).expect("infallible");
2994
2995        let Some(addrtype) = i.next() else {
2996            return Err(AttributeError::ParamNotFound {
2997                param: "Address types".to_string(),
2998                attr,
2999            });
3000        };
3001        let addrtype = addrtype
3002            .parse()
3003            .map_err(|_| AttributeError::InvalidParamValue {
3004                param: "Address types".to_string(),
3005                val: addrtype.to_string(),
3006                attr: attr.clone(),
3007            })?;
3008
3009        let Some(destination) = i.next() else {
3010            return Err(AttributeError::ParamNotFound {
3011                param: "Destination address".to_string(),
3012                attr,
3013            });
3014        };
3015
3016        // RFC 4570, section 3.1: a multicast destination MUST NOT include
3017        // the /ttl and /number of addresses sub-fields.
3018        if destination.contains('/') {
3019            return Err(AttributeError::InvalidParamValue {
3020                param: "Destination address".to_string(),
3021                val: destination.to_string(),
3022                attr,
3023            });
3024        }
3025        let destination = SourceFilterDestination::from_str(destination).expect("infallible");
3026        // RFC 4570, section 3.1: the "*" address type MUST only be used with
3027        // FQDN destinations.
3028        if addrtype == SourceFilterAddrType::Any
3029            && matches!(
3030                destination,
3031                SourceFilterDestination::Address(SourceFilterAddress::IpAddr(_))
3032            )
3033        {
3034            return Err(AttributeError::InvalidParamValue {
3035                param: "Destination address".to_string(),
3036                val: destination.to_string(),
3037                attr,
3038            });
3039        }
3040
3041        let sources: Vec<&str> = i.collect();
3042        if sources.is_empty() {
3043            return Err(AttributeError::ParamNotFound {
3044                param: "Source list".to_string(),
3045                attr,
3046            });
3047        }
3048        // RFC 4570, section 3.1: only the destination allows the "*" wildcard.
3049        if let Some(source) = sources.iter().find(|source| source == &&"*") {
3050            return Err(AttributeError::InvalidParamValue {
3051                param: "Source address".to_string(),
3052                val: source.to_string(),
3053                attr,
3054            });
3055        }
3056        let sources = sources.into_iter().map(SourceFilterAddress::from).collect();
3057
3058        Ok(SourceFilter {
3059            mode,
3060            nettype,
3061            addrtype,
3062            destination,
3063            sources,
3064        })
3065    }
3066}
3067
3068impl Display for SourceFilter {
3069    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3070        write!(
3071            f,
3072            "{} {} {} {}",
3073            self.mode, self.nettype, self.addrtype, self.destination
3074        )?;
3075        for source in &self.sources {
3076            write!(f, " {}", source)?;
3077        }
3078        Ok(())
3079    }
3080}
3081
3082impl TypedAttribute for SourceFilter {
3083    const NAME: &'static str = "source-filter";
3084}
3085
3086/// Media stream identification tag attribute (`a=mid`)
3087///
3088/// Assigns an identification tag to a media description, unique within
3089/// the session description, used to reference media descriptions from
3090/// grouping semantics such as BUNDLE.
3091///
3092/// See [RFC 5888 Section 4](https://datatracker.ietf.org/doc/html/rfc5888#section-4).
3093#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3094#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3095pub struct Mid(pub String);
3096
3097impl FromStr for Mid {
3098    type Err = AttributeError;
3099
3100    fn from_str(s: &str) -> Result<Self, Self::Err> {
3101        if !is_token(s.as_bytes()) {
3102            return Err(AttributeError::InvalidParamValue {
3103                param: "Identification tag".to_string(),
3104                val: s.to_string(),
3105                attr: <Self as TypedAttribute>::NAME.to_string(),
3106            });
3107        }
3108
3109        Ok(Mid(s.to_string()))
3110    }
3111}
3112
3113impl Display for Mid {
3114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3115        f.write_str(&self.0)
3116    }
3117}
3118
3119impl TypedAttribute for Mid {
3120    const NAME: &'static str = "mid";
3121}
3122
3123/// RTP/RTCP multiplexing attribute (`a=rtcp-mux`)
3124///
3125/// Indicates that RTP and RTCP packets are sent to the same transport
3126/// address and port. It is a property attribute without a value, used
3127/// at the media level.
3128///
3129/// See [RFC 5761 Section 5](https://datatracker.ietf.org/doc/html/rfc5761#section-5).
3130#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3132pub struct RtcpMux;
3133
3134impl FromStr for RtcpMux {
3135    type Err = AttributeError;
3136
3137    fn from_str(s: &str) -> Result<Self, Self::Err> {
3138        if !s.is_empty() {
3139            return Err(AttributeError::UnsupportedFormat {
3140                val: s.to_string(),
3141                attr: <Self as TypedAttribute>::NAME.to_string(),
3142            });
3143        }
3144
3145        Ok(RtcpMux)
3146    }
3147}
3148
3149impl Display for RtcpMux {
3150    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3151        Ok(())
3152    }
3153}
3154
3155impl TypedAttribute for RtcpMux {
3156    const NAME: &'static str = "rtcp-mux";
3157}
3158
3159/// RTP/RTCP multiplexing-only attribute (`a=rtcp-mux-only`)
3160///
3161/// Indicates that the endpoint can only receive RTP/RTCP multiplexed
3162/// packets, i.e. it does not accept RTCP packets on a separate
3163/// transport address and port even if one is indicated. It is a
3164/// property attribute without a value, used at the media level.
3165///
3166/// See [RFC 8858 Section 3](https://datatracker.ietf.org/doc/html/rfc8858#section-3).
3167#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3169pub struct RtcpMuxOnly;
3170
3171impl FromStr for RtcpMuxOnly {
3172    type Err = AttributeError;
3173
3174    fn from_str(s: &str) -> Result<Self, Self::Err> {
3175        if !s.is_empty() {
3176            return Err(AttributeError::UnsupportedFormat {
3177                val: s.to_string(),
3178                attr: <Self as TypedAttribute>::NAME.to_string(),
3179            });
3180        }
3181
3182        Ok(RtcpMuxOnly)
3183    }
3184}
3185
3186impl Display for RtcpMuxOnly {
3187    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3188        Ok(())
3189    }
3190}
3191
3192impl TypedAttribute for RtcpMuxOnly {
3193    const NAME: &'static str = "rtcp-mux-only";
3194}
3195
3196/// Reduced-size RTCP attribute (`a=rtcp-rsize`)
3197///
3198/// Indicates that the endpoint supports the reduced-size RTCP
3199/// compound packet format for the media stream. It is a property
3200/// attribute without a value, used at the media level.
3201///
3202/// See [RFC 5506 Section 5](https://datatracker.ietf.org/doc/html/rfc5506#section-5).
3203#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3204#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3205pub struct RtcpRsize;
3206
3207impl FromStr for RtcpRsize {
3208    type Err = AttributeError;
3209
3210    fn from_str(s: &str) -> Result<Self, Self::Err> {
3211        if !s.is_empty() {
3212            return Err(AttributeError::UnsupportedFormat {
3213                val: s.to_string(),
3214                attr: <Self as TypedAttribute>::NAME.to_string(),
3215            });
3216        }
3217
3218        Ok(RtcpRsize)
3219    }
3220}
3221
3222impl Display for RtcpRsize {
3223    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3224        Ok(())
3225    }
3226}
3227
3228impl TypedAttribute for RtcpRsize {
3229    const NAME: &'static str = "rtcp-rsize";
3230}
3231
3232/// ICE-lite implementation attribute (`a=ice-lite`)
3233///
3234/// Indicates that the agent implements the ICE-lite implementation,
3235/// which does not run the full ICE connectivity checks. It is a
3236/// property attribute without a value, used at the session level.
3237///
3238/// See [RFC 5245 Section 15.3](https://datatracker.ietf.org/doc/html/rfc5245#section-15.3).
3239#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3241pub struct IceLite;
3242
3243impl FromStr for IceLite {
3244    type Err = AttributeError;
3245
3246    fn from_str(s: &str) -> Result<Self, Self::Err> {
3247        if !s.is_empty() {
3248            return Err(AttributeError::UnsupportedFormat {
3249                val: s.to_string(),
3250                attr: <Self as TypedAttribute>::NAME.to_string(),
3251            });
3252        }
3253
3254        Ok(IceLite)
3255    }
3256}
3257
3258impl Display for IceLite {
3259    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3260        Ok(())
3261    }
3262}
3263
3264impl TypedAttribute for IceLite {
3265    const NAME: &'static str = "ice-lite";
3266}
3267
3268/// ICE character of the `ice-char` production (RFC 5245, section 15.1).
3269fn is_ice_char(c: char) -> bool {
3270    matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '/')
3271}
3272
3273/// ICE username fragment attribute (`a=ice-ufrag`)
3274///
3275/// Conveys the username fragment used by ICE for message integrity,
3276/// 4 to 256 ICE characters. It can appear at the session level or the
3277/// media level, where the media level value takes precedence.
3278///
3279/// See [RFC 5245 Section 15.4](https://datatracker.ietf.org/doc/html/rfc5245#section-15.4).
3280#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3282pub struct IceUfrag(pub String);
3283
3284impl FromStr for IceUfrag {
3285    type Err = AttributeError;
3286
3287    fn from_str(s: &str) -> Result<Self, Self::Err> {
3288        // ICE characters are ASCII, so the byte length is the length.
3289        if !(4..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
3290            return Err(AttributeError::InvalidParamValue {
3291                param: "ICE username fragment".to_string(),
3292                val: s.to_string(),
3293                attr: <Self as TypedAttribute>::NAME.to_string(),
3294            });
3295        }
3296
3297        Ok(IceUfrag(s.to_string()))
3298    }
3299}
3300
3301impl Display for IceUfrag {
3302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3303        f.write_str(&self.0)
3304    }
3305}
3306
3307impl TypedAttribute for IceUfrag {
3308    const NAME: &'static str = "ice-ufrag";
3309}
3310
3311/// ICE password attribute (`a=ice-pwd`)
3312///
3313/// Conveys the password used by ICE for message integrity, 22 to 256
3314/// ICE characters. It can appear at the session level or the media
3315/// level, where the media level value takes precedence.
3316///
3317/// See [RFC 5245 Section 15.4](https://datatracker.ietf.org/doc/html/rfc5245#section-15.4).
3318#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3319#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3320pub struct IcePwd(pub String);
3321
3322impl FromStr for IcePwd {
3323    type Err = AttributeError;
3324
3325    fn from_str(s: &str) -> Result<Self, Self::Err> {
3326        // ICE characters are ASCII, so the byte length is the length.
3327        if !(22..=256).contains(&s.len()) || !s.chars().all(is_ice_char) {
3328            return Err(AttributeError::InvalidParamValue {
3329                param: "ICE password".to_string(),
3330                val: s.to_string(),
3331                attr: <Self as TypedAttribute>::NAME.to_string(),
3332            });
3333        }
3334
3335        Ok(IcePwd(s.to_string()))
3336    }
3337}
3338
3339impl Display for IcePwd {
3340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3341        f.write_str(&self.0)
3342    }
3343}
3344
3345impl TypedAttribute for IcePwd {
3346    const NAME: &'static str = "ice-pwd";
3347}
3348
3349/// ICE extension option tags attribute (`a=ice-options`)
3350///
3351/// Lists the ICE extension option tags supported by the agent, e.g.
3352/// `trickle` (RFC 8839). It is used at the session level.
3353///
3354/// See [RFC 5245 Section 15.5](https://datatracker.ietf.org/doc/html/rfc5245#section-15.5).
3355#[derive(Debug, Clone, PartialEq, Eq)]
3356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3357pub struct IceOptions(pub Vec<String>);
3358
3359impl IceOptions {
3360    pub fn new(tags: impl IntoIterator<Item = impl ToString>) -> Self {
3361        IceOptions(tags.into_iter().map(|tag| tag.to_string()).collect())
3362    }
3363}
3364
3365impl FromStr for IceOptions {
3366    type Err = AttributeError;
3367
3368    fn from_str(s: &str) -> Result<Self, Self::Err> {
3369        let attr = <Self as TypedAttribute>::NAME.to_string();
3370        let mut tags = Vec::new();
3371
3372        for tag in s.split_ascii_whitespace() {
3373            if !tag.chars().all(is_ice_char) {
3374                return Err(AttributeError::InvalidParamValue {
3375                    param: "ICE option tag".to_string(),
3376                    val: tag.to_string(),
3377                    attr,
3378                });
3379            }
3380
3381            tags.push(tag.to_string());
3382        }
3383
3384        if tags.is_empty() {
3385            return Err(AttributeError::ParamNotFound {
3386                param: "ICE option tags".to_string(),
3387                attr,
3388            });
3389        }
3390
3391        Ok(IceOptions(tags))
3392    }
3393}
3394
3395impl Display for IceOptions {
3396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3397        for (i, tag) in self.0.iter().enumerate() {
3398            if i > 0 {
3399                f.write_char(' ')?;
3400            }
3401            f.write_str(tag)?;
3402        }
3403        Ok(())
3404    }
3405}
3406
3407impl TypedAttribute for IceOptions {
3408    const NAME: &'static str = "ice-options";
3409}
3410
3411/// End of ICE candidates attribute (`a=end-of-candidates`)
3412///
3413/// Indicates that the sending agent will not trickle further ICE
3414/// candidates. It is a property attribute without a value, used at the
3415/// session level or the media level.
3416///
3417/// See [RFC 8840 Section 8.1](https://datatracker.ietf.org/doc/html/rfc8840#section-8.1).
3418#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
3419#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3420pub struct EndOfCandidates;
3421
3422impl FromStr for EndOfCandidates {
3423    type Err = AttributeError;
3424
3425    fn from_str(s: &str) -> Result<Self, Self::Err> {
3426        if !s.is_empty() {
3427            return Err(AttributeError::UnsupportedFormat {
3428                val: s.to_string(),
3429                attr: <Self as TypedAttribute>::NAME.to_string(),
3430            });
3431        }
3432
3433        Ok(EndOfCandidates)
3434    }
3435}
3436
3437impl Display for EndOfCandidates {
3438    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3439        Ok(())
3440    }
3441}
3442
3443impl TypedAttribute for EndOfCandidates {
3444    const NAME: &'static str = "end-of-candidates";
3445}
3446
3447/// MediaStream identification attribute (`a=msid`)
3448///
3449/// Associates a media description with a MediaStream and identifies the
3450/// MediaStreamTrack it conveys in the application data. Both fields are
3451/// tokens of 1 to 64 characters.
3452///
3453/// See [RFC 8830 Section 2](https://datatracker.ietf.org/doc/html/rfc8830#section-2).
3454#[derive(Debug, Clone, PartialEq, Eq)]
3455#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3456pub struct Msid {
3457    /// Identifier of the MediaStream this media description belongs to.
3458    pub id: String,
3459    /// Application data, the identifier of the MediaStreamTrack.
3460    pub appdata: Option<String>,
3461}
3462
3463impl Msid {
3464    pub fn new(id: impl ToString, appdata: Option<impl ToString>) -> Self {
3465        Msid {
3466            id: id.to_string(),
3467            appdata: appdata.map(|appdata| appdata.to_string()),
3468        }
3469    }
3470}
3471
3472impl FromStr for Msid {
3473    type Err = AttributeError;
3474
3475    fn from_str(s: &str) -> Result<Self, Self::Err> {
3476        let attr = <Self as TypedAttribute>::NAME.to_string();
3477        let mut tokens = s.split_ascii_whitespace();
3478
3479        let id = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3480            param: "MSID id".to_string(),
3481            attr: attr.clone(),
3482        })?;
3483        let appdata = tokens.next();
3484
3485        let trailing: Vec<_> = tokens.collect();
3486        if !trailing.is_empty() {
3487            return Err(AttributeError::UnexpectedTrailingItem {
3488                val: trailing.join(" "),
3489                attr,
3490            });
3491        }
3492
3493        // Both fields are 1*64 token-chars (RFC 8830, section 2).
3494        if id.len() > 64 || !is_token(id.as_bytes()) {
3495            return Err(AttributeError::InvalidParamValue {
3496                param: "MSID id".to_string(),
3497                val: id.to_string(),
3498                attr,
3499            });
3500        }
3501
3502        if let Some(appdata) = appdata {
3503            if appdata.len() > 64 || !is_token(appdata.as_bytes()) {
3504                return Err(AttributeError::InvalidParamValue {
3505                    param: "MSID appdata".to_string(),
3506                    val: appdata.to_string(),
3507                    attr,
3508                });
3509            }
3510        }
3511
3512        Ok(Msid {
3513            id: id.to_string(),
3514            appdata: appdata.map(ToString::to_string),
3515        })
3516    }
3517}
3518
3519impl Display for Msid {
3520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3521        f.write_str(&self.id)?;
3522        if let Some(appdata) = &self.appdata {
3523            f.write_char(' ')?;
3524            f.write_str(appdata)?;
3525        }
3526        Ok(())
3527    }
3528}
3529
3530impl TypedAttribute for Msid {
3531    const NAME: &'static str = "msid";
3532}
3533
3534/// SCTP port attribute (`a=sctp-port`)
3535///
3536/// Indicates the SCTP port number of the association used by the media
3537/// description, used with the "UDP/DTLS/SCTP" and "TCP/DTLS/SCTP"
3538/// proto values at the media level. The port range is 0 to 65535 and
3539/// leading zeroes are not used.
3540///
3541/// See [RFC 8841 Section 5.2](https://datatracker.ietf.org/doc/html/rfc8841#section-5.2).
3542#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3543#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3544pub struct SctpPort(pub u16);
3545
3546/// Parses `1*5DIGIT` without leading zeroes into a `u16` port or count.
3547fn parse_1_5_digits(s: &str) -> Option<u16> {
3548    if s.is_empty()
3549        || s.len() > 5
3550        || !s.chars().all(|c| c.is_ascii_digit())
3551        || (s.len() > 1 && s.starts_with('0'))
3552    {
3553        return None;
3554    }
3555
3556    s.parse().ok()
3557}
3558
3559impl FromStr for SctpPort {
3560    type Err = AttributeError;
3561
3562    fn from_str(s: &str) -> Result<Self, Self::Err> {
3563        let invalid = || AttributeError::InvalidParamValue {
3564            param: "SCTP port".to_string(),
3565            val: s.to_string(),
3566            attr: <Self as TypedAttribute>::NAME.to_string(),
3567        };
3568
3569        // sctp-port-value = 1*5DIGIT, leading zeroes must not be used.
3570        let port = parse_1_5_digits(s).ok_or_else(invalid)?;
3571        Ok(SctpPort(port))
3572    }
3573}
3574
3575impl Display for SctpPort {
3576    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3577        write!(f, "{}", self.0)
3578    }
3579}
3580
3581impl TypedAttribute for SctpPort {
3582    const NAME: &'static str = "sctp-port";
3583}
3584
3585/// Maximum SCTP user message size attribute (`a=max-message-size`)
3586///
3587/// Indicates the maximum SCTP user message size, in bytes, that the
3588/// endpoint is willing to receive, where zero means any size. It is
3589/// used at the media level and defaults to 64 KiB when absent. Leading
3590/// zeroes are not used.
3591///
3592/// See [RFC 8841 Section 6.2](https://datatracker.ietf.org/doc/html/rfc8841#section-6.2).
3593#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3594#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3595pub struct MaxMessageSize(pub u64);
3596
3597impl FromStr for MaxMessageSize {
3598    type Err = AttributeError;
3599
3600    fn from_str(s: &str) -> Result<Self, Self::Err> {
3601        let invalid = || AttributeError::InvalidParamValue {
3602            param: "Max message size".to_string(),
3603            val: s.to_string(),
3604            attr: <Self as TypedAttribute>::NAME.to_string(),
3605        };
3606
3607        // max-message-size-value = 1*DIGIT, leading zeroes must not be used.
3608        let size = parse_decimal_no_leading_zeroes(s).ok_or_else(invalid)?;
3609        Ok(MaxMessageSize(size))
3610    }
3611}
3612
3613impl Display for MaxMessageSize {
3614    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3615        write!(f, "{}", self.0)
3616    }
3617}
3618
3619impl TypedAttribute for MaxMessageSize {
3620    const NAME: &'static str = "max-message-size";
3621}
3622
3623/// SCTP port map attribute (`a=sctpmap`)
3624///
3625/// Legacy association of the SCTP port with the application running on
3626/// the association and the number of streams, sent as
3627/// `a=sctpmap:<port> <app> [<streams>]`. It was defined only in
3628/// `draft-ietf-mmusic-sctp-sdp`, never published as an RFC, and is
3629/// superseded by `a=sctp-port` (RFC 8841, section 5).
3630///
3631/// See [draft-ietf-mmusic-sctp-sdp](https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-sctp-sdp).
3632#[derive(Debug, Clone, PartialEq, Eq)]
3633#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
3634pub struct SctpMap {
3635    /// SCTP port number, in the range 0 to 65535.
3636    pub number: u16,
3637    /// Application running on top of the association, e.g.
3638    /// `webrtc-datachannel`.
3639    pub app: String,
3640    /// Number of streams supported by each side of the association.
3641    pub streams: Option<u16>,
3642}
3643
3644impl SctpMap {
3645    pub fn new(number: u16, app: impl ToString, streams: Option<u16>) -> Self {
3646        SctpMap {
3647            number,
3648            app: app.to_string(),
3649            streams,
3650        }
3651    }
3652}
3653
3654impl FromStr for SctpMap {
3655    type Err = AttributeError;
3656
3657    fn from_str(s: &str) -> Result<Self, Self::Err> {
3658        let attr = <Self as TypedAttribute>::NAME.to_string();
3659        let mut tokens = s.split_ascii_whitespace();
3660
3661        let number = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3662            param: "SCTP port number".to_string(),
3663            attr: attr.clone(),
3664        })?;
3665        let number = parse_1_5_digits(number).ok_or_else(|| AttributeError::InvalidParamValue {
3666            param: "SCTP port number".to_string(),
3667            val: number.to_string(),
3668            attr: attr.clone(),
3669        })?;
3670
3671        let app = tokens.next().ok_or_else(|| AttributeError::ParamNotFound {
3672            param: "Application".to_string(),
3673            attr: attr.clone(),
3674        })?;
3675        if !is_token(app.as_bytes()) {
3676            return Err(AttributeError::InvalidParamValue {
3677                param: "Application".to_string(),
3678                val: app.to_string(),
3679                attr,
3680            });
3681        }
3682
3683        let streams = match tokens.next() {
3684            Some(streams) => Some(parse_1_5_digits(streams).ok_or_else(|| {
3685                AttributeError::InvalidParamValue {
3686                    param: "Stream count".to_string(),
3687                    val: streams.to_string(),
3688                    attr: attr.clone(),
3689                }
3690            })?),
3691            None => None,
3692        };
3693
3694        let trailing: Vec<_> = tokens.collect();
3695        if !trailing.is_empty() {
3696            return Err(AttributeError::UnexpectedTrailingItem {
3697                val: trailing.join(" "),
3698                attr,
3699            });
3700        }
3701
3702        Ok(SctpMap {
3703            number,
3704            app: app.to_string(),
3705            streams,
3706        })
3707    }
3708}
3709
3710impl Display for SctpMap {
3711    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3712        write!(f, "{} {}", self.number, self.app)?;
3713        if let Some(streams) = self.streams {
3714            write!(f, " {}", streams)?;
3715        }
3716        Ok(())
3717    }
3718}
3719
3720impl TypedAttribute for SctpMap {
3721    const NAME: &'static str = "sctpmap";
3722}
3723
3724#[cfg(test)]
3725mod tests {
3726    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
3727
3728    use super::*;
3729    use crate::*;
3730
3731    #[test]
3732    fn direction_parse() {
3733        assert_eq!("sendonly".parse::<Direction>(), Ok(Direction::SendOnly));
3734        assert_eq!("recvonly".parse::<Direction>(), Ok(Direction::RecvOnly));
3735        assert_eq!("sendrecv".parse::<Direction>(), Ok(Direction::SendRecv));
3736        assert_eq!("inactive".parse::<Direction>(), Ok(Direction::Inactive));
3737        assert!("invalid".parse::<Direction>().is_err());
3738    }
3739
3740    #[test]
3741    fn direction_display() {
3742        assert_eq!(Direction::SendOnly.to_string(), "sendonly");
3743        assert_eq!(Direction::RecvOnly.to_string(), "recvonly");
3744        assert_eq!(Direction::SendRecv.to_string(), "sendrecv");
3745        assert_eq!(Direction::Inactive.to_string(), "inactive");
3746    }
3747
3748    #[test]
3749    fn parse_rtcp_fb() {
3750        let sdp = "v=0\r
3751o=alice 3203093520 3203093520 IN IP4 host.example.com\r
3752s=Multicast video with feedback\r
3753t=3203130148 3203137348\r
3754m=audio 49170 RTP/AVP 0\r
3755c=IN IP4 224.2.1.183\r
3756a=rtpmap:0 PCMU/8000\r
3757m=video 51372 RTP/AVPF 98 99\r
3758c=IN IP4 224.2.1.184\r
3759a=rtpmap:98 H263-1998/90000\r
3760a=rtpmap:99 H261/90000\r
3761a=rtcp-fb:* nack\r
3762a=rtcp-fb:98 nack rpsi\r
3763a=rtcp-fb:* trr-int 1000\r
3764a=rtcp-fb:98 ccm vbcm 1 2\r
3765a=rtcp-fb:* ccm tmmbr smaxpr=120\r
3766";
3767
3768        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3769        let mut written = vec![];
3770        parsed.write(&mut written).unwrap();
3771
3772        let v = fallible_iterator::convert(parsed.medias[1].attributes_typed::<RtcpFb>())
3773            .collect::<Vec<_>>()
3774            .expect("Valid vector of attributes");
3775        assert_eq!(v[0].pt, RtcpFbPt::Wildcard);
3776        assert_eq!(v[1].val, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)));
3777        assert_eq!(v[2].val, RtcpFbVal::TrrInt(1000));
3778        assert_eq!(v[3].val, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(vec![1, 2])));
3779        assert_eq!(
3780            v[4].val,
3781            RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(Some("smaxpr=120".to_string())))
3782        );
3783    }
3784
3785    #[test]
3786    fn parse_group_attribute() {
3787        let sdp = "v=0\r
3788o=Laura 289083124 289083124 IN IP4 two.example.com\r
3789c=IN IP4 233.252.0.1/127\r
3790t=0 0\r
3791a=group:LS 1 2\r
3792m=audio 30000 RTP/AVP 0\r
3793a=mid:1\r
3794m=video 30002 RTP/AVP 31\r
3795a=mid:2\r
3796m=audio 30004 RTP/AVP 0\r
3797i=This media stream contains the Spanish translation\r
3798a=mid:3\r
3799";
3800        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3801
3802        let g = parsed.attributes_typed::<Group>().collect::<Vec<_>>();
3803        assert_eq!(g.len(), 1);
3804        assert_eq!(g[0].as_ref().unwrap().semantics, GroupSemantics::LS);
3805        assert_eq!(
3806            g[0].as_ref().unwrap().mid_tags,
3807            vec!["1".to_string(), "2".to_string()]
3808        );
3809    }
3810
3811    #[test]
3812    fn parse_setup_attribute() {
3813        let sdp = "v=0\r
3814m=image 54111 TCP t38\r
3815c=IN IP4 192.0.2.2\r
3816a=setup:actpass\r
3817a=connection:new\r
3818";
3819        let media = Session::parse(sdp.as_bytes()).unwrap().medias;
3820
3821        let s = media[0].attributes_typed::<Setup>().collect::<Vec<_>>();
3822
3823        assert_eq!(s.len(), 1);
3824        assert_eq!(s[0].as_ref().unwrap().to_owned(), Setup::ActPass);
3825    }
3826
3827    #[test]
3828    fn parse_ssrc_attributes() {
3829        let sdp = "v=0\r
3830o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
3831m=video 49174 RTP/AVPF 96 98\r
3832a=rtpmap:98 rtx/90000\r
3833a=fmtp:98 apt=96;rtx-time=3000\r
3834a=ssrc-group:FID 11111 22222\r
3835a=ssrc:11111 cname:user3@example.com\r
3836a=ssrc:22222 fmtp:0 0-15\r
3837a=ssrc-group:FID 33333 44444\r
3838a=ssrc:33333 cname:user3@example.com\r
3839a=ssrc:44444 cname:user3@example.com\r
3840a=ssrc:1698359993 rtcp:5003 IN IP4 127.0.0.1
3841";
3842
3843        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3844        let m = &parsed.medias[0];
3845
3846        let ssrcs = m
3847            .attributes_typed::<Ssrc>()
3848            .filter(|s| {
3849                let Ok(ssrc) = s else { return false };
3850                ssrc.attribute == SsrcAttribute::Fmtp || ssrc.attribute == SsrcAttribute::Rtcp
3851            })
3852            .collect::<Vec<_>>();
3853
3854        let ssrc_id = ssrcs[0].as_ref().unwrap().ssrc_id;
3855
3856        let ssrc_groups = m
3857            .attributes_typed::<SsrcGroup>()
3858            .filter(|s| {
3859                let Ok(ssrc_group) = s else { return false };
3860
3861                ssrc_group.ssrc_ids[1] == ssrc_id
3862            })
3863            .collect::<Vec<_>>();
3864
3865        assert_eq!(
3866            ssrc_groups[0].as_ref().unwrap().semantics,
3867            GroupSemantics::FID
3868        );
3869
3870        assert_eq!(ssrcs[1].as_ref().unwrap().attribute, SsrcAttribute::Rtcp);
3871    }
3872
3873    #[test]
3874    fn parse_crypto_attributes() {
3875        let sdp = "v=0\r
3876o=sam 2890844526 2890842807 IN IP4 10.47.16.5\r
3877s=SRTP Discussion\r
3878i=A discussion of Secure RTP\r
3879u=http://www.example.com/seminars/srtp.pdf\r
3880e=marge@example.com (Marge Simpson)\r
3881c=IN IP4 168.2.17.12\r
3882t=2873397496 2873404696\r
3883m=audio 49170 RTP/SAVP 0\r
3884a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC\r
3885a=crypto:2 F8_128_HMAC_SHA1_80 inline:MTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5QUJjZGVm|2^20|1:4;inline:QUJjZGVmMTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5|2^20|2:4 FEC_ORDER=FEC_SRTP\r
3886m=video 51372 RTP/SAVP 31\r
3887a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2|1066:4\r
3888";
3889
3890        let parsed = Session::parse(sdp.as_bytes()).unwrap();
3891        let a = &parsed.medias[0];
3892
3893        let audio_cryptos = a.attributes_typed::<Crypto>().collect::<Vec<_>>();
3894
3895        assert_eq!(
3896            audio_cryptos[0].as_ref().unwrap().crypto_suite,
3897            CryptoSuite::AesCm128HmacSha1_80
3898        );
3899        assert_eq!(audio_cryptos[1].as_ref().unwrap().key_params.len(), 2);
3900
3901        assert_eq!(
3902            audio_cryptos[1].as_ref().unwrap().key_params[1].mki_and_length,
3903            Some((2, 4))
3904        );
3905
3906        assert_eq!(
3907            audio_cryptos[1].as_ref().unwrap().session_params[0],
3908            SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
3909        );
3910
3911        let v = &parsed.medias[1];
3912
3913        let video_cryptos = v
3914            .attributes_typed::<Crypto>()
3915            .filter(|c| {
3916                let Ok(crypto) = c else { return false };
3917
3918                crypto.tag == 1
3919            })
3920            .collect::<Vec<_>>();
3921
3922        let test_crypto = Crypto {
3923            tag: 1,
3924            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
3925            key_params: vec![SrtpKeyParam {
3926                key_and_salt: "YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2".to_string(),
3927                lifetime: None,
3928                mki_and_length: Some((1066, 4)),
3929            }],
3930            session_params: Vec::new(),
3931        };
3932
3933        assert_eq!(&test_crypto, video_cryptos[0].as_ref().unwrap());
3934    }
3935
3936    #[test]
3937    fn write_crypto_attribute() {
3938        let crypto = Crypto {
3939            tag: 1,
3940            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
3941            key_params: vec![
3942                SrtpKeyParam {
3943                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
3944                    lifetime: Some(1048576),
3945                    mki_and_length: Some((1, 4)),
3946                },
3947                SrtpKeyParam {
3948                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
3949                    lifetime: Some(1048576),
3950                    mki_and_length: Some((1, 4)),
3951                },
3952            ],
3953            session_params: vec![SrtpSessionParam::FecOrder(FecOrder::SrtpFec)],
3954        };
3955
3956        assert_eq!(
3957            crypto.to_string(),
3958            "1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4;inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC"
3959        );
3960    }
3961
3962    #[test]
3963    fn parse_srtp_key_param_lifetime() {
3964        // RFC 4568 allows lifetimes up to 2^48, which doesn't fit into u32.
3965        let param = SrtpKeyParam::from_str("inline:YWJj|2^48").unwrap();
3966        assert_eq!(param.lifetime, Some(1 << 48));
3967        assert_eq!(param.to_string(), "inline:YWJj|2^48");
3968
3969        let param = SrtpKeyParam::from_str("inline:YWJj|1000000000000").unwrap();
3970        assert_eq!(param.lifetime, Some(1_000_000_000_000));
3971        assert_eq!(param.to_string(), "inline:YWJj|1000000000000");
3972
3973        for invalid in [
3974            // larger than the maximum of 2^48
3975            "inline:YWJj|2^49",
3976            "inline:YWJj|2^100",
3977            "inline:YWJj|281474976710657",
3978            // zero is not a positive integer
3979            "inline:YWJj|0",
3980            // leading zeroes and signs are not allowed
3981            "inline:YWJj|007",
3982            "inline:YWJj|0123",
3983            "inline:YWJj|2^007",
3984            "inline:YWJj|+1048576",
3985            "inline:YWJj|2^+20",
3986        ] {
3987            assert!(
3988                SrtpKeyParam::from_str(invalid).is_err(),
3989                "{} should be invalid",
3990                invalid
3991            );
3992        }
3993    }
3994
3995    #[test]
3996    fn parse_candidate_attributes() {
3997        use std::net::{Ipv4Addr, Ipv6Addr};
3998
3999        let sdp = "v=0\r
4000o=- 2890844526 2890842807 IN IP4 192.168.1.1\r
4001s=-\r
4002c=IN IP4 192.168.1.1\r
4003t=0 0\r
4004m=audio 49152 RTP/AVP 0\r
4005a=candidate:1 1 UDP 2130706432 192.168.1.1 49152 typ host raddr 10.0.1.1 rport 49153 generation 0\r
4006a=candidate:2 1 UDP 1692467200 10.0.1.1 49152 typ srflx raddr 192.168.1.1 rport 49153\r
4007a=candidate:3 2 UDP 1692467184 192.168.1.1 49153 typ host\r
4008a=candidate:4 1 UDP 100 2001:db8::1 49152 typ host\r
4009a=candidate:5 1 UDP 50 192.168.1.1 49154 typ prflx\r
4010a=candidate:6 1 UDP 25 192.168.1.1 49155 typ relay raddr 10.0.0.1 rport 49156\r
4011a=candidate:7 1 UDP 10 192.168.1.1 49157 typ unknown_type\r
4012";
4013
4014        let session = Session::parse(sdp.as_bytes()).unwrap();
4015        let candidates: Vec<Candidate> =
4016            fallible_iterator::convert(session.medias[0].attributes_typed::<Candidate>())
4017                .collect::<Vec<_>>()
4018                .expect("Valid vector of candidates");
4019
4020        assert_eq!(candidates.len(), 7);
4021
4022        assert_eq!(candidates[0].foundation, "1");
4023        assert_eq!(candidates[0].component_id, 1);
4024        assert_eq!(
4025            candidates[0].address,
4026            CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
4027        );
4028        assert_eq!(candidates[0].port, 49152);
4029        assert_eq!(candidates[0].typ, CandidateType::Host);
4030        assert_eq!(
4031            candidates[0].rel_addr,
4032            Some(IpAddr::V4(Ipv4Addr::new(10, 0, 1, 1)))
4033        );
4034        assert_eq!(candidates[0].rel_port, Some(49153));
4035        assert_eq!(
4036            candidates[0].extensions,
4037            vec![("generation".to_string(), "0".to_string())]
4038        );
4039
4040        assert_eq!(candidates[1].foundation, "2");
4041        assert_eq!(candidates[1].typ, CandidateType::Srflx);
4042        assert_eq!(
4043            candidates[1].rel_addr,
4044            Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
4045        );
4046        assert_eq!(candidates[1].rel_port, Some(49153));
4047
4048        assert_eq!(candidates[2].foundation, "3");
4049        assert_eq!(candidates[2].component_id, 2);
4050        assert_eq!(candidates[2].typ, CandidateType::Host);
4051
4052        assert_eq!(candidates[3].foundation, "4");
4053        assert_eq!(
4054            candidates[3].address,
4055            CandidateAddress::IpAddr(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)))
4056        );
4057        assert_eq!(candidates[3].typ, CandidateType::Host);
4058
4059        assert_eq!(candidates[4].foundation, "5");
4060        assert_eq!(candidates[4].typ, CandidateType::Prflx);
4061
4062        assert_eq!(candidates[5].foundation, "6");
4063        assert_eq!(candidates[5].typ, CandidateType::Relay);
4064
4065        assert_eq!(candidates[6].foundation, "7");
4066        assert_eq!(
4067            candidates[6].typ,
4068            CandidateType::Other("unknown_type".to_string())
4069        );
4070    }
4071
4072    #[test]
4073    fn write_candidate() {
4074        use std::net::Ipv4Addr;
4075
4076        let candidate = Candidate {
4077            foundation: "abcd/1234".into(),
4078            component_id: 1,
4079            transport: "UDP".into(),
4080            priority: 2130706432,
4081            address: CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
4082            port: 49152,
4083            typ: CandidateType::Srflx,
4084            rel_addr: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
4085            rel_port: Some(49153),
4086            extensions: vec![("tcptype".to_string(), "active".to_string())],
4087        };
4088
4089        assert_eq!(
4090            candidate.to_string(),
4091            "abcd/1234 1 UDP 2130706432 192.168.0.1 49152 typ srflx raddr 10.0.0.1 rport 49153 tcptype active"
4092        );
4093    }
4094
4095    #[test]
4096    fn test_attribute_errors() {
4097        // Test RtpMap error paths
4098        assert_eq!(
4099            "99".parse::<RtpMap>().unwrap_err(),
4100            AttributeError::UnsupportedFormat {
4101                val: "99".to_string(),
4102                attr: "rtpmap".to_string()
4103            }
4104        );
4105        assert_eq!(
4106            "abc 90000".parse::<RtpMap>().unwrap_err(),
4107            AttributeError::InvalidParamValue {
4108                param: "Payload type".to_string(),
4109                val: "abc".to_string(),
4110                attr: "rtpmap".to_string()
4111            }
4112        );
4113        assert_eq!(
4114            "200 enc/90000".parse::<RtpMap>().unwrap_err(),
4115            AttributeError::InvalidParamValue {
4116                param: "Payload type".to_string(),
4117                val: "200(expected 0-127)".to_string(),
4118                attr: "rtpmap".to_string()
4119            }
4120        );
4121        assert_eq!(
4122            "99 ".parse::<RtpMap>().unwrap_err(),
4123            AttributeError::ParamNotFound {
4124                param: "Clock rate".to_string(),
4125                attr: "rtpmap".to_string()
4126            }
4127        );
4128        assert_eq!(
4129            "99 /".parse::<RtpMap>().unwrap_err(),
4130            AttributeError::InvalidParamValue {
4131                param: "Clock rate".to_string(),
4132                val: "".to_string(),
4133                attr: "rtpmap".to_string()
4134            }
4135        );
4136
4137        assert_eq!(
4138            "invalid".parse::<Fmtp>().unwrap_err(),
4139            AttributeError::UnsupportedFormat {
4140                val: "invalid".to_string(),
4141                attr: "fmtp".to_string()
4142            }
4143        );
4144        assert_eq!(
4145            "abc profile=1".parse::<Fmtp>().unwrap_err(),
4146            AttributeError::InvalidParamValue {
4147                param: "fmtp".to_string(),
4148                val: "abc".to_string(),
4149                attr: "fmtp".to_string()
4150            }
4151        );
4152
4153        // Test Rtcp error paths
4154        assert_eq!(
4155            "".parse::<Rtcp>().unwrap_err(),
4156            AttributeError::ParamNotFound {
4157                param: "Port".to_string(),
4158                attr: "rtcp".to_string()
4159            }
4160        );
4161        assert_eq!(
4162            "abc IN IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4163            AttributeError::InvalidParamValue {
4164                param: "Port".to_string(),
4165                val: "abc".to_string(),
4166                attr: "rtcp".to_string()
4167            }
4168        );
4169
4170        // Test Fingerprint error paths
4171        assert_eq!(
4172            "".parse::<Fingerprint>().unwrap_err(),
4173            AttributeError::ParamNotFound {
4174                param: "Hash value".to_string(),
4175                attr: "fingerprint".to_string()
4176            }
4177        );
4178        assert_eq!(
4179            "SHA-1".parse::<Fingerprint>().unwrap_err(),
4180            AttributeError::ParamNotFound {
4181                param: "Hash value".to_string(),
4182                attr: "fingerprint".to_string()
4183            }
4184        );
4185
4186        // Test Candidate error paths
4187        assert_eq!(
4188            "".parse::<Candidate>().unwrap_err(),
4189            AttributeError::ParamNotFound {
4190                param: "Component id".to_string(),
4191                attr: "candidate".to_string()
4192            }
4193        );
4194        assert_eq!(
4195            "1 1 UDP 100".parse::<Candidate>().unwrap_err(),
4196            AttributeError::ParamNotFound {
4197                param: "Address".to_string(),
4198                attr: "candidate".to_string()
4199            }
4200        );
4201
4202        // Test ExtMap error paths
4203        assert_eq!(
4204            "".parse::<ExtMap>().unwrap_err(),
4205            AttributeError::InvalidParamValue {
4206                param: "Id".to_string(),
4207                val: "".to_string(),
4208                attr: "extmap".to_string()
4209            }
4210        );
4211        assert_eq!(
4212            "999999 http://example.com".parse::<ExtMap>().unwrap_err(),
4213            AttributeError::InvalidParamValue {
4214                param: "Id".to_string(),
4215                val: "999999".to_string(),
4216                attr: "extmap".to_string()
4217            }
4218        );
4219
4220        // Test Group error paths
4221        assert_eq!(
4222            "".parse::<Group>().unwrap_err(),
4223            AttributeError::ParamNotFound {
4224                param: "Semantics".to_string(),
4225                attr: "group".to_string()
4226            }
4227        );
4228        // The identification tags are optional in the grammar.
4229        assert_eq!(
4230            "LS".parse::<Group>().unwrap(),
4231            Group {
4232                semantics: GroupSemantics::LS,
4233                mid_tags: vec![],
4234            }
4235        );
4236        // Sloppy senders separate the fields with other whitespace than a
4237        // single space, which must not produce empty tags.
4238        let group = "bundle\t0  1 ".parse::<Group>().unwrap();
4239        assert_eq!(group.semantics, GroupSemantics::Bundle);
4240        assert_eq!(group.mid_tags, vec!["0".to_string(), "1".to_string()]);
4241        assert_eq!(group.to_string(), "BUNDLE 0 1");
4242
4243        // Ssrc groups split on runs of whitespace as well.
4244        let ssrc_group = "FEC\t123  456".parse::<SsrcGroup>().unwrap();
4245        assert_eq!(ssrc_group.semantics, GroupSemantics::FEC);
4246        assert_eq!(ssrc_group.ssrc_ids, vec![123, 456]);
4247
4248        // Test Ssrc error paths
4249        assert_eq!(
4250            "".parse::<Ssrc>().unwrap_err(),
4251            AttributeError::ParamNotFound {
4252                param: "Ssrc id".to_string(),
4253                attr: "ssrc".to_string()
4254            }
4255        );
4256        assert_eq!(
4257            "abc".parse::<Ssrc>().unwrap_err(),
4258            AttributeError::ParamNotFound {
4259                param: "Ssrc id".to_string(),
4260                attr: "ssrc".to_string()
4261            }
4262        );
4263
4264        // Test Setup error paths
4265        let setup_err = "foo".parse::<Setup>().err().unwrap();
4266        assert!(matches!(setup_err, AttributeError::Other { .. }));
4267        assert_eq!(format!("{}", setup_err), "setup: Invalid Setup value foo");
4268
4269        // Test Crypto error paths
4270        assert_eq!(
4271            "".parse::<Crypto>().unwrap_err(),
4272            AttributeError::InvalidParamValue {
4273                param: "Tag".to_string(),
4274                val: "".to_string(),
4275                attr: "crypto".to_string()
4276            }
4277        );
4278        assert_eq!(
4279            "abc AES_CM_128_HMAC_SHA1_32 inline:key"
4280                .parse::<Crypto>()
4281                .unwrap_err(),
4282            AttributeError::InvalidParamValue {
4283                param: "Tag".to_string(),
4284                val: "abc".to_string(),
4285                attr: "crypto".to_string()
4286            }
4287        );
4288
4289        // Test RtcpFb error paths
4290        assert_eq!(
4291            "".parse::<RtcpFb>().unwrap_err(),
4292            AttributeError::InvalidParamValue {
4293                param: "Payload format".to_string(),
4294                val: "".to_string(),
4295                attr: "rtcp-fb".to_string()
4296            }
4297        );
4298        assert_eq!(
4299            "*".parse::<RtcpFb>().unwrap_err(),
4300            AttributeError::ParamNotFound {
4301                param: "Rtcp feedback value".to_string(),
4302                attr: "rtcp-fb".to_string()
4303            }
4304        );
4305        assert_eq!(
4306            "1 ack ccfb".parse::<RtcpFb>().unwrap_err(),
4307            AttributeError::InvalidParamValue {
4308                param: "Payload type of Congestion control feedback (ccfb)".to_string(),
4309                val: "1(expected wildcard (*))".to_string(),
4310                attr: "rtcp-fb".to_string()
4311            }
4312        );
4313
4314        // Test attribute_typed with missing value
4315        let media = Media {
4316            media: "video".into(),
4317            port: 1234,
4318            num_ports: None,
4319            proto: "RTP/SAVPF".into(),
4320            fmt: "".into(),
4321            media_title: None,
4322            connections: vec![],
4323            bandwidths: vec![],
4324            key: None,
4325            attributes: vec![Attribute {
4326                attribute: "rtpmap".into(),
4327                value: None,
4328            }],
4329        };
4330        assert_eq!(
4331            media
4332                .attributes_typed::<RtpMap>()
4333                .collect::<Vec<Result<RtpMap, AttributeError>>>()
4334                .remove(0)
4335                .unwrap_err(),
4336            AttributeError::UnsupportedFormat {
4337                val: String::new(),
4338                attr: "rtpmap".to_string()
4339            }
4340        );
4341    }
4342
4343    #[test]
4344    #[allow(deprecated)]
4345    fn parse_rtcp_address() {
4346        let sdp = "v=0\r
4347o=alice 3203093520 3203093520 IN IP4 host.example.com\r
4348s=parse rtcp attribute address test\r
4349a=rtcp:5000 IN IP4 127.0.0.1\r
4350a=rtcp:5000 IN IP4 127.0.0.0/24\r
4351a=rtcp:5000 IN IP6 ::1\r
4352a=rtcp:5000 IN NONIP non-IP\r
4353a=rtcp:53020\r
4354";
4355
4356        let parsed = Session::parse(sdp.as_bytes()).unwrap();
4357        let mut rtcp_attr_iter = parsed.attributes_typed::<Rtcp>();
4358
4359        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4360        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
4361        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.1"));
4362        assert_eq!(
4363            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4364            IpAddr::V4(Ipv4Addr::LOCALHOST),
4365        );
4366
4367        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4368        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
4369        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.0/24"));
4370        // The `/24` suffix is parsed as the TTL of RFC 8866 multicast
4371        // notation, not as part of the address, see [`Rtcp::try_address`].
4372        assert_eq!(
4373            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4374            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 0)),
4375        );
4376        let address = rtcp.try_address().unwrap().unwrap();
4377        assert_eq!(address.ttl, Some(24));
4378        assert_eq!(address.count, None);
4379        assert!(!rtcp.is_multicast());
4380
4381        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4382        assert_eq!(rtcp.addrtype, Some(AddrType::Ip6));
4383        assert_eq!(rtcp.connection_address.as_deref(), Some("::1"));
4384        assert_eq!(
4385            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
4386            IpAddr::V6(Ipv6Addr::LOCALHOST),
4387        );
4388
4389        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4390        assert_eq!(rtcp.addrtype, Some(AddrType::Other("NONIP".to_string())));
4391        assert_eq!(rtcp.connection_address.as_deref(), Some("non-IP"));
4392        assert_eq!(
4393            rtcp.try_parse_connection_ip_address().unwrap().unwrap_err(),
4394            "non-IP",
4395        );
4396
4397        // RFC 3605: the address part is optional.
4398        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
4399        assert_eq!(rtcp.port, 53020);
4400        assert_eq!(rtcp.nettype, None);
4401        assert_eq!(rtcp.addrtype, None);
4402        assert_eq!(rtcp.connection_address, None);
4403        assert_eq!(rtcp.try_parse_connection_ip_address(), None);
4404        assert_eq!(rtcp.try_address(), None);
4405        assert!(!rtcp.is_multicast());
4406        assert_eq!(rtcp.to_string(), "53020");
4407    }
4408
4409    #[test]
4410    fn rtcp_multicast_address_helpers() {
4411        // RFC 3605: a=rtcp reuses the SDP connection-address token, so the
4412        // multicast `address/ttl[/count]` notation of RFC 8866, section 5.7
4413        // may also be used here. Parsing the notation itself is covered by
4414        // `connection_try_address`, here only its use through `a=rtcp` and
4415        // the address family inference that is specific to `a=rtcp`.
4416        let rtcp = "5000 IN IP4 233.252.0.1/127/3".parse::<Rtcp>().unwrap();
4417        assert!(rtcp.is_multicast());
4418        assert_eq!(
4419            rtcp.try_address().unwrap().unwrap(),
4420            ConnectionAddress {
4421                ip: Ipv4Addr::new(233, 252, 0, 1).into(),
4422                ttl: Some(127),
4423                count: Some(3),
4424            }
4425        );
4426
4427        // Without `addrtype` it is inferred from the address: IPv4 gets a
4428        // TTL, IPv6 the address count.
4429        let mut rtcp = Rtcp::with_port(5000);
4430        rtcp.connection_address = Some("224.2.1.1/16".to_string());
4431        let address = rtcp.try_address().unwrap().unwrap();
4432        assert_eq!(address.ttl, Some(16));
4433        assert_eq!(address.count, None);
4434
4435        let mut rtcp = Rtcp::with_port(5000);
4436        rtcp.connection_address = Some("ff12::5/3".to_string());
4437        let address = rtcp.try_address().unwrap().unwrap();
4438        assert_eq!(address.ttl, None);
4439        assert_eq!(address.count, Some(3));
4440
4441        let rtcp = "5000 IN IP6 ff12::5/3".parse::<Rtcp>().unwrap();
4442        assert!(rtcp.is_ipv6());
4443        assert!(!rtcp.is_ipv4());
4444        assert_eq!(rtcp.try_is_multicast(), Some(Ok(true)));
4445
4446        // Without the address part the multicast and family helpers report
4447        // `None`/`false`, `a=rtcp` only carries the port in that case.
4448        let rtcp = Rtcp::with_port(5000);
4449        assert_eq!(rtcp.try_is_multicast(), None);
4450        assert!(!rtcp.is_multicast());
4451        assert!(!rtcp.is_ipv4());
4452        assert!(!rtcp.is_ipv6());
4453
4454        // FQDN addresses are not parseable as `IpAddr`.
4455        let rtcp = "5000 IN IP4 rtcp.example.com".parse::<Rtcp>().unwrap();
4456        assert!(!rtcp.is_multicast());
4457        assert!(!rtcp.is_ipv4());
4458        assert_eq!(
4459            rtcp.try_is_multicast(),
4460            Some(Err(ConnectionAddressError::InvalidAddress(
4461                "rtcp.example.com".to_string()
4462            )))
4463        );
4464    }
4465
4466    #[test]
4467    fn parse_rtcp_attribute_whitespace() {
4468        // Runs of spaces are a single separator, like in o= and c= lines,
4469        // and empty fields are rejected instead of ending up as e.g. an
4470        // empty `nettype` or `addrtype`.
4471        assert_eq!(
4472            "6000  IN IP4 127.0.0.1".parse::<Rtcp>().unwrap(),
4473            Rtcp::new(6000, NetType::In, AddrType::Ip4, "127.0.0.1")
4474        );
4475        assert_eq!(
4476            "6001 IN  IP4 127.0.0.1 ".parse::<Rtcp>().unwrap(),
4477            Rtcp::new(6001, NetType::In, AddrType::Ip4, "127.0.0.1")
4478        );
4479        assert_eq!("53020 ".parse::<Rtcp>().unwrap(), Rtcp::with_port(53020));
4480
4481        assert!("6002 IN  127.0.0.1".parse::<Rtcp>().is_err());
4482        assert!("6003 IN IP4 127.0.0.1 extra".parse::<Rtcp>().is_err());
4483    }
4484
4485    #[test]
4486    fn parse_rtcp_nettype_and_addrtype_must_be_tokens() {
4487        // RFC 3605 reuses the SDP nettype and addrtype tokens (RFC 8866,
4488        // section 9), so reject other values like the o= and c= parsers do.
4489        assert_eq!(
4490            "6000 a<b IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4491            AttributeError::InvalidParamValue {
4492                param: "Network type".to_string(),
4493                val: "a<b".to_string(),
4494                attr: "rtcp".to_string(),
4495            }
4496        );
4497        assert_eq!(
4498            "6000 IN IP4/x 127.0.0.1".parse::<Rtcp>().unwrap_err(),
4499            AttributeError::InvalidParamValue {
4500                param: "Address type".to_string(),
4501                val: "IP4/x".to_string(),
4502                attr: "rtcp".to_string(),
4503            }
4504        );
4505    }
4506
4507    #[test]
4508    fn parse_rid_attribute() {
4509        // RFC 8851 examples.
4510        let rid: Rid = "5 send pt=99,102;max-br=64000".parse().unwrap();
4511        assert_eq!(
4512            rid,
4513            Rid::with_params("5", RidDirection::Send, ["pt=99,102", "max-br=64000"])
4514        );
4515        assert_eq!(rid.to_string(), "5 send pt=99,102;max-br=64000");
4516
4517        let rid: Rid = "6 send pt=100,97,101,102".parse().unwrap();
4518        assert_eq!(
4519            rid,
4520            Rid::with_params("6", RidDirection::Send, ["pt=100,97,101,102"])
4521        );
4522
4523        let rid: Rid = "f recv max-br=19.5;apt=100".parse().unwrap();
4524        assert_eq!(
4525            rid,
4526            Rid::with_params("f", RidDirection::Recv, ["max-br=19.5", "apt=100"])
4527        );
4528        assert_eq!(rid.to_string(), "f recv max-br=19.5;apt=100");
4529
4530        // Without parameters, and with a valueless parameter.
4531        let rid: Rid = "1 recv".parse().unwrap();
4532        assert_eq!(rid.direction, RidDirection::Recv);
4533        assert!(rid.params.is_empty());
4534        assert_eq!(rid.to_string(), "1 recv");
4535        assert_eq!(
4536            "2 send max-width".parse::<Rid>().unwrap(),
4537            Rid::with_params("2", RidDirection::Send, ["max-width"])
4538        );
4539
4540        // Trailing separators are tolerated, the direction is case-sensitive
4541        // (RFC 8851, section 10).
4542        assert_eq!(
4543            "2 send pt=99;".parse::<Rid>().unwrap(),
4544            Rid::with_params("2", RidDirection::Send, ["pt=99"])
4545        );
4546        assert!("2 SEND pt=99;".parse::<Rid>().is_err());
4547
4548        for invalid in [
4549            "",
4550            "1",
4551            " send",
4552            // The ID is restricted to alphanumeric, "-" and "_"
4553            // (RFC 8851, section 10):
4554            "1! send",
4555            "id:1 recv",
4556            "1 up",
4557            "1 send x y",
4558            "1 send pt=1;max br=2",
4559        ] {
4560            assert!(
4561                invalid.parse::<Rid>().is_err(),
4562                "expected error for {:?}",
4563                invalid
4564            );
4565        }
4566    }
4567
4568    #[test]
4569    fn parse_key_mgmt_attribute() {
4570        let key_mgmt: KeyMgmt = "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0".parse().unwrap();
4571        assert_eq!(
4572            key_mgmt,
4573            KeyMgmt::with_data(KeyMgmtMethod::Mikey, "AAAAIklkAAAAdGV4dHRleHR0ZXh0")
4574        );
4575        assert_eq!(key_mgmt.to_string(), "mikey AAAAIklkAAAAdGV4dHRleHR0ZXh0");
4576
4577        // Unknown methods are kept verbatim; KMPIDs are case-sensitive
4578        // (RFC 4567 section 3), so `MIKEY` is not the `mikey` method.
4579        for method in ["rawurl", "MIKEY", "internal"] {
4580            let key_mgmt: KeyMgmt = format!("{} data", method).parse().unwrap();
4581            assert_eq!(key_mgmt.method, KeyMgmtMethod::Other(method.to_string()));
4582            assert_eq!(key_mgmt.data.as_deref(), Some("data"));
4583            assert_eq!(key_mgmt.to_string(), format!("{} data", method));
4584        }
4585
4586        // Method without data.
4587        let key_mgmt: KeyMgmt = "internal".parse().unwrap();
4588        assert_eq!(
4589            key_mgmt,
4590            KeyMgmt::new(KeyMgmtMethod::Other("internal".to_string()))
4591        );
4592        assert_eq!(key_mgmt.to_string(), "internal");
4593
4594        // Data is kept verbatim, including inner whitespace.
4595        let key_mgmt: KeyMgmt = "mikey bar  baz".parse().unwrap();
4596        assert_eq!(key_mgmt.data.as_deref(), Some("bar  baz"));
4597        assert_eq!(key_mgmt.to_string(), "mikey bar  baz");
4598
4599        assert!("".parse::<KeyMgmt>().is_err());
4600        assert!("   ".parse::<KeyMgmt>().is_err());
4601        // The method is restricted to alphanumeric (RFC 4567, section 3):
4602        assert!("mi!key data".parse::<KeyMgmt>().is_err());
4603        assert!("-mikey".parse::<KeyMgmt>().is_err());
4604    }
4605
4606    #[test]
4607    fn parse_remote_candidate_attribute() {
4608        let candidates: RemoteCandidates = "1 192.168.1.1 50000".parse().unwrap();
4609        assert_eq!(
4610            candidates,
4611            RemoteCandidates::new([RemoteCandidate::new(
4612                1,
4613                "192.168.1.1".parse::<IpAddr>().unwrap(),
4614                50000
4615            )])
4616        );
4617        assert_eq!(candidates.to_string(), "1 192.168.1.1 50000");
4618
4619        // Multiple entries in a single attribute (RFC 8839, section 5.2).
4620        let candidates: RemoteCandidates = "1 192.0.2.3 45664 2 192.0.2.3 45665".parse().unwrap();
4621        assert_eq!(candidates.0.len(), 2);
4622        assert_eq!(candidates.0[1].component_id, 2);
4623        assert_eq!(
4624            candidates.to_string(),
4625            "1 192.0.2.3 45664 2 192.0.2.3 45665"
4626        );
4627
4628        // IPv6 and the value parsed on its own.
4629        let candidate: RemoteCandidate = "1 2001:db8::1 9".parse().unwrap();
4630        assert_eq!(candidate.address, "2001:db8::1".parse::<IpAddr>().unwrap());
4631        assert_eq!(candidate.to_string(), "1 2001:db8::1 9");
4632
4633        for invalid in [
4634            "",
4635            "1",
4636            "1 10.0.0.1",
4637            "x 10.0.0.1 50000",
4638            "1 x 50000",
4639            // FQDNs must not be used (RFC 8839, section 5.1).
4640            "1 example.com 50000",
4641            "1 10.0.0.1 70000",
4642            "1 10.0.0.1 50000 2",
4643        ] {
4644            assert!(
4645                invalid.parse::<RemoteCandidates>().is_err(),
4646                "expected error for {:?}",
4647                invalid
4648            );
4649        }
4650    }
4651
4652    #[test]
4653    fn parse_source_filter_attribute() {
4654        // RFC 4570, section 3.2 examples.
4655        let filter: SourceFilter = "incl IN IP4 232.3.4.5 192.0.2.10".parse().unwrap();
4656        assert_eq!(filter.mode, SourceFilterMode::Incl);
4657        assert_eq!(filter.nettype, NetType::In);
4658        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip4);
4659        assert_eq!(
4660            filter.destination,
4661            SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
4662                "232.3.4.5".parse().unwrap()
4663            ))
4664        );
4665        assert_eq!(
4666            filter.sources,
4667            ["192.0.2.10".parse::<SourceFilterAddress>().unwrap()]
4668        );
4669        assert_eq!(filter.to_string(), "incl IN IP4 232.3.4.5 192.0.2.10");
4670
4671        // Unicast destination.
4672        let filter: SourceFilter = "excl IN IP4 192.0.2.11 192.0.2.10".parse().unwrap();
4673        assert_eq!(filter.mode, SourceFilterMode::Excl);
4674        assert_eq!(filter.to_string(), "excl IN IP4 192.0.2.11 192.0.2.10");
4675
4676        // Destination wildcard.
4677        let filter: SourceFilter = "incl IN IP4 * 192.0.2.10".parse().unwrap();
4678        assert_eq!(filter.destination, SourceFilterDestination::Wildcard);
4679        assert_eq!(filter.to_string(), "incl IN IP4 * 192.0.2.10");
4680
4681        // Multiple space-separated sources.
4682        let filter: SourceFilter = "incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
4683            .parse()
4684            .unwrap();
4685        assert_eq!(filter.sources.len(), 2);
4686        assert_eq!(
4687            filter.to_string(),
4688            "incl IN IP4 224.2.1.1 192.0.2.10 192.0.2.42"
4689        );
4690
4691        // IPv6.
4692        let filter: SourceFilter = "incl IN IP6 ff0e::11a 2001:db8::1".parse().unwrap();
4693        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
4694        assert_eq!(
4695            filter.destination,
4696            SourceFilterDestination::Address(SourceFilterAddress::IpAddr(
4697                "ff0e::11a".parse().unwrap()
4698            ))
4699        );
4700        assert_eq!(filter.to_string(), "incl IN IP6 ff0e::11a 2001:db8::1");
4701
4702        // FQDNs with the "*" address type.
4703        let filter: SourceFilter = "incl IN * channel-1.example.com src-1.example.com"
4704            .parse()
4705            .unwrap();
4706        assert_eq!(filter.addrtype, SourceFilterAddrType::Any);
4707        assert_eq!(
4708            filter.destination,
4709            SourceFilterDestination::Address(SourceFilterAddress::FQDN(
4710                "channel-1.example.com".to_string()
4711            ))
4712        );
4713        assert_eq!(
4714            filter.sources,
4715            [SourceFilterAddress::FQDN("src-1.example.com".to_string())]
4716        );
4717        assert_eq!(
4718            filter.to_string(),
4719            "incl IN * channel-1.example.com src-1.example.com"
4720        );
4721
4722        // The filter mode is case-insensitive (RFC 4570, appendix A), the
4723        // address type is the SDP addrtype token and case-sensitive.
4724        let filter: SourceFilter = "EXCL IN IP6 ff0e::11a 2001:db8::1".parse().unwrap();
4725        assert_eq!(filter.mode, SourceFilterMode::Excl);
4726        assert_eq!(filter.addrtype, SourceFilterAddrType::Ip6);
4727        assert!("excl IN ip6 ff0e::11a 2001:db8::1"
4728            .parse::<SourceFilter>()
4729            .is_err());
4730
4731        for invalid in [
4732            "",
4733            "incl",
4734            "incl IN",
4735            "incl IN IP4",
4736            "incl IN IP4 232.3.4.5",
4737            // No /ttl or /number of addresses suffixes on the destination
4738            // (RFC 4570 section 3.1):
4739            "incl IN IP4 232.3.4.5/127 192.0.2.10",
4740            // The "*" address type only with FQDN destinations (section 3.1):
4741            "incl IN * 232.3.4.5 192.0.2.10",
4742            // The "*" wildcard is not allowed in the source list (appendix A):
4743            "incl IN IP4 232.3.4.5 *",
4744            "unknown IN IP4 232.3.4.5 192.0.2.10",
4745            // The network type is a token (RFC 4570 appendix A):
4746            "incl IN(IP4 232.3.4.5 192.0.2.10",
4747            "incl IN IP8 232.3.4.5 192.0.2.10",
4748        ] {
4749            assert!(
4750                invalid.parse::<SourceFilter>().is_err(),
4751                "expected error for {:?}",
4752                invalid
4753            );
4754        }
4755    }
4756
4757    #[test]
4758    fn parse_mid_attribute() {
4759        let mid: Mid = "1".parse().unwrap();
4760        assert_eq!(mid, Mid("1".to_string()));
4761        assert_eq!(mid.to_string(), "1");
4762
4763        let attribute: Attribute = mid.clone().into();
4764        assert_eq!(attribute.attribute, "mid");
4765        assert_eq!(attribute.value.as_deref(), Some("1"));
4766
4767        // Media descriptions commonly use opaque, browser-style tags.
4768        let mid: Mid = "v2R1d1".parse().unwrap();
4769        assert_eq!(mid.to_string(), "v2R1d1");
4770
4771        for invalid in [
4772            "", " ", // The identification tag is a token (RFC 8866, section 5).
4773            "1 2", "a:b",
4774        ] {
4775            assert!(
4776                invalid.parse::<Mid>().is_err(),
4777                "expected error for {:?}",
4778                invalid
4779            );
4780        }
4781    }
4782
4783    #[test]
4784    fn parse_rtcp_mux_attribute() {
4785        assert_eq!("".parse::<RtcpMux>().unwrap(), RtcpMux);
4786
4787        let attribute: Attribute = RtcpMux.into();
4788        assert_eq!(attribute.attribute, "rtcp-mux");
4789        assert_eq!(attribute.value, None);
4790
4791        // A property attribute must not carry a value.
4792        assert!("1".parse::<RtcpMux>().is_err());
4793
4794        let sdp = "v=0\r
4795o=alice 3203093520 3203093520 IN IP4 host.example.com\r
4796s=Property attribute test\r
4797t=3203130148 3203137348\r
4798m=audio 49170 RTP/AVP 0\r
4799a=rtcp-mux\r
4800";
4801        let parsed = Session::parse(sdp.as_bytes()).unwrap();
4802        assert_eq!(
4803            parsed.medias[0].get_first_attribute_typed::<RtcpMux>(),
4804            Some(Ok(RtcpMux))
4805        );
4806    }
4807
4808    #[test]
4809    fn parse_rtcp_mux_only_attribute() {
4810        assert!(matches!("".parse::<RtcpMuxOnly>(), Ok(RtcpMuxOnly)));
4811
4812        let attribute: Attribute = RtcpMuxOnly.into();
4813        assert_eq!(attribute.attribute, "rtcp-mux-only");
4814        assert_eq!(attribute.value, None);
4815
4816        // A property attribute must not carry a value.
4817        assert!("1".parse::<RtcpMuxOnly>().is_err());
4818    }
4819
4820    #[test]
4821    fn parse_rtcp_rsize_attribute() {
4822        assert!(matches!("".parse::<RtcpRsize>(), Ok(RtcpRsize)));
4823
4824        let attribute: Attribute = RtcpRsize.into();
4825        assert_eq!(attribute.attribute, "rtcp-rsize");
4826        assert_eq!(attribute.value, None);
4827
4828        // A property attribute must not carry a value.
4829        assert!("1".parse::<RtcpRsize>().is_err());
4830    }
4831
4832    #[test]
4833    fn parse_ice_lite_attribute() {
4834        assert!(matches!("".parse::<IceLite>(), Ok(IceLite)));
4835
4836        let attribute: Attribute = IceLite.into();
4837        assert_eq!(attribute.attribute, "ice-lite");
4838        assert_eq!(attribute.value, None);
4839
4840        // A property attribute must not carry a value.
4841        assert!("1".parse::<IceLite>().is_err());
4842    }
4843
4844    #[test]
4845    fn parse_ice_ufrag_attribute() {
4846        let ufrag: IceUfrag = "EpGh".parse().unwrap();
4847        assert_eq!(ufrag, IceUfrag("EpGh".to_string()));
4848        assert_eq!(ufrag.to_string(), "EpGh");
4849
4850        let attribute: Attribute = ufrag.into();
4851        assert_eq!(attribute.attribute, "ice-ufrag");
4852        assert_eq!(attribute.value.as_deref(), Some("EpGh"));
4853
4854        // All ICE characters are allowed, including "+" and "/" (RFC 5245,
4855        // section 15.1).
4856        assert_eq!("a+Z/9".parse::<IceUfrag>().unwrap().to_string(), "a+Z/9");
4857
4858        // The 256 characters upper bound.
4859        assert!("a".repeat(256).parse::<IceUfrag>().is_ok());
4860
4861        let too_long = "a".repeat(257);
4862        for invalid in [
4863            "",
4864            "abc",             // shorter than 4 characters
4865            too_long.as_str(), // longer than 256 characters
4866            "Ep-Gh",           // "-" is not an ICE character
4867            "Ep Gh",           // neither is the space
4868            "\u{30b0}pGh",     // non-ASCII
4869        ] {
4870            assert!(
4871                invalid.parse::<IceUfrag>().is_err(),
4872                "expected error for {:?}",
4873                invalid
4874            );
4875        }
4876    }
4877
4878    #[test]
4879    fn parse_ice_pwd_attribute() {
4880        let password = "aq1s2dq3w4er5ty6ui7p8o9i0u";
4881        let pwd: IcePwd = password.parse().unwrap();
4882        assert_eq!(pwd, IcePwd(password.to_string()));
4883        assert_eq!(pwd.to_string(), password);
4884
4885        let attribute: Attribute = pwd.into();
4886        assert_eq!(attribute.attribute, "ice-pwd");
4887        assert_eq!(attribute.value.as_deref(), Some(password));
4888
4889        // The shortest and longest valid passwords (RFC 5245, section 15.4).
4890        let shortest = "a".repeat(22);
4891        assert_eq!(shortest.parse::<IcePwd>().unwrap().to_string(), shortest);
4892        assert!("a".repeat(256).parse::<IcePwd>().is_ok());
4893
4894        let too_long = "a".repeat(257);
4895        for invalid in [
4896            "",
4897            "a".repeat(21).as_str(),          // shorter than 22 characters
4898            too_long.as_str(),                // longer than 256 characters
4899            "abcdefghijklmnopqr-stu",         // "-" is not an ICE character
4900            "\u{30b0}bcdefghijklmnopqrstuvw", // non-ASCII
4901        ] {
4902            assert!(
4903                invalid.parse::<IcePwd>().is_err(),
4904                "expected error for {:?}",
4905                invalid
4906            );
4907        }
4908    }
4909
4910    #[test]
4911    fn parse_ice_options_attribute() {
4912        let options: IceOptions = "trickle".parse().unwrap();
4913        assert_eq!(options, IceOptions::new(["trickle"]));
4914        assert_eq!(options.to_string(), "trickle");
4915
4916        // Multiple tags in a single attribute (RFC 5245, section 15.5).
4917        let options: IceOptions = "trickle renomination".parse().unwrap();
4918        assert_eq!(options, IceOptions::new(["trickle", "renomination"]));
4919        assert_eq!(options.to_string(), "trickle renomination");
4920
4921        let attribute: Attribute = options.into();
4922        assert_eq!(attribute.attribute, "ice-options");
4923        assert_eq!(attribute.value.as_deref(), Some("trickle renomination"));
4924
4925        for invalid in ["", "   ", "tr-ickle"] {
4926            assert!(
4927                invalid.parse::<IceOptions>().is_err(),
4928                "expected error for {:?}",
4929                invalid
4930            );
4931        }
4932    }
4933
4934    #[test]
4935    fn parse_end_of_candidates_attribute() {
4936        assert!(matches!("".parse::<EndOfCandidates>(), Ok(EndOfCandidates)));
4937
4938        let attribute: Attribute = EndOfCandidates.into();
4939        assert_eq!(attribute.attribute, "end-of-candidates");
4940        assert_eq!(attribute.value, None);
4941
4942        // A property attribute must not carry a value.
4943        assert!("1".parse::<EndOfCandidates>().is_err());
4944    }
4945
4946    #[test]
4947    fn parse_msid_attribute() {
4948        // RFC 8830, section 2 example.
4949        let msid: Msid = "examplefoo examplebar".parse().unwrap();
4950        assert_eq!(msid, Msid::new("examplefoo", Some("examplebar")));
4951        assert_eq!(msid.to_string(), "examplefoo examplebar");
4952
4953        let attribute: Attribute = msid.into();
4954        assert_eq!(attribute.attribute, "msid");
4955        assert_eq!(attribute.value.as_deref(), Some("examplefoo examplebar"));
4956
4957        // The appdata is optional; "-" means "no MediaStream" and is a valid
4958        // token (RFC 8830, section 3).
4959        let msid: Msid = "-".parse().unwrap();
4960        assert_eq!(msid, Msid::new("-", None::<&str>));
4961        assert_eq!(msid.to_string(), "-");
4962
4963        let too_long = "a".repeat(65);
4964        for invalid in [
4965            "",
4966            too_long.as_str(), // longer than 64 characters
4967            "foo \u{fffd}",    // non-token character
4968            "foo bar baz",     // more than two fields
4969        ] {
4970            assert!(
4971                invalid.parse::<Msid>().is_err(),
4972                "expected error for {:?}",
4973                invalid
4974            );
4975        }
4976    }
4977
4978    #[test]
4979    fn parse_sctp_port_attribute() {
4980        // RFC 8841, section 5.2 example.
4981        let port: SctpPort = "5000".parse().unwrap();
4982        assert_eq!(port, SctpPort(5000));
4983        assert_eq!(port.to_string(), "5000");
4984
4985        let attribute: Attribute = port.into();
4986        assert_eq!(attribute.attribute, "sctp-port");
4987        assert_eq!(attribute.value.as_deref(), Some("5000"));
4988
4989        // The port range includes 0 and 65535 (RFC 8841, section 5.2).
4990        assert_eq!("0".parse::<SctpPort>().unwrap(), SctpPort(0));
4991        assert_eq!("65535".parse::<SctpPort>().unwrap(), SctpPort(65535));
4992
4993        for invalid in [
4994            "", "65536",  // out of range
4995            "99999",  // 5 digits but out of range
4996            "05000",  // leading zeroes must not be used
4997            "500000", // more than 5 digits
4998            "abc", "50a",
4999        ] {
5000            assert!(
5001                invalid.parse::<SctpPort>().is_err(),
5002                "expected error for {:?}",
5003                invalid
5004            );
5005        }
5006    }
5007
5008    #[test]
5009    fn parse_max_message_size_attribute() {
5010        // RFC 8841, section 6.2 example.
5011        let size: MaxMessageSize = "100000".parse().unwrap();
5012        assert_eq!(size, MaxMessageSize(100000));
5013        assert_eq!(size.to_string(), "100000");
5014
5015        let attribute: Attribute = size.into();
5016        assert_eq!(attribute.attribute, "max-message-size");
5017        assert_eq!(attribute.value.as_deref(), Some("100000"));
5018
5019        // Zero means messages of any size (RFC 8841, section 6.1).
5020        assert_eq!("0".parse::<MaxMessageSize>().unwrap(), MaxMessageSize(0));
5021
5022        let too_large = "1".repeat(21);
5023        for invalid in [
5024            "",
5025            "01", // leading zeroes must not be used
5026            "1_000",
5027            "abc",
5028            "10a",
5029            too_large.as_str(), // larger than u64::MAX
5030        ] {
5031            assert!(
5032                invalid.parse::<MaxMessageSize>().is_err(),
5033                "expected error for {:?}",
5034                invalid
5035            );
5036        }
5037    }
5038
5039    #[test]
5040    fn parse_sctpmap_attribute() {
5041        // Real-world legacy syntax.
5042        let map: SctpMap = "5000 webrtc-datachannel 256".parse().unwrap();
5043        assert_eq!(map, SctpMap::new(5000, "webrtc-datachannel", Some(256)));
5044        assert_eq!(map.to_string(), "5000 webrtc-datachannel 256");
5045
5046        let attribute: Attribute = map.into();
5047        assert_eq!(attribute.attribute, "sctpmap");
5048        assert_eq!(
5049            attribute.value.as_deref(),
5050            Some("5000 webrtc-datachannel 256")
5051        );
5052
5053        // The stream count is optional and zero is allowed.
5054        let map: SctpMap = "9 webrtc-datachannel".parse().unwrap();
5055        assert_eq!(map.streams, None);
5056        assert_eq!(map.to_string(), "9 webrtc-datachannel");
5057        assert_eq!("0 app 0".parse::<SctpMap>().unwrap().streams, Some(0));
5058
5059        for invalid in [
5060            "",
5061            "5000",               // application missing
5062            "65536 app",          // port out of range
5063            "05000 app",          // leading zeroes
5064            "app app",            // port not a number
5065            "5000 app 70000",     // stream count out of range
5066            "5000 a:pp",          // application not a token
5067            "5000 app 256 extra", // trailing item
5068        ] {
5069            assert!(
5070                invalid.parse::<SctpMap>().is_err(),
5071                "expected error for {:?}",
5072                invalid
5073            );
5074        }
5075    }
5076}