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