Skip to main content

webrtc/peer_connection/policy/
sdp_semantics.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// SDPSemantics determines which style of SDP offers and answers
6/// can be used.
7///
8/// This is unused, we only support UnifiedPlan.
9#[derive(Default, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
10pub enum RTCSdpSemantics {
11    Unspecified = 0,
12
13    /// UnifiedPlan uses unified-plan offers and answers
14    /// (the default in Chrome since M72)
15    /// <https://tools.ietf.org/html/draft-roach-mmusic-unified-plan-00>
16    #[serde(rename = "unified-plan")]
17    #[default]
18    UnifiedPlan = 1,
19
20    /// PlanB uses plan-b offers and answers
21    /// NB: This format should be considered deprecated
22    /// <https://tools.ietf.org/html/draft-uberti-rtcweb-plan-00>
23    #[serde(rename = "plan-b")]
24    PlanB = 2,
25
26    /// UnifiedPlanWithFallback prefers unified-plan
27    /// offers and answers, but will respond to a plan-b offer
28    /// with a plan-b answer
29    #[serde(rename = "unified-plan-with-fallback")]
30    UnifiedPlanWithFallback = 3,
31}
32
33const SDP_SEMANTICS_UNIFIED_PLAN_WITH_FALLBACK: &str = "unified-plan-with-fallback";
34const SDP_SEMANTICS_UNIFIED_PLAN: &str = "unified-plan";
35const SDP_SEMANTICS_PLAN_B: &str = "plan-b";
36
37impl From<&str> for RTCSdpSemantics {
38    fn from(raw: &str) -> Self {
39        match raw {
40            SDP_SEMANTICS_UNIFIED_PLAN_WITH_FALLBACK => RTCSdpSemantics::UnifiedPlanWithFallback,
41            SDP_SEMANTICS_UNIFIED_PLAN => RTCSdpSemantics::UnifiedPlan,
42            SDP_SEMANTICS_PLAN_B => RTCSdpSemantics::PlanB,
43            _ => RTCSdpSemantics::Unspecified,
44        }
45    }
46}
47
48impl fmt::Display for RTCSdpSemantics {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        let s = match *self {
51            RTCSdpSemantics::UnifiedPlanWithFallback => SDP_SEMANTICS_UNIFIED_PLAN_WITH_FALLBACK,
52            RTCSdpSemantics::UnifiedPlan => SDP_SEMANTICS_UNIFIED_PLAN,
53            RTCSdpSemantics::PlanB => SDP_SEMANTICS_PLAN_B,
54            RTCSdpSemantics::Unspecified => crate::UNSPECIFIED_STR,
55        };
56        write!(f, "{s}")
57    }
58}
59
60#[cfg(test)]
61mod test {
62    use std::collections::HashSet;
63
64    use sdp::description::media::MediaDescription;
65    use sdp::description::session::{SessionDescription, ATTR_KEY_SSRC};
66
67    use super::*;
68
69    #[test]
70    fn test_sdp_semantics_string() {
71        let tests = vec![
72            (RTCSdpSemantics::Unspecified, "Unspecified"),
73            (
74                RTCSdpSemantics::UnifiedPlanWithFallback,
75                "unified-plan-with-fallback",
76            ),
77            (RTCSdpSemantics::PlanB, "plan-b"),
78            (RTCSdpSemantics::UnifiedPlan, "unified-plan"),
79        ];
80
81        for (value, expected_string) in tests {
82            assert_eq!(value.to_string(), expected_string);
83        }
84    }
85
86    // The following tests are for non-standard SDP semantics
87    // (i.e. not unified-unified)
88    fn get_md_names(sdp: &SessionDescription) -> Vec<String> {
89        sdp.media_descriptions
90            .iter()
91            .map(|md| md.media_name.media.clone())
92            .collect()
93    }
94
95    fn extract_ssrc_list(md: &MediaDescription) -> Vec<String> {
96        let mut ssrcs = HashSet::new();
97        for attr in &md.attributes {
98            if attr.key == ATTR_KEY_SSRC {
99                if let Some(value) = &attr.value {
100                    let fields: Vec<&str> = value.split_whitespace().collect();
101                    if let Some(ssrc) = fields.first() {
102                        ssrcs.insert(*ssrc);
103                    }
104                }
105            }
106        }
107        ssrcs
108            .into_iter()
109            .map(|ssrc| ssrc.to_owned())
110            .collect::<Vec<String>>()
111    }
112}