1 #[cfg(test)]
2 mod util_test;
3
4 use super::error::{Error, Result};
5
6 use std::collections::HashMap;
7 use std::fmt;
8
9 pub 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)]
13 pub 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
30 const CONNECTION_ROLE_ACTIVE_STR: &str = "active";
31 const CONNECTION_ROLE_PASSIVE_STR: &str = "passive";
32 const CONNECTION_ROLE_ACTPASS_STR: &str = "actpass";
33 const CONNECTION_ROLE_HOLDCONN_STR: &str = "holdconn";
34
35 impl fmt::Display for ConnectionRole {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result36 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
48 impl From<u8> for ConnectionRole {
from(v: u8) -> Self49 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
60 impl From<&str> for ConnectionRole {
from(raw: &str) -> Self61 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.
new_session_id() -> u6476 pub(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)]
83 pub 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
92 impl fmt::Display for Codec {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 write!(
95 f,
96 "{} {}/{}/{} ({}) [{}]",
97 self.payload_type,
98 self.name,
99 self.clock_rate,
100 self.encoding_parameters,
101 self.fmtp,
102 self.rtcp_feedback.join(", "),
103 )
104 }
105 }
106
parse_rtpmap(rtpmap: &str) -> Result<Codec>107 pub(crate) fn parse_rtpmap(rtpmap: &str) -> Result<Codec> {
108 // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>]
109 let split: Vec<&str> = rtpmap.split_whitespace().collect();
110 if split.len() != 2 {
111 return Err(Error::MissingWhitespace);
112 }
113
114 let pt_split: Vec<&str> = split[0].split(':').collect();
115 if pt_split.len() != 2 {
116 return Err(Error::MissingColon);
117 }
118 let payload_type = pt_split[1].parse::<u8>()?;
119
120 let split: Vec<&str> = split[1].split('/').collect();
121 let name = split[0].to_string();
122 let parts = split.len();
123 let clock_rate = if parts > 1 {
124 split[1].parse::<u32>()?
125 } else {
126 0
127 };
128 let encoding_parameters = if parts > 2 {
129 split[2].to_string()
130 } else {
131 "".to_string()
132 };
133
134 Ok(Codec {
135 payload_type,
136 name,
137 clock_rate,
138 encoding_parameters,
139 ..Default::default()
140 })
141 }
142
parse_fmtp(fmtp: &str) -> Result<Codec>143 pub(crate) fn parse_fmtp(fmtp: &str) -> Result<Codec> {
144 // a=fmtp:<format> <format specific parameters>
145 let split: Vec<&str> = fmtp.split_whitespace().collect();
146 if split.len() != 2 {
147 return Err(Error::MissingWhitespace);
148 }
149
150 let fmtp = split[1].to_string();
151
152 let split: Vec<&str> = split[0].split(':').collect();
153 if split.len() != 2 {
154 return Err(Error::MissingColon);
155 }
156 let payload_type = split[1].parse::<u8>()?;
157
158 Ok(Codec {
159 payload_type,
160 fmtp,
161 ..Default::default()
162 })
163 }
164
parse_rtcp_fb(rtcp_fb: &str) -> Result<Codec>165 pub(crate) fn parse_rtcp_fb(rtcp_fb: &str) -> Result<Codec> {
166 // a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>]
167 let split: Vec<&str> = rtcp_fb.splitn(2, ' ').collect();
168 if split.len() != 2 {
169 return Err(Error::MissingWhitespace);
170 }
171
172 let pt_split: Vec<&str> = split[0].split(':').collect();
173 if pt_split.len() != 2 {
174 return Err(Error::MissingColon);
175 }
176
177 Ok(Codec {
178 payload_type: pt_split[1].parse::<u8>()?,
179 rtcp_feedback: vec![split[1].to_string()],
180 ..Default::default()
181 })
182 }
183
merge_codecs(mut codec: Codec, codecs: &mut HashMap<u8, Codec>)184 pub(crate) fn merge_codecs(mut codec: Codec, codecs: &mut HashMap<u8, Codec>) {
185 if let Some(saved_codec) = codecs.get_mut(&codec.payload_type) {
186 if saved_codec.payload_type == 0 {
187 saved_codec.payload_type = codec.payload_type
188 }
189 if saved_codec.name.is_empty() {
190 saved_codec.name = codec.name
191 }
192 if saved_codec.clock_rate == 0 {
193 saved_codec.clock_rate = codec.clock_rate
194 }
195 if saved_codec.encoding_parameters.is_empty() {
196 saved_codec.encoding_parameters = codec.encoding_parameters
197 }
198 if saved_codec.fmtp.is_empty() {
199 saved_codec.fmtp = codec.fmtp
200 }
201 saved_codec.rtcp_feedback.append(&mut codec.rtcp_feedback);
202 } else {
203 codecs.insert(codec.payload_type, codec);
204 }
205 }
206
equivalent_fmtp(want: &str, got: &str) -> bool207 fn equivalent_fmtp(want: &str, got: &str) -> bool {
208 let mut want_split: Vec<&str> = want.split(';').collect();
209 let mut got_split: Vec<&str> = got.split(';').collect();
210
211 if want_split.len() != got_split.len() {
212 return false;
213 }
214
215 want_split.sort_unstable();
216 got_split.sort_unstable();
217
218 for (i, &want_part) in want_split.iter().enumerate() {
219 let want_part = want_part.trim();
220 let got_part = got_split[i].trim();
221 if got_part != want_part {
222 return false;
223 }
224 }
225
226 true
227 }
228
codecs_match(wanted: &Codec, got: &Codec) -> bool229 pub(crate) fn codecs_match(wanted: &Codec, got: &Codec) -> bool {
230 if !wanted.name.is_empty() && wanted.name.to_lowercase() != got.name.to_lowercase() {
231 return false;
232 }
233 if wanted.clock_rate != 0 && wanted.clock_rate != got.clock_rate {
234 return false;
235 }
236 if !wanted.encoding_parameters.is_empty()
237 && wanted.encoding_parameters != got.encoding_parameters
238 {
239 return false;
240 }
241 if !wanted.fmtp.is_empty() && !equivalent_fmtp(&wanted.fmtp, &got.fmtp) {
242 return false;
243 }
244
245 true
246 }
247