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::{builders, enums::*, parser::is_token, Attribute};
15
16/// Trait for Typed Attribute structs
17pub trait TypedAttribute: Display + FromStr<Err = AttributeError> {
18    const NAME: &'static str;
19}
20
21impl<T: TypedAttribute> From<T> for Attribute {
22    fn from(attr: T) -> Attribute {
23        Attribute {
24            attribute: T::NAME.to_string(),
25            value: Some(attr.to_string()),
26        }
27    }
28}
29
30/// Attribute error with specific details
31#[derive(Debug, PartialEq, Eq, thiserror::Error)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub enum AttributeError {
34    /// If an Attribute is not found in a media or the session
35    #[error("Attribute {} not found", .0)]
36    NotFound(String),
37    /// If a parameter is missing in an attribute
38    #[error("Param {} not found in {}", .param, .attr)]
39    ParamNotFound { param: String, attr: String },
40    /// If a parameter value is not valid type or not in range
41    #[error("Invalid value {} for Param {} in {}", .val ,.param, .attr)]
42    InvalidParamValue {
43        param: String,
44        val: String,
45        attr: String,
46    },
47    /// If an attribute is not in expected format
48    #[error("Unsupported attribute format: {} for {}", .val, .attr)]
49    UnsupportedFormat { val: String, attr: String },
50    /// If there are more than expected items trailing in the attribute parameters
51    #[error("Unexpected trailing item {} for in {}", .val, .attr)]
52    UnexpectedTrailingItem { val: String, attr: String },
53    /// Unspecified error
54    #[error("{}: {}", .attr, .error)]
55    Other { error: String, attr: String },
56}
57
58impl AttributeError {
59    pub fn is_attribute_not_found(&self) -> bool {
60        matches!(self, AttributeError::NotFound(_))
61    }
62
63    /// Preprends the provided `param_context` to the parameter field of this AttributeError if applicable
64    fn add_param_context(&mut self, param_context: &str) {
65        use AttributeError::*;
66        match self {
67            NotFound(_)
68            | Other { .. }
69            | UnsupportedFormat { .. }
70            | UnexpectedTrailingItem { .. } => (),
71            ParamNotFound { param, .. } | InvalidParamValue { param, .. } => {
72                param.push(')');
73                param.insert_str(0, " (");
74                param.insert_str(0, param_context);
75            }
76        }
77    }
78
79    /// Sets the attribute field of this AttributeError to new_attr
80    pub(crate) fn set_attr(&mut self, new_attr: impl ToString) {
81        use AttributeError::*;
82        match self {
83            NotFound(attr)
84            | ParamNotFound { attr, .. }
85            | InvalidParamValue { attr, .. }
86            | UnsupportedFormat { attr, .. }
87            | UnexpectedTrailingItem { attr, .. }
88            | Other { attr, .. } => *attr = new_attr.to_string(),
89        }
90    }
91}
92
93pub(crate) trait ErrorContext {
94    fn with_param_context(self, param_context: &str) -> Self;
95    fn with_attr(self, new_attr: impl ToString) -> Self;
96}
97
98impl<T> ErrorContext for Result<T, AttributeError> {
99    /// Returns this error with the provided `param_context` prepended to the parameter field if applicable
100    fn with_param_context(self, param_context: &str) -> Self {
101        self.map_err(|mut err| {
102            err.add_param_context(param_context);
103            err
104        })
105    }
106
107    /// Returns this error with the attribute field set to new_attr
108    fn with_attr(self, new_attr: impl ToString) -> Self {
109        self.map_err(|mut err| {
110            err.set_attr(new_attr);
111            err
112        })
113    }
114}
115
116/// RtpMap Attribute
117///
118/// See [RFC 8866 Section 6.6](https://datatracker.ietf.org/doc/html/rfc8866#section-6.6) for more details
119#[derive(Debug, Clone, PartialEq, Eq)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121pub struct RtpMap {
122    /// Payload type, a numerical value between 0 and 127
123    pub payload_type: u8,
124    /// Name of the encoding
125    // TODO: is it useful to have an enum for all known encoding?
126    pub encoding_name: String,
127    /// Clock rate
128    pub clock_rate: u32,
129    /// Encoding parameters.
130    ///
131    /// Currently used only for audio channel count
132    pub encoding_params: Option<String>,
133}
134
135impl RtpMap {
136    pub fn new(payload_type: u8, encoding_name: impl ToString, clock_rate: u32) -> Self {
137        RtpMap {
138            payload_type,
139            encoding_name: encoding_name.to_string(),
140            clock_rate,
141            encoding_params: None,
142        }
143    }
144
145    pub fn builder(
146        payload_type: u8,
147        encoding_name: impl ToString,
148        clock_rate: u32,
149    ) -> builders::RtpMap {
150        builders::RtpMap::new(payload_type, encoding_name, clock_rate)
151    }
152
153    pub fn with_encoding_params(
154        payload_type: u8,
155        encoding_name: impl ToString,
156        clock_rate: u32,
157        encoding_params: impl ToString,
158    ) -> Self {
159        RtpMap {
160            payload_type,
161            encoding_name: encoding_name.to_string(),
162            clock_rate,
163            encoding_params: Some(encoding_params.to_string()),
164        }
165    }
166
167    pub fn set_encoding_params(&mut self, encoding_params: impl ToString) {
168        self.encoding_params = Some(encoding_params.to_string());
169    }
170}
171
172impl FromStr for RtpMap {
173    type Err = AttributeError;
174
175    fn from_str(s: &str) -> Result<Self, Self::Err> {
176        let Some((pt, rest)) = s.split_once(' ') else {
177            return Err(AttributeError::UnsupportedFormat {
178                val: s.to_string(),
179                attr: <Self as TypedAttribute>::NAME.to_string(),
180            });
181        };
182
183        let Ok(pt) = pt.parse::<u8>() else {
184            return Err(AttributeError::InvalidParamValue {
185                param: "Payload type".to_string(),
186                val: pt.to_string(),
187                attr: <Self as TypedAttribute>::NAME.to_string(),
188            });
189        };
190
191        if pt > 127 {
192            return Err(AttributeError::InvalidParamValue {
193                param: "Payload type".to_string(),
194                val: format!("{pt}(expected 0-127)"),
195                attr: <Self as TypedAttribute>::NAME.to_string(),
196            });
197        }
198
199        let mut i = rest.splitn(3, '/');
200        let Some(encoding) = i.next() else {
201            return Err(AttributeError::ParamNotFound {
202                param: "Encoding name".to_string(),
203                attr: <Self as TypedAttribute>::NAME.to_string(),
204            });
205        };
206
207        let Some(clock_rate) = i.next() else {
208            return Err(AttributeError::ParamNotFound {
209                param: "Clock rate".to_string(),
210                attr: <Self as TypedAttribute>::NAME.to_string(),
211            });
212        };
213
214        let Ok(clock_rate) = clock_rate.parse::<u32>() else {
215            return Err(AttributeError::InvalidParamValue {
216                param: "Clock rate".to_string(),
217                val: clock_rate.to_string(),
218                attr: <Self as TypedAttribute>::NAME.to_string(),
219            });
220        };
221
222        let params = i.next().map(String::from);
223
224        Ok(Self {
225            payload_type: pt,
226            encoding_name: encoding.to_owned(),
227            clock_rate,
228            encoding_params: params,
229        })
230    }
231}
232
233impl Display for RtpMap {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(
236            f,
237            "{} {}/{}",
238            self.payload_type, self.encoding_name, self.clock_rate
239        )?;
240        if let Some(params) = &self.encoding_params {
241            f.write_char('/')?;
242            f.write_str(params)?;
243        }
244        Ok(())
245    }
246}
247
248impl TypedAttribute for RtpMap {
249    const NAME: &'static str = "rtpmap";
250}
251
252/// Format specific parameters
253#[derive(Debug, Clone, PartialEq, Eq)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
255pub struct FmtpParam {
256    pub param: String,
257    pub val: Option<String>,
258}
259
260impl FmtpParam {
261    pub fn new(param: impl ToString) -> Self {
262        FmtpParam {
263            param: param.to_string(),
264            val: None,
265        }
266    }
267
268    pub fn builder(param: impl ToString) -> builders::FmtpParam {
269        builders::FmtpParam::new(param)
270    }
271
272    pub fn with_value(param: impl ToString, value: impl ToString) -> Self {
273        FmtpParam {
274            param: param.to_string(),
275            val: Some(value.to_string()),
276        }
277    }
278
279    pub fn set_value(&mut self, value: impl ToString) {
280        self.val = Some(value.to_string());
281    }
282}
283
284/// Format Parameters
285///
286/// See [RFC 8866 Section 6.15](https://datatracker.ietf.org/doc/html/rfc8866#section-6.15) for more details
287#[derive(Debug, Clone, PartialEq, Eq)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289pub struct Fmtp {
290    /// Payload format
291    pub fmt: u8,
292    /// Format specific parameters
293    // Multiple params are expected to be semicolon separated
294    // Each param can be a 'key=value' pair or just single parameter
295    pub format_specific_params: Vec<FmtpParam>,
296}
297
298impl Fmtp {
299    pub fn new(fmt: u8) -> Self {
300        Fmtp {
301            fmt,
302            format_specific_params: vec![],
303        }
304    }
305
306    pub fn builder(fmt: u8) -> builders::Fmtp {
307        builders::Fmtp::new(fmt)
308    }
309
310    pub fn add_format_specific_param(&mut self, format_specific_param: FmtpParam) {
311        self.format_specific_params.push(format_specific_param)
312    }
313
314    pub fn add_format_specific_params(
315        &mut self,
316        format_specific_params: impl IntoIterator<Item = FmtpParam>,
317    ) {
318        self.format_specific_params.extend(format_specific_params)
319    }
320}
321
322impl FromStr for Fmtp {
323    type Err = AttributeError;
324
325    fn from_str(s: &str) -> Result<Self, Self::Err> {
326        let Some((fmt, rest)) = s.split_once(' ') else {
327            return Err(AttributeError::UnsupportedFormat {
328                val: s.to_string(),
329                attr: <Self as TypedAttribute>::NAME.to_string(),
330            });
331        };
332
333        let Ok(fmt) = fmt.parse::<u8>() else {
334            return Err(AttributeError::InvalidParamValue {
335                param: "fmtp".to_string(),
336                val: fmt.to_string(),
337                attr: <Self as TypedAttribute>::NAME.to_string(),
338            });
339        };
340
341        let mut params: Vec<FmtpParam> = Vec::new();
342        for param in rest.split(';') {
343            if let Some((key, value)) = param.split_once('=') {
344                params.push(FmtpParam {
345                    param: key.to_string(),
346                    val: Some(value.to_string()),
347                });
348            } else {
349                params.push(FmtpParam {
350                    param: param.to_string(),
351                    val: None,
352                });
353            }
354        }
355
356        Ok(Self {
357            fmt,
358            format_specific_params: params,
359        })
360    }
361}
362
363impl Display for Fmtp {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        write!(f, "{} ", self.fmt)?;
366        let mut iter = self.format_specific_params.iter().peekable();
367        while let Some(p) = iter.next() {
368            write!(f, "{}", p.param)?;
369            if let Some(val) = &p.val {
370                f.write_char('=')?;
371                f.write_str(val.as_str())?;
372            }
373            if iter.peek().is_some() {
374                f.write_char(';')?;
375            }
376        }
377        Ok(())
378    }
379}
380
381impl TypedAttribute for Fmtp {
382    const NAME: &'static str = "fmtp";
383}
384
385/// RTCP port number and address
386///
387/// To be used if not algorithmically derived
388/// from the RTP port described in the media line
389///
390/// The address part is optional: when absent, RTCP is sent to the
391/// connection address given for the media.
392///
393/// See [RFC 3605 Section 2.1](https://datatracker.ietf.org/doc/html/rfc3605#section-2.1)
394#[derive(Debug, Clone, PartialEq, Eq)]
395#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
396pub struct Rtcp {
397    /// Port used for the RTCP stream
398    pub port: u16,
399    /// Network Type
400    ///
401    /// `None` if the address part is absent
402    pub nettype: Option<NetType>,
403    /// Address type
404    ///
405    /// `None` if the address part is absent
406    pub addrtype: Option<AddrType>,
407    /// Connection address: can be IP Address, unicast, multicast, ...
408    /// Conformity may be checked against the `addrtype`.
409    ///
410    /// `None` if the address part is absent
411    pub connection_address: Option<String>,
412}
413
414impl Rtcp {
415    /// Construct an [`Rtcp`] with only a port number, i.e. without an address
416    /// part.
417    ///
418    /// RTCP is then sent to the connection address given for the media.
419    pub fn with_port(port: u16) -> Self {
420        Rtcp {
421            port,
422            nettype: None,
423            addrtype: None,
424            connection_address: None,
425        }
426    }
427
428    /// Construct an [`Rtcp`] with the specified IP `connection_address`
429    pub fn with_ip_addr(port: u16, connection_address: impl Into<IpAddr>) -> Self {
430        let connection_address = connection_address.into();
431        Rtcp {
432            port,
433            nettype: Some(NetType::In),
434            addrtype: Some(connection_address.into()),
435            connection_address: Some(connection_address.to_string()),
436        }
437    }
438
439    /// Construct an [`Rtcp`]
440    ///
441    /// See also [`Rtcp::with_ip_addr`] and [`Rtcp::with_port`]
442    pub fn new(
443        port: u16,
444        nettype: NetType,
445        addrtype: AddrType,
446        connection_address: impl ToString,
447    ) -> Self {
448        Rtcp {
449            port,
450            nettype: Some(nettype),
451            addrtype: Some(addrtype),
452            connection_address: Some(connection_address.to_string()),
453        }
454    }
455
456    /// Tries to parse the `connection_address` `String` of `self` as `IpAddr`
457    ///
458    /// Returns `None` if the address part is absent, the `Ok` with the parsed
459    /// `IpAddr` or `Err` with the string address if parsing failed.
460    pub fn try_parse_connection_ip_address(&self) -> Option<Result<IpAddr, &str>> {
461        self.connection_address
462            .as_deref()
463            .map(|address| address.parse::<IpAddr>().map_err(|_| address))
464    }
465
466    /// Sets the `connection_address` & `addrtype` of `self` from the specified `IpAddr`
467    ///
468    /// Also sets the `nettype` to `IN` if it is not set yet.
469    pub fn set_connection_ip_address(&mut self, connection_address: impl Into<IpAddr>) {
470        let connection_address = connection_address.into();
471        self.nettype.get_or_insert(NetType::In);
472        self.addrtype = Some(connection_address.into());
473        self.connection_address = Some(connection_address.to_string());
474    }
475}
476
477impl FromStr for Rtcp {
478    type Err = AttributeError;
479
480    fn from_str(s: &str) -> Result<Self, Self::Err> {
481        // Runs of spaces are a single separator, like in o= and c= lines,
482        // so that no field can end up empty.
483        let mut i = s.split(' ').filter(|part| !part.is_empty());
484        let Some(port) = i.next() else {
485            return Err(AttributeError::ParamNotFound {
486                param: "Port".to_string(),
487                attr: <Self as TypedAttribute>::NAME.to_string(),
488            });
489        };
490
491        let Ok(port) = port.parse::<u16>() else {
492            return Err(AttributeError::InvalidParamValue {
493                param: "Port".to_string(),
494                val: port.to_string(),
495                attr: <Self as TypedAttribute>::NAME.to_string(),
496            });
497        };
498
499        // The address part is optional (RFC 3605): if the port is the only
500        // field, RTCP is sent to the media's connection address.
501        let Some(nettype) = i.next() else {
502            return Ok(Self::with_port(port));
503        };
504
505        // RFC 3605 reuses the SDP nettype and addrtype tokens (RFC 8866,
506        // section 9), same as in o= and c= lines.
507        if !is_token(nettype.as_bytes()) {
508            return Err(AttributeError::InvalidParamValue {
509                param: "Network type".to_string(),
510                val: nettype.to_string(),
511                attr: <Self as TypedAttribute>::NAME.to_string(),
512            });
513        }
514        let nettype = NetType::from_str(nettype).expect("infallible");
515
516        let Some(addrtype) = i.next() else {
517            return Err(AttributeError::ParamNotFound {
518                param: "Address type".to_string(),
519                attr: <Self as TypedAttribute>::NAME.to_string(),
520            });
521        };
522
523        if !is_token(addrtype.as_bytes()) {
524            return Err(AttributeError::InvalidParamValue {
525                param: "Address type".to_string(),
526                val: addrtype.to_string(),
527                attr: <Self as TypedAttribute>::NAME.to_string(),
528            });
529        }
530        let addrtype = AddrType::from_str(addrtype).expect("infallible");
531
532        let Some(connection_address) = i.next() else {
533            return Err(AttributeError::ParamNotFound {
534                param: "Connection address".to_string(),
535                attr: <Self as TypedAttribute>::NAME.to_string(),
536            });
537        };
538
539        if let Some(unexpected) = i.next() {
540            return Err(AttributeError::UnexpectedTrailingItem {
541                val: unexpected.to_string(),
542                attr: <Self as TypedAttribute>::NAME.to_string(),
543            });
544        }
545
546        Ok(Self {
547            port,
548            nettype: Some(nettype),
549            addrtype: Some(addrtype),
550            connection_address: Some(connection_address.to_string()),
551        })
552    }
553}
554
555impl Display for Rtcp {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        write!(f, "{}", self.port)?;
558        if let Some(nettype) = &self.nettype {
559            write!(f, " {nettype}")?;
560        }
561        if let Some(addrtype) = &self.addrtype {
562            write!(f, " {addrtype}")?;
563        }
564        if let Some(connection_address) = &self.connection_address {
565            write!(f, " {connection_address}")?;
566        }
567        Ok(())
568    }
569}
570
571impl TypedAttribute for Rtcp {
572    const NAME: &'static str = "rtcp";
573}
574
575/// RTCP Feedback Capability
576///
577/// See [RFC 4585 Section 4.2](https://datatracker.ietf.org/doc/html/rfc4585#section-4.2)
578#[derive(Debug, PartialEq, Eq, Clone)]
579#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
580pub struct RtcpFb {
581    /// Payload format for which feedback messages may be used
582    pub pt: RtcpFbPt,
583    /// RTCP Feedback value
584    pub val: RtcpFbVal,
585}
586
587impl RtcpFb {
588    pub fn new(pt: impl Into<RtcpFbPt>, val: impl Into<RtcpFbVal>) -> Self {
589        RtcpFb {
590            pt: pt.into(),
591            val: val.into(),
592        }
593    }
594}
595
596impl FromStr for RtcpFb {
597    type Err = AttributeError;
598
599    fn from_str(s: &str) -> Result<Self, Self::Err> {
600        let mut i = s.split(' ');
601        let Some(pt) = i.next() else {
602            return Err(AttributeError::ParamNotFound {
603                param: "Payload format".to_string(),
604                attr: <Self as TypedAttribute>::NAME.to_string(),
605            });
606        };
607
608        let pt = if let Ok(pt) = pt.parse::<u8>() {
609            RtcpFbPt::Fmt(pt)
610        } else if pt == "*" {
611            RtcpFbPt::Wildcard
612        } else {
613            return Err(AttributeError::InvalidParamValue {
614                param: "Payload format".to_string(),
615                val: pt.to_string(),
616                attr: <Self as TypedAttribute>::NAME.to_string(),
617            });
618        };
619
620        let Some(val) = i.next() else {
621            return Err(AttributeError::ParamNotFound {
622                param: "Rtcp feedback value".to_string(),
623                attr: <Self as TypedAttribute>::NAME.to_string(),
624            });
625        };
626
627        let rtcp_fb_val = match val {
628            "ack" => {
629                if let Some(ack_val) = i.next() {
630                    let ack_val = match ack_val {
631                        "rpsi" => RtcpFbAck::Rpsi,
632                        "app" => {
633                            if let Some(app_param) = i.next() {
634                                RtcpFbAck::App(Some(app_param.to_string()))
635                            } else {
636                                RtcpFbAck::App(None)
637                            }
638                        }
639                        "ccfb" => {
640                            // The payload type used with "ccfb" feedback MUST be the wildcard type
641                            // See https://datatracker.ietf.org/doc/html/rfc8888#section-6
642                            if let RtcpFbPt::Fmt(pt) = pt {
643                                return Err(AttributeError::InvalidParamValue {
644                                    param: "Payload type of Congestion control feedback (ccfb)"
645                                        .to_string(),
646                                    val: format!("{pt}(expected wildcard (*))"),
647                                    attr: <Self as TypedAttribute>::NAME.to_string(),
648                                });
649                            } else {
650                                RtcpFbAck::Ccfb
651                            }
652                        }
653                        other => RtcpFbAck::Other(other.to_string()),
654                    };
655                    RtcpFbVal::Ack(Some(ack_val))
656                } else {
657                    RtcpFbVal::Ack(None)
658                }
659            }
660            "nack" => {
661                if let Some(nack_val) = i.next() {
662                    let nack_val = match nack_val {
663                        "pli" => RtcpFbNack::Pli,
664                        "sli" => RtcpFbNack::Sli,
665                        "rpsi" => RtcpFbNack::Rpsi,
666                        "app" => {
667                            if let Some(app_param) = i.next() {
668                                RtcpFbNack::App(Some(app_param.to_string()))
669                            } else {
670                                RtcpFbNack::App(None)
671                            }
672                        }
673                        "ecn" => RtcpFbNack::Ecn,
674                        other => RtcpFbNack::Other(other.to_string()),
675                    };
676                    RtcpFbVal::Nack(Some(nack_val))
677                } else {
678                    RtcpFbVal::Nack(None)
679                }
680            }
681            "trr-int" => {
682                if let Some(val) = i.next() {
683                    let Ok(i) = val.parse::<u64>() else {
684                        return Err(AttributeError::InvalidParamValue {
685                            param: "Minimum interval between RTCP packets (trr-int)".to_string(),
686                            val: val.to_string(),
687                            attr: <Self as TypedAttribute>::NAME.to_string(),
688                        });
689                    };
690                    RtcpFbVal::TrrInt(i)
691                } else {
692                    return Err(AttributeError::Other {
693                        error: "Minimum interval between RTCP packets (trr-int) not specified"
694                            .to_string(),
695                        attr: <Self as TypedAttribute>::NAME.to_string(),
696                    });
697                }
698            }
699            "ccm" => {
700                if let Some(ccm_val) = i.next() {
701                    let ccm_val = match ccm_val {
702                        "fir" => RtcpFbCcm::Fir,
703                        "tmmbr" => {
704                            if let Some(tmmbr_val) = i.next() {
705                                RtcpFbCcm::Tmmbr(Some(tmmbr_val.to_string()))
706                            } else {
707                                RtcpFbCcm::Tmmbr(None)
708                            }
709                        }
710                        "tstr" => RtcpFbCcm::Tstr,
711                        "vbcm" => {
712                            let mut v = vec![];
713                            for vbcm_val in i {
714                                let Ok(p) = vbcm_val.parse::<u8>() else {
715                                    return Err(AttributeError::InvalidParamValue {
716                                        param: "Video backchannel messages (vbcm)".to_string(),
717                                        val: vbcm_val.to_string(),
718                                        attr: <Self as TypedAttribute>::NAME.to_string(),
719                                    });
720                                };
721                                v.push(p);
722                            }
723                            RtcpFbCcm::Vbcm(v)
724                        }
725                        other => RtcpFbCcm::Other(other.to_string()),
726                    };
727                    RtcpFbVal::Ccm(ccm_val)
728                } else {
729                    return Err(AttributeError::ParamNotFound {
730                        param: "Codec control messages (ccm)".to_string(),
731                        attr: <Self as TypedAttribute>::NAME.to_string(),
732                    });
733                }
734            }
735            "transport-cc" => RtcpFbVal::TransportCc,
736            other => RtcpFbVal::Other(other.to_string()),
737        };
738
739        Ok(Self {
740            pt,
741            val: rtcp_fb_val,
742        })
743    }
744}
745
746impl Display for RtcpFb {
747    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
748        match self.pt {
749            RtcpFbPt::Wildcard => f.write_char('*')?,
750            RtcpFbPt::Fmt(pt) => write!(f, "{pt}")?,
751        }
752
753        f.write_char(' ')?;
754        write!(f, "{}", self.val)
755    }
756}
757
758impl TypedAttribute for RtcpFb {
759    const NAME: &'static str = "rtcp-fb";
760}
761
762/// Media Direction Attributes
763///
764/// See [RFC 8866 Section 6.7](https://datatracker.ietf.org/doc/html/rfc8866#section-6.7)
765#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
766#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
767pub enum Direction {
768    #[default]
769    SendRecv,
770    SendOnly,
771    RecvOnly,
772    Inactive,
773}
774
775impl Direction {
776    pub fn as_str(&self) -> &'static str {
777        match self {
778            Self::SendOnly => "sendonly",
779            Self::RecvOnly => "recvonly",
780            Self::SendRecv => "sendrecv",
781            Self::Inactive => "inactive",
782        }
783    }
784
785    pub fn has_send(self) -> bool {
786        matches!(self, Self::SendRecv | Self::SendOnly)
787    }
788
789    pub fn has_recv(self) -> bool {
790        matches!(self, Self::SendRecv | Self::RecvOnly)
791    }
792
793    pub fn reverse(self) -> Self {
794        match self {
795            Self::SendRecv => Self::SendRecv,
796            Self::SendOnly => Self::RecvOnly,
797            Self::RecvOnly => Self::SendOnly,
798            Self::Inactive => Self::Inactive,
799        }
800    }
801
802    pub fn intersect_with_remote(self, remote: Self) -> Self {
803        match (self, remote) {
804            (Self::Inactive, _)
805            | (_, Self::Inactive)
806            | (Self::RecvOnly, Self::RecvOnly)
807            | (Self::SendOnly, Self::SendOnly) => Self::Inactive,
808            (Self::SendRecv, Self::SendRecv) => Self::SendRecv,
809            (Self::SendOnly, Self::RecvOnly | Self::SendRecv)
810            | (Self::SendRecv, Self::RecvOnly) => Self::SendOnly,
811            (Self::RecvOnly, Self::SendRecv | Self::SendOnly)
812            | (Self::SendRecv, Self::SendOnly) => Self::RecvOnly,
813        }
814    }
815}
816
817impl FromStr for Direction {
818    type Err = ParseEnumError;
819
820    fn from_str(s: &str) -> Result<Self, Self::Err> {
821        if "sendonly".eq_ignore_ascii_case(s) {
822            Ok(Direction::SendOnly)
823        } else if "recvonly".eq_ignore_ascii_case(s) {
824            Ok(Direction::RecvOnly)
825        } else if "sendrecv".eq_ignore_ascii_case(s) {
826            Ok(Direction::SendRecv)
827        } else if "inactive".eq_ignore_ascii_case(s) {
828            Ok(Direction::Inactive)
829        } else {
830            Err(ParseEnumError::Invalid(s.to_string()))
831        }
832    }
833}
834
835impl Display for Direction {
836    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
837        f.write_str(self.as_str())
838    }
839}
840
841impl From<Direction> for Attribute {
842    fn from(attr: Direction) -> Attribute {
843        Attribute {
844            attribute: attr.to_string(),
845            value: None,
846        }
847    }
848}
849
850/// RTP header extensions map
851///
852/// See [RFC 8285 Section 8](https://datatracker.ietf.org/doc/html/rfc8285#section-8)
853#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
854#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
855pub struct ExtMap {
856    /// The local identifier (ID) of this extension
857    pub id: u8,
858    /// Direction
859    pub direction: Option<Direction>,
860    /// The format and meaning of the extension
861    pub uri: String,
862    /// Extension attributes
863    pub attributes: Option<String>,
864}
865
866impl ExtMap {
867    pub fn new(id: u8, uri: impl ToString) -> Self {
868        ExtMap {
869            id,
870            direction: None,
871            uri: uri.to_string(),
872            attributes: None,
873        }
874    }
875
876    pub fn builder(id: u8, uri: impl ToString) -> builders::ExtMap {
877        builders::ExtMap::new(id, uri)
878    }
879
880    pub fn with_direction(id: u8, direction: Direction, uri: impl ToString) -> Self {
881        ExtMap {
882            id,
883            direction: Some(direction),
884            uri: uri.to_string(),
885            attributes: None,
886        }
887    }
888
889    pub fn with_direction_and_attributes(
890        id: u8,
891        direction: Direction,
892        uri: impl ToString,
893        attributes: impl ToString,
894    ) -> Self {
895        ExtMap {
896            id,
897            direction: Some(direction),
898            uri: uri.to_string(),
899            attributes: Some(attributes.to_string()),
900        }
901    }
902
903    pub fn set_direction(&mut self, direction: Direction) {
904        self.direction = Some(direction);
905    }
906
907    pub fn set_attributes(&mut self, attributes: impl ToString) {
908        self.attributes = Some(attributes.to_string());
909    }
910}
911
912impl FromStr for ExtMap {
913    type Err = AttributeError;
914
915    fn from_str(s: &str) -> Result<Self, Self::Err> {
916        let mut i = s.splitn(3, ' ');
917
918        let Some(id_direction) = i.next() else {
919            return Err(AttributeError::ParamNotFound {
920                param: "id/direction".to_string(),
921                attr: <Self as TypedAttribute>::NAME.to_string(),
922            });
923        };
924
925        let mut d = id_direction.split('/');
926
927        let Some(id) = d.next() else {
928            return Err(AttributeError::ParamNotFound {
929                param: "id".to_string(),
930                attr: <Self as TypedAttribute>::NAME.to_string(),
931            });
932        };
933
934        let direction = if let Some(d) = d.next() {
935            let Ok(dir) = Direction::from_str(d) else {
936                return Err(AttributeError::InvalidParamValue {
937                    param: "Direction".to_string(),
938                    val: d.to_string(),
939                    attr: <Self as TypedAttribute>::NAME.to_string(),
940                });
941            };
942            Some(dir)
943        } else {
944            None
945        };
946
947        let Ok(id) = id.parse::<u8>() else {
948            return Err(AttributeError::InvalidParamValue {
949                param: "Id".to_string(),
950                val: id.to_string(),
951                attr: <Self as TypedAttribute>::NAME.to_string(),
952            });
953        };
954
955        if id == 0 {
956            return Err(AttributeError::InvalidParamValue {
957                param: "Id".to_string(),
958                val: id.to_string(),
959                attr: <Self as TypedAttribute>::NAME.to_string(),
960            });
961        }
962
963        let Some(uri) = i.next() else {
964            return Err(AttributeError::ParamNotFound {
965                param: "URI".to_string(),
966                attr: <Self as TypedAttribute>::NAME.to_string(),
967            });
968        };
969
970        let attributes = i.next().map(|attr| attr.to_string());
971
972        Ok(Self {
973            id,
974            direction,
975            uri: uri.to_string(),
976            attributes,
977        })
978    }
979}
980
981impl Display for ExtMap {
982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
983        write!(f, "{}", self.id)?;
984        if let Some(direction) = &self.direction {
985            f.write_char('/')?;
986            f.write_str(direction.as_str())?;
987        }
988
989        f.write_char(' ')?;
990        f.write_str(&self.uri)?;
991
992        if let Some(attr) = &self.attributes {
993            f.write_char(' ')?;
994            f.write_str(attr.as_str())?;
995        }
996        Ok(())
997    }
998}
999
1000impl TypedAttribute for ExtMap {
1001    const NAME: &'static str = "extmap";
1002}
1003
1004/// Fingerprint Attribute
1005///
1006/// See [RFC 8122 Section 5](https://datatracker.ietf.org/doc/html/rfc8122#section-5)
1007#[derive(Debug, Clone, PartialEq, Eq)]
1008#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1009pub struct Fingerprint {
1010    /// Name of hash function used
1011    pub hash_func: HashFunc,
1012    /// Hash value
1013    pub fingerprint: Vec<u8>,
1014}
1015
1016impl Fingerprint {
1017    pub fn new(hash_func: HashFunc) -> Self {
1018        Fingerprint {
1019            hash_func,
1020            fingerprint: vec![],
1021        }
1022    }
1023
1024    pub fn with_fingerprint(
1025        hash_func: HashFunc,
1026        fingerprint: impl IntoIterator<Item = u8>,
1027    ) -> Self {
1028        Fingerprint {
1029            hash_func,
1030            fingerprint: std::iter::FromIterator::from_iter(fingerprint),
1031        }
1032    }
1033
1034    pub fn set_fingerprint(&mut self, fingerprint: impl IntoIterator<Item = u8>) {
1035        self.fingerprint.clear();
1036        self.fingerprint.extend(fingerprint);
1037    }
1038}
1039
1040impl FromStr for Fingerprint {
1041    type Err = AttributeError;
1042
1043    fn from_str(s: &str) -> Result<Self, Self::Err> {
1044        let mut i = s.splitn(2, ' ');
1045
1046        let hash_func = if let Some(hash_func) = i.next() {
1047            HashFunc::new(hash_func)
1048        } else {
1049            return Err(AttributeError::ParamNotFound {
1050                param: "Hash function".to_string(),
1051                attr: <Self as TypedAttribute>::NAME.to_string(),
1052            });
1053        };
1054
1055        let mut fingerprint: Vec<u8> = vec![];
1056        if let Some(fp) = i.next() {
1057            for f in fp.split(':') {
1058                let Ok(mut f) = hex::decode(f) else {
1059                    return Err(AttributeError::InvalidParamValue {
1060                        param: "Fingerprint value".to_string(),
1061                        val: f.to_string(),
1062                        attr: <Self as TypedAttribute>::NAME.to_string(),
1063                    });
1064                };
1065
1066                fingerprint.append(&mut f);
1067            }
1068        } else {
1069            return Err(AttributeError::ParamNotFound {
1070                param: "Hash value".to_string(),
1071                attr: <Self as TypedAttribute>::NAME.to_string(),
1072            });
1073        };
1074
1075        Ok(Self {
1076            hash_func,
1077            fingerprint,
1078        })
1079    }
1080}
1081
1082impl Display for Fingerprint {
1083    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1084        f.write_str(self.hash_func.as_str())?;
1085        let mut first = true;
1086        for v in &self.fingerprint {
1087            if first {
1088                f.write_char(' ')?;
1089                first = false;
1090            } else {
1091                f.write_char(':')?;
1092            }
1093            write!(f, "{v:02X}")?;
1094        }
1095        Ok(())
1096    }
1097}
1098
1099impl TypedAttribute for Fingerprint {
1100    const NAME: &'static str = "fingerprint";
1101}
1102
1103/// Group Attribute
1104///
1105/// See [RFC 5888 Section 5](https://datatracker.ietf.org/doc/html/rfc5888#section-5)
1106#[derive(Debug, Clone, PartialEq, Eq)]
1107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1108pub struct Group {
1109    pub semantics: GroupSemantics,
1110    pub mid_tags: Vec<String>,
1111}
1112
1113impl Group {
1114    pub fn new(semantics: GroupSemantics) -> Self {
1115        Group {
1116            semantics,
1117            mid_tags: vec![],
1118        }
1119    }
1120
1121    pub fn add_mid_tag(&mut self, mid_tag: impl ToString) {
1122        self.mid_tags.push(mid_tag.to_string())
1123    }
1124
1125    pub fn add_mid_tags(&mut self, mid_tags: impl IntoIterator<Item = impl ToString>) {
1126        self.mid_tags
1127            .extend(mid_tags.into_iter().map(|i| i.to_string()))
1128    }
1129}
1130
1131impl FromStr for Group {
1132    type Err = AttributeError;
1133
1134    fn from_str(s: &str) -> Result<Self, Self::Err> {
1135        let mut i = s.split(' ');
1136
1137        let Some(semantics) = i.next() else {
1138            return Err(AttributeError::ParamNotFound {
1139                param: "Semantics".to_string(),
1140                attr: <Self as TypedAttribute>::NAME.to_string(),
1141            });
1142        };
1143
1144        let semantics = GroupSemantics::new(semantics);
1145
1146        let mut mid_tags = vec![];
1147        for mid in i {
1148            mid_tags.push(mid.to_string());
1149        }
1150
1151        if mid_tags.is_empty() {
1152            return Err(AttributeError::ParamNotFound {
1153                param: "Media identification tags".to_string(),
1154                attr: <Self as TypedAttribute>::NAME.to_string(),
1155            });
1156        }
1157
1158        Ok(Self {
1159            semantics,
1160            mid_tags,
1161        })
1162    }
1163}
1164
1165impl Display for Group {
1166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1167        f.write_str(self.semantics.as_str())?;
1168        for m in &self.mid_tags {
1169            f.write_char(' ')?;
1170            f.write_str(m)?;
1171        }
1172        Ok(())
1173    }
1174}
1175
1176impl TypedAttribute for Group {
1177    const NAME: &'static str = "group";
1178}
1179
1180/// Setup attribute for the session or media.
1181///
1182/// See [RFC 4145 Section 4](https://datatracker.ietf.org/doc/html/rfc4145#section-4) for more details.
1183#[derive(Debug, Clone, PartialEq, Eq)]
1184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1185pub enum Setup {
1186    /// Initiator of the connection.
1187    Active,
1188    /// Acceptor of the connection.
1189    Passive,
1190    /// Act as either initiator or acceptor of the connection.
1191    ActPass,
1192    /// Do not establish a connection.
1193    HoldConn,
1194}
1195
1196impl FromStr for Setup {
1197    type Err = AttributeError;
1198
1199    fn from_str(s: &str) -> Result<Self, Self::Err> {
1200        if "active".eq_ignore_ascii_case(s) {
1201            Ok(Setup::Active)
1202        } else if "passive".eq_ignore_ascii_case(s) {
1203            Ok(Setup::Passive)
1204        } else if "actpass".eq_ignore_ascii_case(s) {
1205            Ok(Setup::ActPass)
1206        } else if "holdconn".eq_ignore_ascii_case(s) {
1207            Ok(Setup::HoldConn)
1208        } else {
1209            Err(AttributeError::Other {
1210                error: format!("Invalid Setup value {s}"),
1211                attr: <Self as TypedAttribute>::NAME.to_string(),
1212            })
1213        }
1214    }
1215}
1216
1217impl Display for Setup {
1218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1219        let s = match self {
1220            Setup::Active => "active",
1221            Setup::Passive => "passive",
1222            Setup::ActPass => "actpass",
1223            Setup::HoldConn => "holdconn",
1224        };
1225        f.write_str(s)
1226    }
1227}
1228
1229impl TypedAttribute for Setup {
1230    const NAME: &'static str = "setup";
1231}
1232
1233/// SSRC media attribute.
1234///
1235/// See [RFC 5576 Section 4.1](https://datatracker.ietf.org/doc/html/rfc5576#section-4.1)
1236#[derive(Debug, Clone, PartialEq, Eq)]
1237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1238pub struct Ssrc {
1239    pub ssrc_id: u32,
1240    pub attribute: SsrcAttribute,
1241    pub value: Option<String>,
1242}
1243
1244impl Ssrc {
1245    pub fn new(ssrc_id: u32, attribute: SsrcAttribute) -> Self {
1246        Ssrc {
1247            ssrc_id,
1248            attribute,
1249            value: None,
1250        }
1251    }
1252
1253    pub fn with_typed_attribute(ssrc_id: u32, attribute: impl TypedAttribute) -> Self {
1254        let value = attribute.to_string();
1255        Ssrc {
1256            ssrc_id,
1257            attribute: SsrcAttribute::from(attribute),
1258            value: Some(value),
1259        }
1260    }
1261
1262    pub fn with_value(ssrc_id: u32, attribute: SsrcAttribute, value: impl ToString) -> Self {
1263        Ssrc {
1264            ssrc_id,
1265            attribute,
1266            value: Some(value.to_string()),
1267        }
1268    }
1269
1270    pub fn set_value(&mut self, value: impl ToString) {
1271        self.value = Some(value.to_string());
1272    }
1273
1274    /// Gets the inner attribute as a `TypedAttribute`.
1275    ///
1276    /// # Errors
1277    ///
1278    /// * `AttributeError::Other` if the inner attribute doesn't match
1279    ///   the specified `TypedAttribute` or if the value is empty.
1280    /// * a specific `AttributeError` if the typed attribute couldn't be built.
1281    pub fn get_typed<T: TypedAttribute>(&self) -> Result<T, AttributeError> {
1282        if !self.attribute.as_str().eq_ignore_ascii_case(T::NAME) {
1283            return Err(AttributeError::Other {
1284                error: format!("Attribute type mismatch (requested {})", T::NAME),
1285                attr: self.attribute.as_str().to_string(),
1286            });
1287        }
1288
1289        let Some(ref value) = self.value else {
1290            return Err(AttributeError::Other {
1291                error: "No value for the attribute".to_string(),
1292                attr: T::NAME.to_string(),
1293            });
1294        };
1295
1296        T::from_str(value)
1297    }
1298}
1299
1300impl FromStr for Ssrc {
1301    type Err = AttributeError;
1302
1303    fn from_str(s: &str) -> Result<Self, Self::Err> {
1304        let Some((ssrc_id_str, rest)) = s.split_once(' ') else {
1305            return Err(AttributeError::ParamNotFound {
1306                param: "Ssrc id".to_string(),
1307                attr: <Self as TypedAttribute>::NAME.to_string(),
1308            });
1309        };
1310
1311        let Ok(ssrc_id) = ssrc_id_str.parse::<u32>() else {
1312            return Err(AttributeError::InvalidParamValue {
1313                param: "Ssrc id".to_string(),
1314                val: ssrc_id_str.to_string(),
1315                attr: <Self as TypedAttribute>::NAME.to_string(),
1316            });
1317        };
1318
1319        let (attr, value) = if let Some((attr_str, value)) = rest.split_once(':') {
1320            (attr_str, Some(value.to_string()))
1321        } else {
1322            (rest, None)
1323        };
1324
1325        Ok(Self {
1326            ssrc_id,
1327            attribute: SsrcAttribute::new(attr),
1328            value,
1329        })
1330    }
1331}
1332
1333impl Display for Ssrc {
1334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1335        use crate::{Fmtp, MediaClockSource, ReferenceClock, Rtcp};
1336
1337        let attr_str = match &self.attribute {
1338            SsrcAttribute::Cname => "cname",
1339            SsrcAttribute::PreviousSsrc => "previous-ssrc",
1340            SsrcAttribute::Fmtp => <Fmtp as TypedAttribute>::NAME,
1341            SsrcAttribute::Rtcp => <Rtcp as TypedAttribute>::NAME,
1342            SsrcAttribute::ReferenceClock => <ReferenceClock as TypedAttribute>::NAME,
1343            SsrcAttribute::MediaClockSource => <MediaClockSource as TypedAttribute>::NAME,
1344            SsrcAttribute::Other(other) => other.as_str(),
1345        };
1346        write!(f, "{} {attr_str}", self.ssrc_id)?;
1347
1348        if let Some(value) = &self.value {
1349            f.write_char(':')?;
1350            f.write_str(value)?;
1351        }
1352
1353        Ok(())
1354    }
1355}
1356
1357impl TypedAttribute for Ssrc {
1358    const NAME: &'static str = "ssrc";
1359}
1360
1361/// SSRC group attribute
1362///
1363/// See [RFC 5576 Section 4.2](https://datatracker.ietf.org/doc/html/rfc5576#section-4.2)
1364#[derive(Debug, Clone, PartialEq, Eq)]
1365#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1366pub struct SsrcGroup {
1367    pub semantics: GroupSemantics,
1368    pub ssrc_ids: Vec<u32>,
1369}
1370
1371impl SsrcGroup {
1372    pub fn new(semantics: GroupSemantics) -> Self {
1373        SsrcGroup {
1374            semantics,
1375            ssrc_ids: vec![],
1376        }
1377    }
1378
1379    pub fn add_ssrc_id(&mut self, ssrc_id: u32) {
1380        self.ssrc_ids.push(ssrc_id)
1381    }
1382
1383    pub fn add_ssrc_ids(&mut self, ssrc_ids: impl IntoIterator<Item = u32>) {
1384        self.ssrc_ids.extend(ssrc_ids)
1385    }
1386}
1387
1388impl FromStr for SsrcGroup {
1389    type Err = AttributeError;
1390
1391    fn from_str(s: &str) -> Result<Self, Self::Err> {
1392        let mut i = s.split(' ');
1393
1394        let Some(semantics) = i.next() else {
1395            return Err(AttributeError::ParamNotFound {
1396                param: "Semantics".to_string(),
1397                attr: <Self as TypedAttribute>::NAME.to_string(),
1398            });
1399        };
1400
1401        let semantics = if "FEC".eq_ignore_ascii_case(semantics) {
1402            GroupSemantics::FEC
1403        } else if "FID".eq_ignore_ascii_case(semantics) {
1404            GroupSemantics::FID
1405        } else {
1406            // The initial defined semantics for ssrc-group attribute are FID and FEC
1407            // The other registered group semantics are not useful for source grouping
1408            // But keep this open for any other new semantics that are not part of GroupSemantics
1409            GroupSemantics::Other(semantics.to_string())
1410        };
1411
1412        let mut ssrc_ids = vec![];
1413        for ssrc_id in i {
1414            let Ok(ssrc_id) = ssrc_id.parse::<u32>() else {
1415                return Err(AttributeError::InvalidParamValue {
1416                    param: "Ssrc id".to_string(),
1417                    val: ssrc_id.to_string(),
1418                    attr: <Self as TypedAttribute>::NAME.to_string(),
1419                });
1420            };
1421            ssrc_ids.push(ssrc_id);
1422        }
1423
1424        if ssrc_ids.is_empty() {
1425            return Err(AttributeError::ParamNotFound {
1426                param: "ssrc_id".to_string(),
1427                attr: <Self as TypedAttribute>::NAME.to_string(),
1428            });
1429        }
1430
1431        Ok(Self {
1432            semantics,
1433            ssrc_ids,
1434        })
1435    }
1436}
1437
1438impl Display for SsrcGroup {
1439    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1440        let sem = match &self.semantics {
1441            GroupSemantics::FEC => "FEC",
1442            GroupSemantics::FID => "FID",
1443            // Semantics other than FEC and FID are not useful for source grouping but still displaying
1444            // them for debugging purpose
1445            GroupSemantics::LS => "LS",
1446            GroupSemantics::SRF => "SRF",
1447            GroupSemantics::ANAT => "ANAT",
1448            GroupSemantics::DDP => "DDP",
1449            GroupSemantics::Other(s) => s.as_str(),
1450        };
1451
1452        f.write_str(sem)?;
1453        for ssrc_id in &self.ssrc_ids {
1454            f.write_char(' ')?;
1455            write!(f, "{ssrc_id}")?;
1456        }
1457
1458        Ok(())
1459    }
1460}
1461
1462impl TypedAttribute for SsrcGroup {
1463    const NAME: &'static str = "ssrc-group";
1464}
1465
1466/// SRTP Key parameter
1467///
1468/// See [RFC 4568 Section 6.1](https://datatracker.ietf.org/doc/html/rfc4568#section-6.1)
1469#[derive(Debug, PartialEq, Eq, Clone)]
1470#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1471pub struct SrtpKeyParam {
1472    /// Concatenated key and salt, base64 encoded
1473    pub key_and_salt: String,
1474    /// Master key lifetime (max number of SRTP or SRTCP packets using this master key)
1475    ///
1476    /// The value must not exceed 2^48 (RFC 4568, section 8.1).
1477    pub lifetime: Option<u64>,
1478    /// MKI (Master Key Identifier) and length of the MKI field in SRTP packets
1479    pub mki_and_length: Option<(u32, u32)>,
1480}
1481
1482impl SrtpKeyParam {
1483    pub fn new(key_and_salt: impl ToString) -> Self {
1484        SrtpKeyParam {
1485            key_and_salt: key_and_salt.to_string(),
1486            lifetime: None,
1487            mki_and_length: None,
1488        }
1489    }
1490
1491    pub fn with_lifetime(key_and_salt: impl ToString, lifetime: u64) -> Self {
1492        SrtpKeyParam {
1493            key_and_salt: key_and_salt.to_string(),
1494            lifetime: Some(lifetime),
1495            mki_and_length: None,
1496        }
1497    }
1498
1499    pub fn with_lifetime_and_mki_and_length(
1500        key_and_salt: impl ToString,
1501        lifetime: u64,
1502        mki: u32,
1503        length: u32,
1504    ) -> Self {
1505        SrtpKeyParam {
1506            key_and_salt: key_and_salt.to_string(),
1507            lifetime: Some(lifetime),
1508            mki_and_length: Some((mki, length)),
1509        }
1510    }
1511
1512    pub fn set_lifetime(&mut self, lifetime: u64) {
1513        self.lifetime = Some(lifetime);
1514    }
1515
1516    pub fn set_mki_and_length(&mut self, mki: u32, length: u32) {
1517        self.mki_and_length = Some((mki, length));
1518    }
1519}
1520
1521// Parse a plain decimal integer as used in the crypto attribute: digits
1522// only, no sign and no leading zeroes, except for the value 0 itself
1523// (RFC 4568, section 8.1).
1524fn parse_decimal_no_leading_zeroes(s: &str) -> Option<u64> {
1525    if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
1526        return None;
1527    }
1528    if s.len() > 1 && s.starts_with('0') {
1529        return None;
1530    }
1531    s.parse().ok()
1532}
1533
1534impl FromStr for SrtpKeyParam {
1535    type Err = AttributeError;
1536    fn from_str(key_param: &str) -> Result<Self, Self::Err> {
1537        let mut k = key_param.split('|');
1538
1539        let Some(key_and_salt_with_method) = k.next() else {
1540            return Err(AttributeError::ParamNotFound {
1541                param: "Srtp Key and Salt".to_string(),
1542                attr: Crypto::NAME.to_string(),
1543            });
1544        };
1545
1546        let key_and_salt = if key_and_salt_with_method
1547            .get(..7)
1548            .is_some_and(|p| p.eq_ignore_ascii_case("inline:"))
1549        {
1550            &key_and_salt_with_method[7..]
1551        } else {
1552            return Err(AttributeError::InvalidParamValue {
1553                param: "Srtp Key and Salt".to_string(),
1554                val: key_and_salt_with_method.to_string(),
1555                attr: Crypto::NAME.to_string(),
1556            });
1557        };
1558
1559        let (lifetime, mki_and_length) = if let Some(next_param) = k.next() {
1560            match next_param.split_once(':') {
1561                Some(mki_and_length) => {
1562                    // lifetime is not specified, but only MKI and its length
1563                    let Ok(mki) = mki_and_length.0.parse::<u32>() else {
1564                        return Err(AttributeError::InvalidParamValue {
1565                            param: "MKI".to_string(),
1566                            val: next_param.to_string(),
1567                            attr: Crypto::NAME.to_string(),
1568                        });
1569                    };
1570
1571                    let Ok(len) = mki_and_length.1.parse::<u32>() else {
1572                        return Err(AttributeError::InvalidParamValue {
1573                            param: "Length".to_string(),
1574                            val: next_param.to_string(),
1575                            attr: Crypto::NAME.to_string(),
1576                        });
1577                    };
1578                    (None, Some((mki, len)))
1579                }
1580                None => {
1581                    // lifetime is specified
1582                    // The maximum master key lifetime is 2^48 packets for all
1583                    // crypto suites (RFC 4568, section 8.1).
1584                    const MAX_LIFETIME: u64 = 1 << 48;
1585                    let lifetime = match next_param.strip_prefix("2^") {
1586                        Some(exp) => {
1587                            let Some(exp) = parse_decimal_no_leading_zeroes(exp)
1588                                .and_then(|exp| u32::try_from(exp).ok())
1589                            else {
1590                                return Err(AttributeError::InvalidParamValue {
1591                                    param: "Lifetime".to_string(),
1592                                    val: next_param.to_string(),
1593                                    attr: Crypto::NAME.to_string(),
1594                                });
1595                            };
1596                            let Some(lifetime) = 1u64.checked_shl(exp) else {
1597                                return Err(AttributeError::InvalidParamValue {
1598                                    param: "Lifetime".to_string(),
1599                                    val: next_param.to_string(),
1600                                    attr: Crypto::NAME.to_string(),
1601                                });
1602                            };
1603                            // 1u64.checked_shl() never returns zero
1604                            if lifetime > MAX_LIFETIME {
1605                                return Err(AttributeError::InvalidParamValue {
1606                                    param: "Lifetime".to_string(),
1607                                    val: next_param.to_string(),
1608                                    attr: Crypto::NAME.to_string(),
1609                                });
1610                            }
1611                            Some(lifetime)
1612                        }
1613                        None => {
1614                            let Some(lifetime) = parse_decimal_no_leading_zeroes(next_param) else {
1615                                return Err(AttributeError::InvalidParamValue {
1616                                    param: "Lifetime".to_string(),
1617                                    val: next_param.to_string(),
1618                                    attr: Crypto::NAME.to_string(),
1619                                });
1620                            };
1621                            if lifetime == 0 || lifetime > MAX_LIFETIME {
1622                                return Err(AttributeError::InvalidParamValue {
1623                                    param: "Lifetime".to_string(),
1624                                    val: next_param.to_string(),
1625                                    attr: Crypto::NAME.to_string(),
1626                                });
1627                            }
1628                            Some(lifetime)
1629                        }
1630                    };
1631
1632                    // now parse the MKI and length
1633                    let mki_and_length = if let Some(m) = k.next() {
1634                        if let Some(p) = m.split_once(':') {
1635                            let Ok(mki) = p.0.parse::<u32>() else {
1636                                return Err(AttributeError::InvalidParamValue {
1637                                    param: "MKI".to_string(),
1638                                    val: m.to_string(),
1639                                    attr: Crypto::NAME.to_string(),
1640                                });
1641                            };
1642
1643                            let Ok(len) = p.1.parse::<u32>() else {
1644                                return Err(AttributeError::InvalidParamValue {
1645                                    param: "Length".to_string(),
1646                                    val: m.to_string(),
1647                                    attr: Crypto::NAME.to_string(),
1648                                });
1649                            };
1650                            Some((mki, len))
1651                        } else {
1652                            return Err(AttributeError::ParamNotFound {
1653                                param: "MKI and Length".to_string(),
1654                                attr: Crypto::NAME.to_string(),
1655                            });
1656                        }
1657                    } else {
1658                        None
1659                    };
1660
1661                    (lifetime, mki_and_length)
1662                }
1663            }
1664        } else {
1665            (None, None)
1666        };
1667
1668        if let Some((_, len)) = mki_and_length {
1669            if !(1..=128).contains(&len) {
1670                return Err(AttributeError::InvalidParamValue {
1671                    param: "MKI length".to_string(),
1672                    val: len.to_string(),
1673                    attr: Crypto::NAME.to_string(),
1674                });
1675            }
1676        }
1677
1678        if let Some(unexpected) = k.next() {
1679            return Err(AttributeError::UnexpectedTrailingItem {
1680                val: unexpected.to_string(),
1681                attr: Crypto::NAME.to_string(),
1682            });
1683        }
1684
1685        Ok(Self {
1686            key_and_salt: key_and_salt.to_string(),
1687            lifetime,
1688            mki_and_length,
1689        })
1690    }
1691}
1692
1693impl Display for SrtpKeyParam {
1694    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1695        write!(f, "inline:{}", self.key_and_salt)?;
1696        if let Some(lifetime) = self.lifetime {
1697            if lifetime.is_power_of_two() {
1698                write!(f, "|2^{}", lifetime.trailing_zeros())?;
1699            } else {
1700                write!(f, "|{lifetime}")?;
1701            }
1702        }
1703        if let Some((mki, length)) = self.mki_and_length {
1704            write!(f, "|{mki}:{length}")?;
1705        }
1706        Ok(())
1707    }
1708}
1709
1710/// Cryptographic information for the media
1711///
1712/// See [RFC 4568 Section 4](https://datatracker.ietf.org/doc/html/rfc4568#section-4)
1713#[derive(Debug, PartialEq, Eq, Clone)]
1714#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1715pub struct Crypto {
1716    pub tag: u32,
1717    pub crypto_suite: CryptoSuite,
1718    pub key_params: Vec<SrtpKeyParam>,
1719    pub session_params: Vec<SrtpSessionParam>,
1720}
1721
1722impl Crypto {
1723    pub fn new(tag: u32, crypto_suite: CryptoSuite) -> Self {
1724        Crypto {
1725            tag,
1726            crypto_suite,
1727            key_params: vec![],
1728            session_params: vec![],
1729        }
1730    }
1731
1732    pub fn add_key_param(&mut self, key_param: SrtpKeyParam) {
1733        self.key_params.push(key_param)
1734    }
1735
1736    pub fn add_key_params(&mut self, key_params: impl IntoIterator<Item = SrtpKeyParam>) {
1737        self.key_params.extend(key_params)
1738    }
1739
1740    pub fn add_session_param(&mut self, session_param: SrtpSessionParam) {
1741        self.session_params.push(session_param)
1742    }
1743
1744    pub fn add_session_params(
1745        &mut self,
1746        session_params: impl IntoIterator<Item = SrtpSessionParam>,
1747    ) {
1748        self.session_params.extend(session_params)
1749    }
1750}
1751
1752impl FromStr for Crypto {
1753    type Err = AttributeError;
1754
1755    fn from_str(s: &str) -> Result<Self, Self::Err> {
1756        let mut i = s.split(' ');
1757
1758        let Some(tag) = i.next() else {
1759            return Err(AttributeError::ParamNotFound {
1760                param: "Tag".to_string(),
1761                attr: <Self as TypedAttribute>::NAME.to_string(),
1762            });
1763        };
1764
1765        let Ok(tag) = tag.parse::<u32>() else {
1766            return Err(AttributeError::InvalidParamValue {
1767                param: "Tag".to_string(),
1768                val: tag.to_string(),
1769                attr: <Self as TypedAttribute>::NAME.to_string(),
1770            });
1771        };
1772
1773        let Some(crypto_suite) = i.next() else {
1774            return Err(AttributeError::ParamNotFound {
1775                param: "CryptoSuite".to_string(),
1776                attr: <Self as TypedAttribute>::NAME.to_string(),
1777            });
1778        };
1779
1780        let crypto_suite = CryptoSuite::new(crypto_suite);
1781
1782        let Some(key_params_str) = i.next() else {
1783            return Err(AttributeError::ParamNotFound {
1784                param: "Key params".to_string(),
1785                attr: <Self as TypedAttribute>::NAME.to_string(),
1786            });
1787        };
1788
1789        let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1790
1791        for key_param in key_params_str.split(';') {
1792            let key_param = SrtpKeyParam::from_str(key_param)?;
1793            key_params.push(key_param);
1794        }
1795
1796        let mut session_params: Vec<SrtpSessionParam> = Vec::new();
1797        for s in &mut i {
1798            let param = if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("KDR=")) {
1799                let kdr_val = &s[4..];
1800                let Ok(kdr_val) = kdr_val.parse::<u8>() else {
1801                    return Err(AttributeError::InvalidParamValue {
1802                        param: "KDR".to_string(),
1803                        val: kdr_val.to_string(),
1804                        attr: <Self as TypedAttribute>::NAME.to_string(),
1805                    });
1806                };
1807
1808                // Note: the range for KDR value is conflicting in the spec,
1809                // rfc4568#section-6.3.1 says the range should be 1,2,...24 and
1810                // the grammar in rfc4568#section-9.2 says it should be 0..24.
1811                // So using the bigger range i.e., 0..24 for now
1812                if !(0..=24).contains(&kdr_val) {
1813                    return Err(AttributeError::InvalidParamValue {
1814                        param: "KDR".to_string(),
1815                        val: format!("{kdr_val}(expected range 0..24)"),
1816                        attr: <Self as TypedAttribute>::NAME.to_string(),
1817                    });
1818                }
1819                SrtpSessionParam::Kdr(kdr_val)
1820            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTCP") {
1821                SrtpSessionParam::UnencryptedSrtcp
1822            } else if s.eq_ignore_ascii_case("UNENCRYPTED_SRTP") {
1823                SrtpSessionParam::UnencryptedSrtp
1824            } else if s.eq_ignore_ascii_case("UNAUTHENTICATED_SRTP") {
1825                SrtpSessionParam::UnauthenticatedSrtp
1826            } else if s
1827                .get(..10)
1828                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_ORDER="))
1829            {
1830                let fec_ord = &s[10..];
1831                if fec_ord.eq_ignore_ascii_case("FEC_SRTP") {
1832                    SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
1833                } else if fec_ord.eq_ignore_ascii_case("SRTP_FEC") {
1834                    SrtpSessionParam::FecOrder(FecOrder::SrtpFec)
1835                } else {
1836                    return Err(AttributeError::InvalidParamValue {
1837                        param: "FEC order".to_string(),
1838                        val: s.to_string(),
1839                        attr: <Self as TypedAttribute>::NAME.to_string(),
1840                    });
1841                }
1842            } else if s
1843                .get(..8)
1844                .is_some_and(|p| p.eq_ignore_ascii_case("FEC_KEY="))
1845            {
1846                let key_params_str = &s[8..];
1847                let mut key_params: Vec<SrtpKeyParam> = Vec::new();
1848
1849                for key_param in key_params_str.split(';') {
1850                    let key_param = SrtpKeyParam::from_str(key_param)?;
1851                    key_params.push(key_param);
1852                }
1853                SrtpSessionParam::FecKey(key_params)
1854            } else if s.get(..4).is_some_and(|p| p.eq_ignore_ascii_case("WSH=")) {
1855                let wsh_val = &s[4..];
1856                let Ok(wsh_val) = wsh_val.parse::<u8>() else {
1857                    return Err(AttributeError::InvalidParamValue {
1858                        param: "WSH".to_string(),
1859                        val: wsh_val.to_string(),
1860                        attr: <Self as TypedAttribute>::NAME.to_string(),
1861                    });
1862                };
1863
1864                if wsh_val < 64 {
1865                    return Err(AttributeError::InvalidParamValue {
1866                        param: "WSH".to_string(),
1867                        val: wsh_val.to_string(),
1868                        attr: <Self as TypedAttribute>::NAME.to_string(),
1869                    });
1870                }
1871                SrtpSessionParam::Wsh(wsh_val)
1872            } else {
1873                // Extension
1874                SrtpSessionParam::Extension(s.to_string())
1875            };
1876            session_params.push(param);
1877        }
1878
1879        Ok(Self {
1880            tag,
1881            key_params,
1882            crypto_suite,
1883            session_params,
1884        })
1885    }
1886}
1887
1888impl Display for Crypto {
1889    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1890        write!(f, "{} {}", self.tag, self.crypto_suite.as_str())?;
1891
1892        for (i, key_param) in self.key_params.iter().enumerate() {
1893            if i == 0 {
1894                f.write_char(' ')?;
1895            } else {
1896                f.write_char(';')?;
1897            }
1898
1899            write!(f, "{}", key_param)?;
1900        }
1901
1902        for session_param in &self.session_params {
1903            match session_param {
1904                SrtpSessionParam::Kdr(kdr) => write!(f, " KDR={kdr}")?,
1905                SrtpSessionParam::UnencryptedSrtp => write!(f, " UNENCRYPTED_SRTP")?,
1906                SrtpSessionParam::UnencryptedSrtcp => write!(f, " UNENCRYPTED_SRTCP")?,
1907                SrtpSessionParam::UnauthenticatedSrtp => write!(f, " UNAUTHENTICATED_SRTP")?,
1908                SrtpSessionParam::FecOrder(fec_order) => {
1909                    let order = match fec_order {
1910                        FecOrder::FecSrtp => "FEC_SRTP",
1911                        FecOrder::SrtpFec => "SRTP_FEC",
1912                    };
1913                    write!(f, " FEC_ORDER={order}")?;
1914                }
1915                SrtpSessionParam::FecKey(srtp_key_params) => {
1916                    write!(f, " FEC_KEY")?;
1917                    for (i, key_param) in srtp_key_params.iter().enumerate() {
1918                        if i == 0 {
1919                            f.write_char('=')?;
1920                        } else {
1921                            f.write_char(';')?;
1922                        }
1923
1924                        write!(f, "{}", key_param)?;
1925                    }
1926                }
1927                SrtpSessionParam::Wsh(wsh) => write!(f, " WSH={wsh}")?,
1928                SrtpSessionParam::Extension(extn) => {
1929                    f.write_char(' ')?;
1930                    f.write_str(extn)?;
1931                }
1932            }
1933        }
1934        Ok(())
1935    }
1936}
1937
1938impl TypedAttribute for Crypto {
1939    const NAME: &'static str = "crypto";
1940}
1941
1942/// ICE Candidate attribute of the media
1943///
1944/// See [RFC 8839 Section 5.1](https://datatracker.ietf.org/doc/html/rfc8839#section-5.1)
1945#[derive(Debug, PartialEq, Eq, Clone)]
1946#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1947pub struct Candidate {
1948    /// Arbitrary string used in the freezing algorithm to group similar candidates
1949    /// See [RFC 8445 Section 5.1.1.3](https://datatracker.ietf.org/doc/html/rfc8445#section-5.1.1.3)
1950    pub foundation: String,
1951    /// Identifies the specific component of the data stream
1952    /// 1 for RTP and 2 for RTCP
1953    pub component_id: u32,
1954    /// Transport protocol of the candidate
1955    pub transport: String,
1956    /// Candidate's priority
1957    pub priority: u64,
1958    /// IP address of the candidate
1959    /// IPv4, IPv6 addresses and FQDN allowed
1960    pub address: CandidateAddress,
1961    /// Port of the candidate
1962    pub port: u16,
1963    /// Type of the candidate
1964    pub typ: CandidateType,
1965    /// Address related to the candidate
1966    /// Required for srflx, prflx and relay type candidates
1967    pub rel_addr: Option<IpAddr>,
1968    /// Port related to the candidate
1969    /// Required for srflx, prflx and relay type candidates
1970    pub rel_port: Option<u16>,
1971    /// Extensions
1972    pub extensions: Vec<(String, String)>,
1973}
1974
1975impl Candidate {
1976    pub fn new(
1977        foundation: impl ToString,
1978        component_id: u32,
1979        transport: impl ToString,
1980        priority: u64,
1981        address: CandidateAddress,
1982        port: u16,
1983        typ: CandidateType,
1984    ) -> Self {
1985        Candidate {
1986            foundation: foundation.to_string(),
1987            component_id,
1988            transport: transport.to_string(),
1989            priority,
1990            address,
1991            port,
1992            typ,
1993            rel_addr: None,
1994            rel_port: None,
1995            extensions: vec![],
1996        }
1997    }
1998
1999    #[allow(clippy::too_many_arguments)]
2000    pub fn with_rel_addr(
2001        foundation: impl ToString,
2002        component_id: u32,
2003        transport: impl ToString,
2004        priority: u64,
2005        address: CandidateAddress,
2006        port: u16,
2007        typ: CandidateType,
2008        rel_addr: impl Into<IpAddr>,
2009    ) -> Self {
2010        Candidate {
2011            foundation: foundation.to_string(),
2012            component_id,
2013            transport: transport.to_string(),
2014            priority,
2015            address,
2016            port,
2017            typ,
2018            rel_addr: Some(rel_addr.into()),
2019            rel_port: None,
2020            extensions: vec![],
2021        }
2022    }
2023
2024    #[allow(clippy::too_many_arguments)]
2025    pub fn with_rel_addr_and_port(
2026        foundation: impl ToString,
2027        component_id: u32,
2028        transport: impl ToString,
2029        priority: u64,
2030        address: CandidateAddress,
2031        port: u16,
2032        typ: CandidateType,
2033        rel_addr: impl Into<IpAddr>,
2034        rel_port: u16,
2035    ) -> Self {
2036        Candidate {
2037            foundation: foundation.to_string(),
2038            component_id,
2039            transport: transport.to_string(),
2040            priority,
2041            address,
2042            port,
2043            typ,
2044            rel_addr: Some(rel_addr.into()),
2045            rel_port: Some(rel_port),
2046            extensions: vec![],
2047        }
2048    }
2049
2050    pub fn set_rel_addr(&mut self, rel_addr: impl Into<IpAddr>) {
2051        self.rel_addr = Some(rel_addr.into());
2052    }
2053
2054    pub fn set_rel_port(&mut self, rel_port: u16) {
2055        self.rel_port = Some(rel_port);
2056    }
2057
2058    pub fn add_extension(&mut self, name: impl ToString, value: impl ToString) {
2059        self.extensions.push((name.to_string(), value.to_string()))
2060    }
2061}
2062
2063impl FromStr for Candidate {
2064    type Err = AttributeError;
2065
2066    fn from_str(s: &str) -> Result<Self, Self::Err> {
2067        let mut i = s.split(' ');
2068
2069        let Some(foundation) = i.next() else {
2070            return Err(AttributeError::ParamNotFound {
2071                param: "Foundation".to_string(),
2072                attr: <Self as TypedAttribute>::NAME.to_string(),
2073            });
2074        };
2075
2076        let Some(comp_id) = i.next() else {
2077            return Err(AttributeError::ParamNotFound {
2078                param: "Component id".to_string(),
2079                attr: <Self as TypedAttribute>::NAME.to_string(),
2080            });
2081        };
2082
2083        let Ok(comp_id) = comp_id.parse::<u32>() else {
2084            return Err(AttributeError::InvalidParamValue {
2085                param: "Component id".to_string(),
2086                val: comp_id.to_string(),
2087                attr: <Self as TypedAttribute>::NAME.to_string(),
2088            });
2089        };
2090
2091        let Some(transport) = i.next() else {
2092            return Err(AttributeError::ParamNotFound {
2093                param: "Transport".to_string(),
2094                attr: <Self as TypedAttribute>::NAME.to_string(),
2095            });
2096        };
2097
2098        let Some(priority) = i.next() else {
2099            return Err(AttributeError::ParamNotFound {
2100                param: "Priority".to_string(),
2101                attr: <Self as TypedAttribute>::NAME.to_string(),
2102            });
2103        };
2104
2105        let Ok(priority) = priority.parse::<u64>() else {
2106            return Err(AttributeError::InvalidParamValue {
2107                param: "Priority".to_string(),
2108                val: priority.to_string(),
2109                attr: <Self as TypedAttribute>::NAME.to_string(),
2110            });
2111        };
2112
2113        let Some(address) = i.next() else {
2114            return Err(AttributeError::ParamNotFound {
2115                param: "Address".to_string(),
2116                attr: <Self as TypedAttribute>::NAME.to_string(),
2117            });
2118        };
2119
2120        let address = match address.parse::<IpAddr>() {
2121            Ok(a) => CandidateAddress::IpAddr(a),
2122            Err(_) => CandidateAddress::FQDN(address.to_string()),
2123        };
2124
2125        let Some(port) = i.next() else {
2126            return Err(AttributeError::ParamNotFound {
2127                param: "Port".to_string(),
2128                attr: <Self as TypedAttribute>::NAME.to_string(),
2129            });
2130        };
2131
2132        let Ok(port) = port.parse::<u16>() else {
2133            return Err(AttributeError::InvalidParamValue {
2134                param: "Port".to_string(),
2135                val: port.to_string(),
2136                attr: <Self as TypedAttribute>::NAME.to_string(),
2137            });
2138        };
2139
2140        let Some(typ_str) = i.next() else {
2141            return Err(AttributeError::ParamNotFound {
2142                param: "'typ' string".to_string(),
2143                attr: <Self as TypedAttribute>::NAME.to_string(),
2144            });
2145        };
2146
2147        if !typ_str.eq_ignore_ascii_case("typ") {
2148            return Err(AttributeError::ParamNotFound {
2149                param: "'typ' string".to_string(),
2150                attr: <Self as TypedAttribute>::NAME.to_string(),
2151            });
2152        }
2153
2154        let Some(cand_type) = i.next() else {
2155            return Err(AttributeError::ParamNotFound {
2156                param: "Candidate type".to_string(),
2157                attr: <Self as TypedAttribute>::NAME.to_string(),
2158            });
2159        };
2160
2161        let cand_type = CandidateType::new(cand_type);
2162
2163        let mut rel_addr: Option<IpAddr> = None;
2164        let mut rel_port: Option<u16> = None;
2165        let mut exts: Vec<(String, String)> = Vec::new();
2166
2167        while let Some(key) = i.next() {
2168            if key.eq_ignore_ascii_case("raddr") {
2169                let Some(raddr) = i.next() else {
2170                    return Err(AttributeError::ParamNotFound {
2171                        param: "Relative address".to_string(),
2172                        attr: <Self as TypedAttribute>::NAME.to_string(),
2173                    });
2174                };
2175
2176                if let Ok(raddr) = raddr.parse::<IpAddr>() {
2177                    rel_addr = Some(raddr);
2178                } else {
2179                    return Err(AttributeError::InvalidParamValue {
2180                        param: "Relative address".to_string(),
2181                        val: raddr.to_string(),
2182                        attr: <Self as TypedAttribute>::NAME.to_string(),
2183                    });
2184                };
2185            } else if key.eq_ignore_ascii_case("rport") {
2186                let Some(rport) = i.next() else {
2187                    return Err(AttributeError::ParamNotFound {
2188                        param: "Relative port".to_string(),
2189                        attr: <Self as TypedAttribute>::NAME.to_string(),
2190                    });
2191                };
2192
2193                if let Ok(rport) = rport.parse::<u16>() {
2194                    rel_port = Some(rport);
2195                } else {
2196                    return Err(AttributeError::InvalidParamValue {
2197                        param: "Relative port".to_string(),
2198                        val: rport.to_string(),
2199                        attr: <Self as TypedAttribute>::NAME.to_string(),
2200                    });
2201                }
2202            } else {
2203                let Some(val) = i.next() else {
2204                    return Err(AttributeError::Other {
2205                        error: format!("No val for the extension {key}"),
2206                        attr: <Self as TypedAttribute>::NAME.to_string(),
2207                    });
2208                };
2209
2210                exts.push((key.to_string(), val.to_string()));
2211            }
2212        }
2213
2214        Ok(Self {
2215            foundation: foundation.to_string(),
2216            component_id: comp_id,
2217            transport: transport.to_string(),
2218            priority,
2219            address,
2220            port,
2221            typ: cand_type,
2222            rel_addr,
2223            rel_port,
2224            extensions: exts,
2225        })
2226    }
2227}
2228
2229impl Display for Candidate {
2230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2231        let candidate_addr = match &self.address {
2232            CandidateAddress::IpAddr(a) => a.to_string(),
2233            CandidateAddress::FQDN(d) => d.clone(),
2234        };
2235        write!(
2236            f,
2237            "{} {} {} {} {} {} typ {}",
2238            self.foundation,
2239            self.component_id,
2240            self.transport,
2241            self.priority,
2242            candidate_addr,
2243            self.port,
2244            self.typ.as_str(),
2245        )?;
2246        if let Some(rel_addr) = self.rel_addr {
2247            write!(f, " raddr {rel_addr}")?;
2248        }
2249        if let Some(rel_port) = self.rel_port {
2250            write!(f, " rport {rel_port}")?;
2251        }
2252        for (key, val) in &self.extensions {
2253            write!(f, " {key} {val}")?;
2254        }
2255        Ok(())
2256    }
2257}
2258
2259impl TypedAttribute for Candidate {
2260    const NAME: &'static str = "candidate";
2261}
2262
2263#[cfg(test)]
2264mod tests {
2265    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
2266
2267    use super::*;
2268    use crate::*;
2269
2270    #[test]
2271    fn direction_parse() {
2272        assert_eq!("sendonly".parse::<Direction>(), Ok(Direction::SendOnly));
2273        assert_eq!("recvonly".parse::<Direction>(), Ok(Direction::RecvOnly));
2274        assert_eq!("sendrecv".parse::<Direction>(), Ok(Direction::SendRecv));
2275        assert_eq!("inactive".parse::<Direction>(), Ok(Direction::Inactive));
2276        assert!("invalid".parse::<Direction>().is_err());
2277    }
2278
2279    #[test]
2280    fn direction_display() {
2281        assert_eq!(Direction::SendOnly.to_string(), "sendonly");
2282        assert_eq!(Direction::RecvOnly.to_string(), "recvonly");
2283        assert_eq!(Direction::SendRecv.to_string(), "sendrecv");
2284        assert_eq!(Direction::Inactive.to_string(), "inactive");
2285    }
2286
2287    #[test]
2288    fn parse_rtcp_fb() {
2289        let sdp = "v=0\r
2290o=alice 3203093520 3203093520 IN IP4 host.example.com\r
2291s=Multicast video with feedback\r
2292t=3203130148 3203137348\r
2293m=audio 49170 RTP/AVP 0\r
2294c=IN IP4 224.2.1.183\r
2295a=rtpmap:0 PCMU/8000\r
2296m=video 51372 RTP/AVPF 98 99\r
2297c=IN IP4 224.2.1.184\r
2298a=rtpmap:98 H263-1998/90000\r
2299a=rtpmap:99 H261/90000\r
2300a=rtcp-fb:* nack\r
2301a=rtcp-fb:98 nack rpsi\r
2302a=rtcp-fb:* trr-int 1000\r
2303a=rtcp-fb:98 ccm vbcm 1 2\r
2304a=rtcp-fb:* ccm tmmbr smaxpr=120\r
2305";
2306
2307        let parsed = Session::parse(sdp.as_bytes()).unwrap();
2308        let mut written = vec![];
2309        parsed.write(&mut written).unwrap();
2310
2311        let v = fallible_iterator::convert(parsed.medias[1].attributes_typed::<RtcpFb>())
2312            .collect::<Vec<_>>()
2313            .expect("Valid vector of attributes");
2314        assert_eq!(v[0].pt, RtcpFbPt::Wildcard);
2315        assert_eq!(v[1].val, RtcpFbVal::Nack(Some(RtcpFbNack::Rpsi)));
2316        assert_eq!(v[2].val, RtcpFbVal::TrrInt(1000));
2317        assert_eq!(v[3].val, RtcpFbVal::Ccm(RtcpFbCcm::Vbcm(vec![1, 2])));
2318        assert_eq!(
2319            v[4].val,
2320            RtcpFbVal::Ccm(RtcpFbCcm::Tmmbr(Some("smaxpr=120".to_string())))
2321        );
2322    }
2323
2324    #[test]
2325    fn parse_group_attribute() {
2326        let sdp = "v=0\r
2327o=Laura 289083124 289083124 IN IP4 two.example.com\r
2328c=IN IP4 233.252.0.1/127\r
2329t=0 0\r
2330a=group:LS 1 2\r
2331m=audio 30000 RTP/AVP 0\r
2332a=mid:1\r
2333m=video 30002 RTP/AVP 31\r
2334a=mid:2\r
2335m=audio 30004 RTP/AVP 0\r
2336i=This media stream contains the Spanish translation\r
2337a=mid:3\r
2338";
2339        let parsed = Session::parse(sdp.as_bytes()).unwrap();
2340
2341        let g = parsed.attributes_typed::<Group>().collect::<Vec<_>>();
2342        assert_eq!(g.len(), 1);
2343        assert_eq!(g[0].as_ref().unwrap().semantics, GroupSemantics::LS);
2344        assert_eq!(
2345            g[0].as_ref().unwrap().mid_tags,
2346            vec!["1".to_string(), "2".to_string()]
2347        );
2348    }
2349
2350    #[test]
2351    fn parse_setup_attribute() {
2352        let sdp = "v=0\r
2353m=image 54111 TCP t38\r
2354c=IN IP4 192.0.2.2\r
2355a=setup:actpass\r
2356a=connection:new\r
2357";
2358        let media = Session::parse(sdp.as_bytes()).unwrap().medias;
2359
2360        let s = media[0].attributes_typed::<Setup>().collect::<Vec<_>>();
2361
2362        assert_eq!(s.len(), 1);
2363        assert_eq!(s[0].as_ref().unwrap().to_owned(), Setup::ActPass);
2364    }
2365
2366    #[test]
2367    fn parse_ssrc_attributes() {
2368        let sdp = "v=0\r
2369o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
2370m=video 49174 RTP/AVPF 96 98\r
2371a=rtpmap:98 rtx/90000\r
2372a=fmtp:98 apt=96;rtx-time=3000\r
2373a=ssrc-group:FID 11111 22222\r
2374a=ssrc:11111 cname:user3@example.com\r
2375a=ssrc:22222 fmtp:0 0-15\r
2376a=ssrc-group:FID 33333 44444\r
2377a=ssrc:33333 cname:user3@example.com\r
2378a=ssrc:44444 cname:user3@example.com\r
2379a=ssrc:1698359993 rtcp:5003 IN IP4 127.0.0.1
2380";
2381
2382        let parsed = Session::parse(sdp.as_bytes()).unwrap();
2383        let m = &parsed.medias[0];
2384
2385        let ssrcs = m
2386            .attributes_typed::<Ssrc>()
2387            .filter(|s| {
2388                let Ok(ssrc) = s else { return false };
2389                ssrc.attribute == SsrcAttribute::Fmtp || ssrc.attribute == SsrcAttribute::Rtcp
2390            })
2391            .collect::<Vec<_>>();
2392
2393        let ssrc_id = ssrcs[0].as_ref().unwrap().ssrc_id;
2394
2395        let ssrc_groups = m
2396            .attributes_typed::<SsrcGroup>()
2397            .filter(|s| {
2398                let Ok(ssrc_group) = s else { return false };
2399
2400                ssrc_group.ssrc_ids[1] == ssrc_id
2401            })
2402            .collect::<Vec<_>>();
2403
2404        assert_eq!(
2405            ssrc_groups[0].as_ref().unwrap().semantics,
2406            GroupSemantics::FID
2407        );
2408
2409        assert_eq!(ssrcs[1].as_ref().unwrap().attribute, SsrcAttribute::Rtcp);
2410    }
2411
2412    #[test]
2413    fn parse_crypto_attributes() {
2414        let sdp = "v=0\r
2415o=sam 2890844526 2890842807 IN IP4 10.47.16.5\r
2416s=SRTP Discussion\r
2417i=A discussion of Secure RTP\r
2418u=http://www.example.com/seminars/srtp.pdf\r
2419e=marge@example.com (Marge Simpson)\r
2420c=IN IP4 168.2.17.12\r
2421t=2873397496 2873404696\r
2422m=audio 49170 RTP/SAVP 0\r
2423a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC\r
2424a=crypto:2 F8_128_HMAC_SHA1_80 inline:MTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5QUJjZGVm|2^20|1:4;inline:QUJjZGVmMTIzNDU2Nzg5QUJDREUwMTIzNDU2Nzg5|2^20|2:4 FEC_ORDER=FEC_SRTP\r
2425m=video 51372 RTP/SAVP 31\r
2426a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2|1066:4\r
2427";
2428
2429        let parsed = Session::parse(sdp.as_bytes()).unwrap();
2430        let a = &parsed.medias[0];
2431
2432        let audio_cryptos = a.attributes_typed::<Crypto>().collect::<Vec<_>>();
2433
2434        assert_eq!(
2435            audio_cryptos[0].as_ref().unwrap().crypto_suite,
2436            CryptoSuite::AesCm128HmacSha1_80
2437        );
2438        assert_eq!(audio_cryptos[1].as_ref().unwrap().key_params.len(), 2);
2439
2440        assert_eq!(
2441            audio_cryptos[1].as_ref().unwrap().key_params[1].mki_and_length,
2442            Some((2, 4))
2443        );
2444
2445        assert_eq!(
2446            audio_cryptos[1].as_ref().unwrap().session_params[0],
2447            SrtpSessionParam::FecOrder(FecOrder::FecSrtp)
2448        );
2449
2450        let v = &parsed.medias[1];
2451
2452        let video_cryptos = v
2453            .attributes_typed::<Crypto>()
2454            .filter(|c| {
2455                let Ok(crypto) = c else { return false };
2456
2457                crypto.tag == 1
2458            })
2459            .collect::<Vec<_>>();
2460
2461        let test_crypto = Crypto {
2462            tag: 1,
2463            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
2464            key_params: vec![SrtpKeyParam {
2465                key_and_salt: "YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2".to_string(),
2466                lifetime: None,
2467                mki_and_length: Some((1066, 4)),
2468            }],
2469            session_params: Vec::new(),
2470        };
2471
2472        assert_eq!(&test_crypto, video_cryptos[0].as_ref().unwrap());
2473    }
2474
2475    #[test]
2476    fn write_crypto_attribute() {
2477        let crypto = Crypto {
2478            tag: 1,
2479            crypto_suite: CryptoSuite::AesCm128HmacSha1_80,
2480            key_params: vec![
2481                SrtpKeyParam {
2482                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
2483                    lifetime: Some(1048576),
2484                    mki_and_length: Some((1, 4)),
2485                },
2486                SrtpKeyParam {
2487                    key_and_salt: "WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz".to_string(),
2488                    lifetime: Some(1048576),
2489                    mki_and_length: Some((1, 4)),
2490                },
2491            ],
2492            session_params: vec![SrtpSessionParam::FecOrder(FecOrder::SrtpFec)],
2493        };
2494
2495        assert_eq!(
2496            crypto.to_string(),
2497            "1 AES_CM_128_HMAC_SHA1_80 inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4;inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz|2^20|1:4 FEC_ORDER=SRTP_FEC"
2498        );
2499    }
2500
2501    #[test]
2502    fn parse_srtp_key_param_lifetime() {
2503        // RFC 4568 allows lifetimes up to 2^48, which doesn't fit into u32.
2504        let param = SrtpKeyParam::from_str("inline:YWJj|2^48").unwrap();
2505        assert_eq!(param.lifetime, Some(1 << 48));
2506        assert_eq!(param.to_string(), "inline:YWJj|2^48");
2507
2508        let param = SrtpKeyParam::from_str("inline:YWJj|1000000000000").unwrap();
2509        assert_eq!(param.lifetime, Some(1_000_000_000_000));
2510        assert_eq!(param.to_string(), "inline:YWJj|1000000000000");
2511
2512        for invalid in [
2513            // larger than the maximum of 2^48
2514            "inline:YWJj|2^49",
2515            "inline:YWJj|2^100",
2516            "inline:YWJj|281474976710657",
2517            // zero is not a positive integer
2518            "inline:YWJj|0",
2519            // leading zeroes and signs are not allowed
2520            "inline:YWJj|007",
2521            "inline:YWJj|0123",
2522            "inline:YWJj|2^007",
2523            "inline:YWJj|+1048576",
2524            "inline:YWJj|2^+20",
2525        ] {
2526            assert!(
2527                SrtpKeyParam::from_str(invalid).is_err(),
2528                "{} should be invalid",
2529                invalid
2530            );
2531        }
2532    }
2533
2534    #[test]
2535    fn parse_candidate_attributes() {
2536        use std::net::{Ipv4Addr, Ipv6Addr};
2537
2538        let sdp = "v=0\r
2539o=- 2890844526 2890842807 IN IP4 192.168.1.1\r
2540s=-\r
2541c=IN IP4 192.168.1.1\r
2542t=0 0\r
2543m=audio 49152 RTP/AVP 0\r
2544a=candidate:1 1 UDP 2130706432 192.168.1.1 49152 typ host raddr 10.0.1.1 rport 49153 generation 0\r
2545a=candidate:2 1 UDP 1692467200 10.0.1.1 49152 typ srflx raddr 192.168.1.1 rport 49153\r
2546a=candidate:3 2 UDP 1692467184 192.168.1.1 49153 typ host\r
2547a=candidate:4 1 UDP 100 2001:db8::1 49152 typ host\r
2548a=candidate:5 1 UDP 50 192.168.1.1 49154 typ prflx\r
2549a=candidate:6 1 UDP 25 192.168.1.1 49155 typ relay raddr 10.0.0.1 rport 49156\r
2550a=candidate:7 1 UDP 10 192.168.1.1 49157 typ unknown_type\r
2551";
2552
2553        let session = Session::parse(sdp.as_bytes()).unwrap();
2554        let candidates: Vec<Candidate> =
2555            fallible_iterator::convert(session.medias[0].attributes_typed::<Candidate>())
2556                .collect::<Vec<_>>()
2557                .expect("Valid vector of candidates");
2558
2559        assert_eq!(candidates.len(), 7);
2560
2561        assert_eq!(candidates[0].foundation, "1");
2562        assert_eq!(candidates[0].component_id, 1);
2563        assert_eq!(
2564            candidates[0].address,
2565            CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
2566        );
2567        assert_eq!(candidates[0].port, 49152);
2568        assert_eq!(candidates[0].typ, CandidateType::Host);
2569        assert_eq!(
2570            candidates[0].rel_addr,
2571            Some(IpAddr::V4(Ipv4Addr::new(10, 0, 1, 1)))
2572        );
2573        assert_eq!(candidates[0].rel_port, Some(49153));
2574        assert_eq!(
2575            candidates[0].extensions,
2576            vec![("generation".to_string(), "0".to_string())]
2577        );
2578
2579        assert_eq!(candidates[1].foundation, "2");
2580        assert_eq!(candidates[1].typ, CandidateType::Srflx);
2581        assert_eq!(
2582            candidates[1].rel_addr,
2583            Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
2584        );
2585        assert_eq!(candidates[1].rel_port, Some(49153));
2586
2587        assert_eq!(candidates[2].foundation, "3");
2588        assert_eq!(candidates[2].component_id, 2);
2589        assert_eq!(candidates[2].typ, CandidateType::Host);
2590
2591        assert_eq!(candidates[3].foundation, "4");
2592        assert_eq!(
2593            candidates[3].address,
2594            CandidateAddress::IpAddr(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)))
2595        );
2596        assert_eq!(candidates[3].typ, CandidateType::Host);
2597
2598        assert_eq!(candidates[4].foundation, "5");
2599        assert_eq!(candidates[4].typ, CandidateType::Prflx);
2600
2601        assert_eq!(candidates[5].foundation, "6");
2602        assert_eq!(candidates[5].typ, CandidateType::Relay);
2603
2604        assert_eq!(candidates[6].foundation, "7");
2605        assert_eq!(
2606            candidates[6].typ,
2607            CandidateType::Other("unknown_type".to_string())
2608        );
2609    }
2610
2611    #[test]
2612    fn write_candidate() {
2613        use std::net::Ipv4Addr;
2614
2615        let candidate = Candidate {
2616            foundation: "abcd/1234".into(),
2617            component_id: 1,
2618            transport: "UDP".into(),
2619            priority: 2130706432,
2620            address: CandidateAddress::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
2621            port: 49152,
2622            typ: CandidateType::Srflx,
2623            rel_addr: Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))),
2624            rel_port: Some(49153),
2625            extensions: vec![("tcptype".to_string(), "active".to_string())],
2626        };
2627
2628        assert_eq!(
2629            candidate.to_string(),
2630            "abcd/1234 1 UDP 2130706432 192.168.0.1 49152 typ srflx raddr 10.0.0.1 rport 49153 tcptype active"
2631        );
2632    }
2633
2634    #[test]
2635    fn test_attribute_errors() {
2636        // Test RtpMap error paths
2637        assert_eq!(
2638            "99".parse::<RtpMap>().unwrap_err(),
2639            AttributeError::UnsupportedFormat {
2640                val: "99".to_string(),
2641                attr: "rtpmap".to_string()
2642            }
2643        );
2644        assert_eq!(
2645            "abc 90000".parse::<RtpMap>().unwrap_err(),
2646            AttributeError::InvalidParamValue {
2647                param: "Payload type".to_string(),
2648                val: "abc".to_string(),
2649                attr: "rtpmap".to_string()
2650            }
2651        );
2652        assert_eq!(
2653            "200 enc/90000".parse::<RtpMap>().unwrap_err(),
2654            AttributeError::InvalidParamValue {
2655                param: "Payload type".to_string(),
2656                val: "200(expected 0-127)".to_string(),
2657                attr: "rtpmap".to_string()
2658            }
2659        );
2660        assert_eq!(
2661            "99 ".parse::<RtpMap>().unwrap_err(),
2662            AttributeError::ParamNotFound {
2663                param: "Clock rate".to_string(),
2664                attr: "rtpmap".to_string()
2665            }
2666        );
2667        assert_eq!(
2668            "99 /".parse::<RtpMap>().unwrap_err(),
2669            AttributeError::InvalidParamValue {
2670                param: "Clock rate".to_string(),
2671                val: "".to_string(),
2672                attr: "rtpmap".to_string()
2673            }
2674        );
2675
2676        assert_eq!(
2677            "invalid".parse::<Fmtp>().unwrap_err(),
2678            AttributeError::UnsupportedFormat {
2679                val: "invalid".to_string(),
2680                attr: "fmtp".to_string()
2681            }
2682        );
2683        assert_eq!(
2684            "abc profile=1".parse::<Fmtp>().unwrap_err(),
2685            AttributeError::InvalidParamValue {
2686                param: "fmtp".to_string(),
2687                val: "abc".to_string(),
2688                attr: "fmtp".to_string()
2689            }
2690        );
2691
2692        // Test Rtcp error paths
2693        assert_eq!(
2694            "".parse::<Rtcp>().unwrap_err(),
2695            AttributeError::ParamNotFound {
2696                param: "Port".to_string(),
2697                attr: "rtcp".to_string()
2698            }
2699        );
2700        assert_eq!(
2701            "abc IN IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
2702            AttributeError::InvalidParamValue {
2703                param: "Port".to_string(),
2704                val: "abc".to_string(),
2705                attr: "rtcp".to_string()
2706            }
2707        );
2708
2709        // Test Fingerprint error paths
2710        assert_eq!(
2711            "".parse::<Fingerprint>().unwrap_err(),
2712            AttributeError::ParamNotFound {
2713                param: "Hash value".to_string(),
2714                attr: "fingerprint".to_string()
2715            }
2716        );
2717        assert_eq!(
2718            "SHA-1".parse::<Fingerprint>().unwrap_err(),
2719            AttributeError::ParamNotFound {
2720                param: "Hash value".to_string(),
2721                attr: "fingerprint".to_string()
2722            }
2723        );
2724
2725        // Test Candidate error paths
2726        assert_eq!(
2727            "".parse::<Candidate>().unwrap_err(),
2728            AttributeError::ParamNotFound {
2729                param: "Component id".to_string(),
2730                attr: "candidate".to_string()
2731            }
2732        );
2733        assert_eq!(
2734            "1 1 UDP 100".parse::<Candidate>().unwrap_err(),
2735            AttributeError::ParamNotFound {
2736                param: "Address".to_string(),
2737                attr: "candidate".to_string()
2738            }
2739        );
2740
2741        // Test ExtMap error paths
2742        assert_eq!(
2743            "".parse::<ExtMap>().unwrap_err(),
2744            AttributeError::InvalidParamValue {
2745                param: "Id".to_string(),
2746                val: "".to_string(),
2747                attr: "extmap".to_string()
2748            }
2749        );
2750        assert_eq!(
2751            "999999 http://example.com".parse::<ExtMap>().unwrap_err(),
2752            AttributeError::InvalidParamValue {
2753                param: "Id".to_string(),
2754                val: "999999".to_string(),
2755                attr: "extmap".to_string()
2756            }
2757        );
2758
2759        // Test Group error paths
2760        assert_eq!(
2761            "".parse::<Group>().unwrap_err(),
2762            AttributeError::ParamNotFound {
2763                param: "Media identification tags".to_string(),
2764                attr: "group".to_string()
2765            }
2766        );
2767        assert_eq!(
2768            "LS".parse::<Group>().unwrap_err(),
2769            AttributeError::ParamNotFound {
2770                param: "Media identification tags".to_string(),
2771                attr: "group".to_string()
2772            }
2773        );
2774
2775        // Test Ssrc error paths
2776        assert_eq!(
2777            "".parse::<Ssrc>().unwrap_err(),
2778            AttributeError::ParamNotFound {
2779                param: "Ssrc id".to_string(),
2780                attr: "ssrc".to_string()
2781            }
2782        );
2783        assert_eq!(
2784            "abc".parse::<Ssrc>().unwrap_err(),
2785            AttributeError::ParamNotFound {
2786                param: "Ssrc id".to_string(),
2787                attr: "ssrc".to_string()
2788            }
2789        );
2790
2791        // Test Setup error paths
2792        let setup_err = "foo".parse::<Setup>().err().unwrap();
2793        assert!(matches!(setup_err, AttributeError::Other { .. }));
2794        assert_eq!(format!("{}", setup_err), "setup: Invalid Setup value foo");
2795
2796        // Test Crypto error paths
2797        assert_eq!(
2798            "".parse::<Crypto>().unwrap_err(),
2799            AttributeError::InvalidParamValue {
2800                param: "Tag".to_string(),
2801                val: "".to_string(),
2802                attr: "crypto".to_string()
2803            }
2804        );
2805        assert_eq!(
2806            "abc AES_CM_128_HMAC_SHA1_32 inline:key"
2807                .parse::<Crypto>()
2808                .unwrap_err(),
2809            AttributeError::InvalidParamValue {
2810                param: "Tag".to_string(),
2811                val: "abc".to_string(),
2812                attr: "crypto".to_string()
2813            }
2814        );
2815
2816        // Test RtcpFb error paths
2817        assert_eq!(
2818            "".parse::<RtcpFb>().unwrap_err(),
2819            AttributeError::InvalidParamValue {
2820                param: "Payload format".to_string(),
2821                val: "".to_string(),
2822                attr: "rtcp-fb".to_string()
2823            }
2824        );
2825        assert_eq!(
2826            "*".parse::<RtcpFb>().unwrap_err(),
2827            AttributeError::ParamNotFound {
2828                param: "Rtcp feedback value".to_string(),
2829                attr: "rtcp-fb".to_string()
2830            }
2831        );
2832        assert_eq!(
2833            "1 ack ccfb".parse::<RtcpFb>().unwrap_err(),
2834            AttributeError::InvalidParamValue {
2835                param: "Payload type of Congestion control feedback (ccfb)".to_string(),
2836                val: "1(expected wildcard (*))".to_string(),
2837                attr: "rtcp-fb".to_string()
2838            }
2839        );
2840
2841        // Test attribute_typed with missing value
2842        let media = Media {
2843            media: "video".into(),
2844            port: 1234,
2845            num_ports: None,
2846            proto: "RTP/SAVPF".into(),
2847            fmt: "".into(),
2848            media_title: None,
2849            connections: vec![],
2850            bandwidths: vec![],
2851            key: None,
2852            attributes: vec![Attribute {
2853                attribute: "rtpmap".into(),
2854                value: None,
2855            }],
2856        };
2857        assert_eq!(
2858            media
2859                .attributes_typed::<RtpMap>()
2860                .collect::<Vec<Result<RtpMap, AttributeError>>>()
2861                .remove(0)
2862                .unwrap_err(),
2863            AttributeError::Other {
2864                error: "No value for the attribute".to_string(),
2865                attr: "rtpmap".to_string()
2866            }
2867        );
2868    }
2869
2870    #[test]
2871    fn parse_rtcp_address() {
2872        let sdp = "v=0\r
2873o=alice 3203093520 3203093520 IN IP4 host.example.com\r
2874s=parse rtcp attribute address test\r
2875a=rtcp:5000 IN IP4 127.0.0.1\r
2876a=rtcp:5000 IN IP4 127.0.0.0/24\r
2877a=rtcp:5000 IN IP6 ::1\r
2878a=rtcp:5000 IN NONIP non-IP\r
2879a=rtcp:53020\r
2880";
2881
2882        let parsed = Session::parse(sdp.as_bytes()).unwrap();
2883        let mut rtcp_attr_iter = parsed.attributes_typed::<Rtcp>();
2884
2885        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
2886        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
2887        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.1"));
2888        assert_eq!(
2889            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
2890            IpAddr::V4(Ipv4Addr::LOCALHOST),
2891        );
2892
2893        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
2894        assert_eq!(rtcp.addrtype, Some(AddrType::Ip4));
2895        assert_eq!(rtcp.connection_address.as_deref(), Some("127.0.0.0/24"));
2896        assert_eq!(
2897            &rtcp
2898                .try_parse_connection_ip_address()
2899                .unwrap()
2900                .expect_err("IPv4 with mask")
2901                .to_string(),
2902            "127.0.0.0/24"
2903        );
2904
2905        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
2906        assert_eq!(rtcp.addrtype, Some(AddrType::Ip6));
2907        assert_eq!(rtcp.connection_address.as_deref(), Some("::1"));
2908        assert_eq!(
2909            rtcp.try_parse_connection_ip_address().unwrap().unwrap(),
2910            IpAddr::V6(Ipv6Addr::LOCALHOST),
2911        );
2912
2913        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
2914        assert_eq!(rtcp.addrtype, Some(AddrType::Other("NONIP".to_string())));
2915        assert_eq!(rtcp.connection_address.as_deref(), Some("non-IP"));
2916        assert_eq!(
2917            rtcp.try_parse_connection_ip_address().unwrap().unwrap_err(),
2918            "non-IP",
2919        );
2920
2921        // RFC 3605: the address part is optional.
2922        let rtcp = rtcp_attr_iter.next().unwrap().unwrap();
2923        assert_eq!(rtcp.port, 53020);
2924        assert_eq!(rtcp.nettype, None);
2925        assert_eq!(rtcp.addrtype, None);
2926        assert_eq!(rtcp.connection_address, None);
2927        assert_eq!(rtcp.try_parse_connection_ip_address(), None);
2928        assert_eq!(rtcp.to_string(), "53020");
2929    }
2930
2931    #[test]
2932    fn parse_rtcp_attribute_whitespace() {
2933        // Runs of spaces are a single separator, like in o= and c= lines,
2934        // and empty fields are rejected instead of ending up as e.g. an
2935        // empty `nettype` or `addrtype`.
2936        assert_eq!(
2937            "6000  IN IP4 127.0.0.1".parse::<Rtcp>().unwrap(),
2938            Rtcp::new(6000, NetType::In, AddrType::Ip4, "127.0.0.1")
2939        );
2940        assert_eq!(
2941            "6001 IN  IP4 127.0.0.1 ".parse::<Rtcp>().unwrap(),
2942            Rtcp::new(6001, NetType::In, AddrType::Ip4, "127.0.0.1")
2943        );
2944        assert_eq!("53020 ".parse::<Rtcp>().unwrap(), Rtcp::with_port(53020));
2945
2946        assert!("6002 IN  127.0.0.1".parse::<Rtcp>().is_err());
2947        assert!("6003 IN IP4 127.0.0.1 extra".parse::<Rtcp>().is_err());
2948    }
2949
2950    #[test]
2951    fn parse_rtcp_nettype_and_addrtype_must_be_tokens() {
2952        // RFC 3605 reuses the SDP nettype and addrtype tokens (RFC 8866,
2953        // section 9), so reject other values like the o= and c= parsers do.
2954        assert_eq!(
2955            "6000 a<b IP4 127.0.0.1".parse::<Rtcp>().unwrap_err(),
2956            AttributeError::InvalidParamValue {
2957                param: "Network type".to_string(),
2958                val: "a<b".to_string(),
2959                attr: "rtcp".to_string(),
2960            }
2961        );
2962        assert_eq!(
2963            "6000 IN IP4/x 127.0.0.1".parse::<Rtcp>().unwrap_err(),
2964            AttributeError::InvalidParamValue {
2965                param: "Address type".to_string(),
2966                val: "IP4/x".to_string(),
2967                attr: "rtcp".to_string(),
2968            }
2969        );
2970    }
2971}