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