1use crate::error::{Error, Result};
16
17const PROTO_RTP: &str = "RTP";
19const PROFILE_AVP: &str = "AVP";
21
22#[non_exhaustive]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub enum LowerTransport {
29 Udp,
31 Tcp,
33}
34
35impl LowerTransport {
36 pub fn name(&self) -> &'static str {
38 match self {
39 LowerTransport::Udp => "UDP",
40 LowerTransport::Tcp => "TCP",
41 }
42 }
43}
44
45impl core::fmt::Display for LowerTransport {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 f.write_str(self.name())
48 }
49}
50
51#[non_exhaustive]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub enum Delivery {
56 Unicast,
58 Multicast,
60}
61
62impl Delivery {
63 pub fn name(&self) -> &'static str {
65 match self {
66 Delivery::Unicast => "unicast",
67 Delivery::Multicast => "multicast",
68 }
69 }
70}
71
72impl core::fmt::Display for Delivery {
73 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 f.write_str(self.name())
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Default)]
84#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
85pub struct TransportSpec {
86 pub lower_transport: Option<LowerTransport>,
88 pub delivery: Option<Delivery>,
90 pub interleaved: Option<(u8, u8)>,
92 pub client_port: Option<(u16, u16)>,
94 pub server_port: Option<(u16, u16)>,
96 pub port: Option<(u16, u16)>,
98 pub ttl: Option<u8>,
100 pub layers: Option<u32>,
102 pub ssrc: Option<u32>,
104 pub destination: Option<String>,
106 pub source: Option<String>,
108 pub mode: Option<String>,
110 pub append: bool,
112}
113
114impl TransportSpec {
115 pub fn rtp_avp_tcp_interleaved(lo: u8, hi: u8) -> Self {
118 TransportSpec {
119 lower_transport: Some(LowerTransport::Tcp),
120 delivery: Some(Delivery::Unicast),
121 interleaved: Some((lo, hi)),
122 ..Default::default()
123 }
124 }
125
126 fn parse_spec(spec: &str) -> Result<Self> {
128 let mut parts = spec.split(';');
129 let head = parts
130 .next()
131 .ok_or_else(|| Error::TransportParse("empty transport-spec".into()))?
132 .trim();
133
134 let mut triple = head.split('/');
136 let proto = triple.next().unwrap_or("").trim();
137 if !proto.eq_ignore_ascii_case(PROTO_RTP) {
138 return Err(Error::TransportParse(format!(
139 "unsupported transport protocol {proto:?} (only RTP)"
140 )));
141 }
142 let profile = triple
143 .next()
144 .ok_or_else(|| Error::TransportParse("missing profile".into()))?
145 .trim();
146 if !profile.eq_ignore_ascii_case(PROFILE_AVP) {
147 return Err(Error::TransportParse(format!(
148 "unsupported profile {profile:?} (only AVP)"
149 )));
150 }
151 let lower_transport = match triple.next() {
152 None => None,
153 Some(t) => match t.trim() {
154 s if s.eq_ignore_ascii_case("TCP") => Some(LowerTransport::Tcp),
155 s if s.eq_ignore_ascii_case("UDP") => Some(LowerTransport::Udp),
156 other => {
157 return Err(Error::TransportParse(format!(
158 "unknown lower-transport {other:?}"
159 )));
160 }
161 },
162 };
163
164 let mut out = TransportSpec {
165 lower_transport,
166 ..Default::default()
167 };
168
169 for raw in parts {
170 let param = raw.trim();
171 if param.is_empty() {
172 continue;
173 }
174 let (key, value) = match param.split_once('=') {
175 Some((k, v)) => (k.trim(), Some(v.trim())),
176 None => (param, None),
177 };
178 match key.to_ascii_lowercase().as_str() {
179 "unicast" => out.delivery = Some(Delivery::Unicast),
180 "multicast" => out.delivery = Some(Delivery::Multicast),
181 "append" => out.append = true,
182 "interleaved" => {
183 out.interleaved = Some(parse_u8_range(value, "interleaved")?);
184 }
185 "client_port" => out.client_port = Some(parse_u16_range(value, "client_port")?),
186 "server_port" => out.server_port = Some(parse_u16_range(value, "server_port")?),
187 "port" => out.port = Some(parse_u16_range(value, "port")?),
188 "ttl" => out.ttl = Some(parse_scalar(value, "ttl")?),
189 "layers" => out.layers = Some(parse_scalar(value, "layers")?),
190 "ssrc" => {
191 let v = value
192 .ok_or_else(|| Error::TransportParse("ssrc requires a value".into()))?;
193 out.ssrc = Some(
194 u32::from_str_radix(v.trim_matches('"'), 16)
195 .map_err(|e| Error::TransportParse(format!("bad ssrc {v:?}: {e}")))?,
196 );
197 }
198 "destination" => out.destination = value.map(|s| s.trim_matches('"').to_string()),
199 "source" => out.source = value.map(|s| s.trim_matches('"').to_string()),
200 "mode" => {
201 out.mode = value.map(|s| s.trim_matches('"').to_string());
202 }
203 _ => {}
205 }
206 }
207 Ok(out)
208 }
209
210 pub fn to_header_value(&self) -> String {
212 let mut s = String::new();
213 s.push_str(PROTO_RTP);
214 s.push('/');
215 s.push_str(PROFILE_AVP);
216 if let Some(lt) = self.lower_transport {
217 s.push('/');
218 s.push_str(lt.name());
219 }
220 if let Some(d) = self.delivery {
221 s.push(';');
222 s.push_str(d.name());
223 }
224 if let Some(dest) = &self.destination {
225 s.push_str(";destination=");
226 s.push_str(dest);
227 }
228 if let Some(src) = &self.source {
229 s.push_str(";source=");
230 s.push_str(src);
231 }
232 if let Some((lo, hi)) = self.interleaved {
233 s.push_str(&format!(";interleaved={lo}-{hi}"));
234 }
235 if let Some(ttl) = self.ttl {
236 s.push_str(&format!(";ttl={ttl}"));
237 }
238 if let Some(layers) = self.layers {
239 s.push_str(&format!(";layers={layers}"));
240 }
241 if let Some((lo, hi)) = self.port {
242 s.push_str(&format!(";port={lo}-{hi}"));
243 }
244 if let Some((lo, hi)) = self.client_port {
245 s.push_str(&format!(";client_port={lo}-{hi}"));
246 }
247 if let Some((lo, hi)) = self.server_port {
248 s.push_str(&format!(";server_port={lo}-{hi}"));
249 }
250 if let Some(ssrc) = self.ssrc {
251 s.push_str(&format!(";ssrc={ssrc:08X}"));
252 }
253 if let Some(mode) = &self.mode {
254 s.push_str(&format!(";mode=\"{mode}\""));
255 }
256 if self.append {
257 s.push_str(";append");
258 }
259 s
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Default)]
266#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
267pub struct Transport {
268 pub specs: Vec<TransportSpec>,
270}
271
272impl Transport {
273 pub fn single(spec: TransportSpec) -> Self {
275 Transport { specs: vec![spec] }
276 }
277
278 pub fn parse(value: &str) -> Result<Self> {
280 let specs = value
281 .split(',')
282 .map(str::trim)
283 .filter(|s| !s.is_empty())
284 .map(TransportSpec::parse_spec)
285 .collect::<Result<Vec<_>>>()?;
286 if specs.is_empty() {
287 return Err(Error::TransportParse("no transport-specs".into()));
288 }
289 Ok(Transport { specs })
290 }
291
292 pub fn to_header_value(&self) -> String {
294 self.specs
295 .iter()
296 .map(TransportSpec::to_header_value)
297 .collect::<Vec<_>>()
298 .join(",")
299 }
300
301 pub fn first(&self) -> Option<&TransportSpec> {
303 self.specs.first()
304 }
305}
306
307fn parse_scalar<T: core::str::FromStr>(value: Option<&str>, what: &str) -> Result<T>
308where
309 T::Err: core::fmt::Display,
310{
311 let v = value.ok_or_else(|| Error::TransportParse(format!("{what} requires a value")))?;
312 v.trim()
313 .parse::<T>()
314 .map_err(|e| Error::TransportParse(format!("bad {what} {v:?}: {e}")))
315}
316
317fn parse_u8_range(value: Option<&str>, what: &str) -> Result<(u8, u8)> {
318 let (lo, hi) = split_range(value, what)?;
319 let lo: u8 = lo
320 .parse()
321 .map_err(|e| Error::TransportParse(format!("bad {what} low {lo:?}: {e}")))?;
322 let hi: u8 = match hi {
323 Some(h) => h
324 .parse()
325 .map_err(|e| Error::TransportParse(format!("bad {what} high {h:?}: {e}")))?,
326 None => lo,
327 };
328 Ok((lo, hi))
329}
330
331fn parse_u16_range(value: Option<&str>, what: &str) -> Result<(u16, u16)> {
332 let (lo, hi) = split_range(value, what)?;
333 let lo: u16 = lo
334 .parse()
335 .map_err(|e| Error::TransportParse(format!("bad {what} low {lo:?}: {e}")))?;
336 let hi: u16 = match hi {
337 Some(h) => h
338 .parse()
339 .map_err(|e| Error::TransportParse(format!("bad {what} high {h:?}: {e}")))?,
340 None => lo,
341 };
342 Ok((lo, hi))
343}
344
345fn split_range<'a>(value: Option<&'a str>, what: &str) -> Result<(&'a str, Option<&'a str>)> {
346 let v = value
347 .ok_or_else(|| Error::TransportParse(format!("{what} requires a value")))?
348 .trim();
349 match v.split_once('-') {
350 Some((lo, hi)) => Ok((lo.trim(), Some(hi.trim()))),
351 None => Ok((v, None)),
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn parse_tcp_interleaved_round_trip() {
361 let t = Transport::parse("RTP/AVP/TCP;interleaved=0-1").unwrap();
362 let spec = t.first().unwrap();
363 assert_eq!(spec.lower_transport, Some(LowerTransport::Tcp));
364 assert_eq!(spec.interleaved, Some((0, 1)));
365 let s = t.to_header_value();
367 let t2 = Transport::parse(&s).unwrap();
368 assert_eq!(t, t2);
369 }
370
371 #[test]
372 fn parse_udp_unicast_client_port_round_trip() {
373 let t = Transport::parse("RTP/AVP;unicast;client_port=8000-8001").unwrap();
374 let spec = t.first().unwrap();
375 assert_eq!(spec.lower_transport, None);
376 assert_eq!(spec.delivery, Some(Delivery::Unicast));
377 assert_eq!(spec.client_port, Some((8000, 8001)));
378 let t2 = Transport::parse(&t.to_header_value()).unwrap();
379 assert_eq!(t, t2);
380 }
381
382 #[test]
383 fn parse_fixture_setup_transport() {
384 let t = Transport::parse("RTP/AVP/TCP;unicast;interleaved=0-1").unwrap();
385 let spec = t.first().unwrap();
386 assert_eq!(spec.delivery, Some(Delivery::Unicast));
387 assert_eq!(spec.interleaved, Some((0, 1)));
388 }
389
390 #[test]
391 fn ssrc_round_trips_as_hex() {
392 let t = Transport::parse("RTP/AVP;unicast;ssrc=DEADBEEF").unwrap();
393 assert_eq!(t.first().unwrap().ssrc, Some(0xDEAD_BEEF));
394 let t2 = Transport::parse(&t.to_header_value()).unwrap();
395 assert_eq!(t, t2);
396 }
397
398 #[test]
399 fn constructed_spec_serializes_fields() {
400 let spec = TransportSpec {
403 interleaved: Some((2, 3)),
404 delivery: Some(Delivery::Unicast),
405 lower_transport: Some(LowerTransport::Tcp),
406 ..Default::default()
407 };
408 let t = Transport::single(spec);
409 let s = t.to_header_value();
410 assert!(s.contains("interleaved=2-3"), "serialized: {s}");
411 assert!(s.contains("unicast"), "serialized: {s}");
412 let back = Transport::parse(&s).unwrap();
414 assert_eq!(back.first().unwrap().interleaved, Some((2, 3)));
415 }
416
417 #[test]
418 fn rejects_non_rtp() {
419 assert!(Transport::parse("XYZ/AVP").is_err());
420 }
421}