rtc_sdp/util/
mod.rs

1#[cfg(test)]
2mod util_test;
3
4use shared::error::{Error, Result};
5
6use std::collections::HashMap;
7use std::fmt;
8
9pub const ATTRIBUTE_KEY: &str = "a=";
10
11/// ConnectionRole indicates which of the end points should initiate the connection establishment
12#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
13pub enum ConnectionRole {
14    #[default]
15    Unspecified,
16
17    /// ConnectionRoleActive indicates the endpoint will initiate an outgoing connection.
18    Active,
19
20    /// ConnectionRolePassive indicates the endpoint will accept an incoming connection.
21    Passive,
22
23    /// ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or to initiate an outgoing connection.
24    Actpass,
25
26    /// ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being.
27    Holdconn,
28}
29
30const CONNECTION_ROLE_ACTIVE_STR: &str = "active";
31const CONNECTION_ROLE_PASSIVE_STR: &str = "passive";
32const CONNECTION_ROLE_ACTPASS_STR: &str = "actpass";
33const CONNECTION_ROLE_HOLDCONN_STR: &str = "holdconn";
34
35impl fmt::Display for ConnectionRole {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        let s = match self {
38            ConnectionRole::Active => CONNECTION_ROLE_ACTIVE_STR,
39            ConnectionRole::Passive => CONNECTION_ROLE_PASSIVE_STR,
40            ConnectionRole::Actpass => CONNECTION_ROLE_ACTPASS_STR,
41            ConnectionRole::Holdconn => CONNECTION_ROLE_HOLDCONN_STR,
42            _ => "Unspecified",
43        };
44        write!(f, "{s}")
45    }
46}
47
48impl From<u8> for ConnectionRole {
49    fn from(v: u8) -> Self {
50        match v {
51            1 => ConnectionRole::Active,
52            2 => ConnectionRole::Passive,
53            3 => ConnectionRole::Actpass,
54            4 => ConnectionRole::Holdconn,
55            _ => ConnectionRole::Unspecified,
56        }
57    }
58}
59
60impl From<&str> for ConnectionRole {
61    fn from(raw: &str) -> Self {
62        match raw {
63            CONNECTION_ROLE_ACTIVE_STR => ConnectionRole::Active,
64            CONNECTION_ROLE_PASSIVE_STR => ConnectionRole::Passive,
65            CONNECTION_ROLE_ACTPASS_STR => ConnectionRole::Actpass,
66            CONNECTION_ROLE_HOLDCONN_STR => ConnectionRole::Holdconn,
67            _ => ConnectionRole::Unspecified,
68        }
69    }
70}
71
72/// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26#section-5.2.1
73/// Session ID is recommended to be constructed by generating a 64-bit
74/// quantity with the highest bit set to zero and the remaining 63-bits
75/// being cryptographically random.
76pub(crate) fn new_session_id() -> u64 {
77    let c = u64::MAX ^ (1u64 << 63);
78    rand::random::<u64>() & c
79}
80
81// Codec represents a codec
82#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub struct Codec {
84    pub payload_type: u8,
85    pub name: String,
86    pub clock_rate: u32,
87    pub encoding_parameters: String,
88    pub fmtp: String,
89    pub rtcp_feedback: Vec<String>,
90}
91
92impl fmt::Display for Codec {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(
95            f,
96            "{} {}/{}/{} ({}) [",
97            self.payload_type, self.name, self.clock_rate, self.encoding_parameters, self.fmtp,
98        )?;
99
100        let mut first = true;
101        for part in &self.rtcp_feedback {
102            if first {
103                first = false;
104                write!(f, "{part}")?;
105            } else {
106                write!(f, ", {part}")?;
107            }
108        }
109
110        write!(f, "]")
111    }
112}
113
114pub(crate) fn parse_rtpmap(rtpmap: &str) -> Result<Codec> {
115    // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>]
116    let split: Vec<&str> = rtpmap.split_whitespace().collect();
117    if split.len() != 2 {
118        return Err(Error::MissingWhitespace);
119    }
120
121    let pt_split: Vec<&str> = split[0].split(':').collect();
122    if pt_split.len() != 2 {
123        return Err(Error::MissingColon);
124    }
125    let payload_type = pt_split[1].parse::<u8>()?;
126
127    let split: Vec<&str> = split[1].split('/').collect();
128    let name = split[0].to_string();
129    let parts = split.len();
130    let clock_rate = if parts > 1 {
131        split[1].parse::<u32>()?
132    } else {
133        0
134    };
135    let encoding_parameters = if parts > 2 {
136        split[2].to_string()
137    } else {
138        "".to_string()
139    };
140
141    Ok(Codec {
142        payload_type,
143        name,
144        clock_rate,
145        encoding_parameters,
146        ..Default::default()
147    })
148}
149
150pub(crate) fn parse_fmtp(fmtp: &str) -> Result<Codec> {
151    // a=fmtp:<format> <format specific parameters>
152    let split: Vec<&str> = fmtp.split_whitespace().collect();
153    if split.len() != 2 {
154        return Err(Error::MissingWhitespace);
155    }
156
157    let fmtp = split[1].to_string();
158
159    let split: Vec<&str> = split[0].split(':').collect();
160    if split.len() != 2 {
161        return Err(Error::MissingColon);
162    }
163    let payload_type = split[1].parse::<u8>()?;
164
165    Ok(Codec {
166        payload_type,
167        fmtp,
168        ..Default::default()
169    })
170}
171
172pub(crate) fn parse_rtcp_fb(rtcp_fb: &str) -> Result<Codec> {
173    // a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>]
174    let split: Vec<&str> = rtcp_fb.splitn(2, ' ').collect();
175    if split.len() != 2 {
176        return Err(Error::MissingWhitespace);
177    }
178
179    let pt_split: Vec<&str> = split[0].split(':').collect();
180    if pt_split.len() != 2 {
181        return Err(Error::MissingColon);
182    }
183
184    Ok(Codec {
185        payload_type: pt_split[1].parse::<u8>()?,
186        rtcp_feedback: vec![split[1].to_string()],
187        ..Default::default()
188    })
189}
190
191pub(crate) fn merge_codecs(mut codec: Codec, codecs: &mut HashMap<u8, Codec>) {
192    if let Some(saved_codec) = codecs.get_mut(&codec.payload_type) {
193        if saved_codec.payload_type == 0 {
194            saved_codec.payload_type = codec.payload_type
195        }
196        if saved_codec.name.is_empty() {
197            saved_codec.name = codec.name
198        }
199        if saved_codec.clock_rate == 0 {
200            saved_codec.clock_rate = codec.clock_rate
201        }
202        if saved_codec.encoding_parameters.is_empty() {
203            saved_codec.encoding_parameters = codec.encoding_parameters
204        }
205        if saved_codec.fmtp.is_empty() {
206            saved_codec.fmtp = codec.fmtp
207        }
208        saved_codec.rtcp_feedback.append(&mut codec.rtcp_feedback);
209    } else {
210        codecs.insert(codec.payload_type, codec);
211    }
212}
213
214fn equivalent_fmtp(want: &str, got: &str) -> bool {
215    let mut want_split: Vec<&str> = want.split(';').collect();
216    let mut got_split: Vec<&str> = got.split(';').collect();
217
218    if want_split.len() != got_split.len() {
219        return false;
220    }
221
222    want_split.sort_unstable();
223    got_split.sort_unstable();
224
225    for (i, &want_part) in want_split.iter().enumerate() {
226        let want_part = want_part.trim();
227        let got_part = got_split[i].trim();
228        if got_part != want_part {
229            return false;
230        }
231    }
232
233    true
234}
235
236pub(crate) fn codecs_match(wanted: &Codec, got: &Codec) -> bool {
237    if !wanted.name.is_empty() && wanted.name.to_lowercase() != got.name.to_lowercase() {
238        return false;
239    }
240    if wanted.clock_rate != 0 && wanted.clock_rate != got.clock_rate {
241        return false;
242    }
243    if !wanted.encoding_parameters.is_empty()
244        && wanted.encoding_parameters != got.encoding_parameters
245    {
246        return false;
247    }
248    if !wanted.fmtp.is_empty() && !equivalent_fmtp(&wanted.fmtp, &got.fmtp) {
249        return false;
250    }
251
252    true
253}