1use super::*;
6
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum RtpInfos {
13 V1(Vec<v1::RtpInfo>),
14 V2(Vec<v2::RtpInfo>),
15}
16
17impl RtpInfos {
18 pub fn try_into_v1(self) -> Result<Self, Self> {
22 match self {
23 RtpInfos::V1(v1) => Ok(RtpInfos::V1(v1)),
24 RtpInfos::V2(v2) => {
25 if v2.iter().any(|info| info.ssrc_infos.len() != 1) {
26 return Err(RtpInfos::V2(v2));
27 }
28
29 let infos = v2
30 .into_iter()
31 .map(|info| v1::RtpInfo {
32 uri: info.uri,
33 seq: info.ssrc_infos[0].seq,
34 rtptime: info.ssrc_infos[0].rtptime,
35 })
36 .collect();
37
38 Ok(RtpInfos::V1(infos))
39 }
40 }
41 }
42}
43
44pub mod v1 {
45 use super::*;
46
47 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50 pub struct RtpInfo {
51 pub uri: url::Url,
53 pub seq: Option<u16>,
55 pub rtptime: Option<u32>,
57 }
58
59 pub(super) mod parser {
60 use super::*;
61
62 use super::parser_helpers::trim;
63 use crate::nom_extensions::separated_list1_fold;
64 use nom::bytes::complete::{tag, take_while};
65 use nom::combinator::{all_consuming, map_parser, map_res};
66 use nom::multi::separated_list1;
67 use nom::sequence::separated_pair;
68 use nom::IResult;
69 use std::str;
70
71 fn param(input: &[u8]) -> IResult<&[u8], (&str, &str)> {
72 separated_pair(
73 trim(map_res(take_while(|b| b != b'='), str::from_utf8)),
74 tag("="),
75 trim(map_res(take_while(|b| b != b';'), str::from_utf8)),
76 )(input)
77 }
78
79 fn rtp_info(input: &[u8]) -> IResult<&[u8], RtpInfo> {
80 #[derive(Clone, Default)]
81 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82 struct Info<'a> {
83 uri: Option<&'a str>,
84 seq: Option<&'a str>,
85 rtptime: Option<&'a str>,
86 }
87
88 map_res(
89 separated_list1_fold(tag(b";"), param, Info::default(), |mut acc, param| {
90 match param.0 {
91 "url" => acc.uri = Some(param.1),
92 "seq" => acc.seq = Some(param.1),
93 "rtptime" => acc.rtptime = Some(param.1),
94 _ => (),
95 }
96
97 acc
98 }),
99 |info| -> Result<_, HeaderParseError> {
100 let uri = info
101 .uri
102 .and_then(|uri| url::Url::parse(uri).ok())
103 .ok_or(HeaderParseError)?;
104 let seq = info
105 .seq
106 .map(|s| s.parse::<u16>())
107 .transpose()
108 .map_err(|_| HeaderParseError)?;
109
110 let rtptime = info
111 .rtptime
112 .map(|s| s.parse::<u32>())
113 .transpose()
114 .map_err(|_| HeaderParseError)?;
115
116 Ok(RtpInfo { uri, seq, rtptime })
117 },
118 )(input)
119 }
120
121 pub(crate) fn rtp_infos(input: &[u8]) -> IResult<&[u8], Vec<RtpInfo>> {
122 all_consuming(separated_list1(
123 tag(","),
124 map_parser(take_while(|b| b != b','), rtp_info),
125 ))(input)
126 }
127 }
128}
129
130pub mod v2 {
131 use super::*;
132
133 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
135 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136 pub struct RtpInfo {
137 pub uri: url::Url,
139 pub ssrc_infos: Vec<SsrcInfo>,
141 }
142
143 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
145 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146 pub struct SsrcInfo {
147 pub ssrc: u32,
149 pub seq: Option<u16>,
151 pub rtptime: Option<u32>,
153 pub others: BTreeMap<String, Option<String>>,
155 }
156
157 pub(super) mod parser {
158 use super::*;
159
160 use super::parser_helpers::{cond_parser, quoted_string, token, trim};
161 use crate::nom_extensions::separated_list1_fold;
162 use nom::branch::alt;
163 use nom::bytes::complete::{tag, take, take_while};
164 use nom::combinator::{all_consuming, map, map_res};
165 use nom::multi::{many1, separated_list1};
166 use nom::sequence::tuple;
167 use nom::{Err, IResult};
168 use std::str;
169
170 fn param(input: &[u8]) -> IResult<&[u8], (&str, Option<&str>)> {
171 if input.is_empty() {
172 return Err(Err::Error(nom::error::Error::new(
173 input,
174 nom::error::ErrorKind::Eof,
175 )));
176 }
177
178 tuple((
179 trim(map_res(token, str::from_utf8)),
180 cond_parser(
181 tag(b"="),
182 trim(map_res(alt((quoted_string, token)), str::from_utf8)),
183 ),
184 ))(input)
185 }
186
187 fn ssrc_info(input: &[u8]) -> IResult<&[u8], SsrcInfo> {
188 map_res(
189 tuple((
190 trim(tag(b"ssrc")),
191 trim(tag(b"=")),
192 map_res(map_res(take(8usize), str::from_utf8), |s| {
193 u32::from_str_radix(s, 16)
194 }),
195 cond_parser(
196 trim(tag(b":")),
197 separated_list1_fold(
198 tag(b";"),
199 param,
200 BTreeMap::new(),
201 |mut acc, param| {
202 acc.insert(String::from(param.0), param.1.map(String::from));
203
204 acc
205 },
206 ),
207 ),
208 )),
209 |(_, _, ssrc, params)| -> Result<_, HeaderParseError> {
210 let mut params = params.unwrap_or_default();
211
212 let seq = if let Some((_, Some(seq))) = params.remove_entry("seq") {
213 Some(seq.parse::<u16>().map_err(|_| HeaderParseError)?)
214 } else {
215 None
216 };
217
218 let rtptime = if let Some((_, Some(rtptime))) = params.remove_entry("rtptime") {
219 Some(rtptime.parse::<u32>().map_err(|_| HeaderParseError)?)
220 } else {
221 None
222 };
223
224 Ok(SsrcInfo {
225 ssrc,
226 seq,
227 rtptime,
228 others: params,
229 })
230 },
231 )(input)
232 }
233
234 fn rtp_info(input: &[u8]) -> IResult<&[u8], RtpInfo> {
235 map(
236 tuple((
237 trim(tag(b"url")),
238 trim(tag(b"=")),
239 trim(tag(b"\"")),
240 trim(map_res(
241 map_res(take_while(|b| b != b'"'), str::from_utf8),
242 url::Url::parse,
243 )),
244 trim(tag(b"\"")),
245 many1(trim(ssrc_info)),
246 )),
247 |(_, _, _, uri, _, ssrc_infos)| RtpInfo { uri, ssrc_infos },
248 )(input)
249 }
250
251 pub(crate) fn rtp_infos(input: &[u8]) -> IResult<&[u8], Vec<RtpInfo>> {
252 all_consuming(separated_list1(tag(","), rtp_info))(input)
253 }
254 }
255}
256
257mod parser {
258 use super::*;
259
260 use nom::IResult;
261
262 pub(super) fn rtp_infos(input: &[u8]) -> IResult<&[u8], RtpInfos> {
263 fn is_v2_rtpinfo(mut i: &[u8]) -> bool {
264 while i.starts_with(b" ") || i.starts_with(b"\t") {
265 i = &i[1..];
266 }
267
268 if !i.starts_with(b"url") {
269 return false;
270 }
271 i = &i[3..];
272
273 while i.starts_with(b" ") || i.starts_with(b"\t") {
274 i = &i[1..];
275 }
276
277 if !i.starts_with(b"=") {
278 return false;
279 }
280 i = &i[1..];
281
282 while i.starts_with(b" ") || i.starts_with(b"\t") {
283 i = &i[1..];
284 }
285
286 i.starts_with(b"\"")
287 }
288
289 if is_v2_rtpinfo(input) {
290 let (rem, infos) = v2::parser::rtp_infos(input)?;
291 Ok((rem, RtpInfos::V2(infos)))
292 } else {
293 let (rem, infos) = v1::parser::rtp_infos(input)?;
294 Ok((rem, RtpInfos::V1(infos)))
295 }
296 }
297}
298
299impl super::TypedHeader for RtpInfos {
300 fn from_headers(headers: impl AsRef<Headers>) -> Result<Option<Self>, HeaderParseError> {
301 let headers = headers.as_ref();
302
303 let header = match headers.get(&RTP_INFO) {
304 None => return Ok(None),
305 Some(header) => header,
306 };
307
308 let (_rem, rtp_info) =
309 parser::rtp_infos(header.as_str().as_bytes()).map_err(|_| HeaderParseError)?;
310
311 Ok(Some(rtp_info))
312 }
313
314 fn insert_into(&self, mut headers: impl AsMut<Headers>) {
315 use std::fmt::Write;
316
317 let headers = headers.as_mut();
318
319 let mut infos = String::new();
320
321 match self {
322 RtpInfos::V1(v1) => {
323 for info in v1 {
324 if !infos.is_empty() {
325 infos.push(',');
326 }
327
328 write!(&mut infos, "url={}", info.uri).unwrap();
329
330 if let Some(seq) = info.seq {
331 write!(&mut infos, ";seq={seq}").unwrap();
332 }
333
334 if let Some(rtptime) = info.rtptime {
335 write!(&mut infos, ";rtptime={rtptime}").unwrap();
336 }
337 }
338 }
339 RtpInfos::V2(v2) => {
340 for info in v2 {
341 if info.ssrc_infos.is_empty() {
342 continue;
343 }
344
345 if !infos.is_empty() {
346 infos.push(',');
347 }
348
349 write!(&mut infos, "url=\"{}\"", info.uri).unwrap();
350 for ssrc in &info.ssrc_infos {
351 write!(&mut infos, " ssrc={:08X}", ssrc.ssrc).unwrap();
352 if ssrc.seq.is_none() && ssrc.rtptime.is_none() && ssrc.others.is_empty() {
353 continue;
354 }
355 infos.push(':');
356
357 let mut need_semi = false;
358
359 if let Some(seq) = ssrc.seq {
360 write!(&mut infos, "seq={seq}").unwrap();
361 need_semi = true;
362 }
363
364 if let Some(rtptime) = ssrc.rtptime {
365 if need_semi {
366 infos.push(';');
367 }
368 write!(&mut infos, "rtptime={rtptime}").unwrap();
369 need_semi = true;
370 }
371
372 for (name, value) in &ssrc.others {
373 if need_semi {
374 infos.push(';');
375 }
376 if let Some(value) = value {
377 write!(&mut infos, "{name}={value}").unwrap();
378 } else {
379 write!(&mut infos, "{name}").unwrap();
380 }
381 need_semi = true;
382 }
383 }
384 }
385 }
386 }
387
388 headers.insert(RTP_INFO, infos);
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_info() {
398 let header =
399 "url=\"rtsp://example.com/foo/audio\" ssrc=0A13C760:seq=45102;rtptime=12345678";
400 let response = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
401 .header(crate::headers::RTP_INFO, header)
402 .empty();
403
404 let infos = response.typed_header::<super::RtpInfos>().unwrap().unwrap();
405
406 assert_eq!(
407 infos,
408 RtpInfos::V2(vec![v2::RtpInfo {
409 uri: url::Url::parse("rtsp://example.com/foo/audio").unwrap(),
410 ssrc_infos: vec![v2::SsrcInfo {
411 ssrc: 0x0A13C760,
412 seq: Some(45102),
413 rtptime: Some(12345678),
414 others: BTreeMap::new()
415 }],
416 }])
417 );
418
419 let response2 = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
420 .typed_header(&infos)
421 .empty();
422
423 assert_eq!(response, response2);
424 }
425
426 #[test]
427 fn test_info_multiple_ssrc() {
428 let header =
429 "url=\"rtsp://example.com/foo/audio\" ssrc=0A13C760:seq=45102;rtptime=12345678 ssrc=9A9DE123:seq=30211;rtptime=29567112";
430 let response = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
431 .header(crate::headers::RTP_INFO, header)
432 .empty();
433
434 let infos = response.typed_header::<super::RtpInfos>().unwrap().unwrap();
435
436 assert_eq!(
437 infos,
438 RtpInfos::V2(vec![v2::RtpInfo {
439 uri: url::Url::parse("rtsp://example.com/foo/audio").unwrap(),
440 ssrc_infos: vec![
441 v2::SsrcInfo {
442 ssrc: 0x0A13C760,
443 seq: Some(45102),
444 rtptime: Some(12345678),
445 others: BTreeMap::new()
446 },
447 v2::SsrcInfo {
448 ssrc: 0x9A9DE123,
449 seq: Some(30211),
450 rtptime: Some(29567112),
451 others: BTreeMap::new()
452 }
453 ],
454 }])
455 );
456
457 let response2 = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
458 .typed_header(&infos)
459 .empty();
460
461 assert_eq!(response, response2);
462 }
463
464 #[test]
465 fn test_multiple_infos() {
466 let header = "url=\"rtsp://example.com/foo/audio\" ssrc=0A13C760:seq=45102;rtptime=12345678,url=\"rtsp://example.com/foo/video\" ssrc=9A9DE123:seq=30211;rtptime=29567112";
467 let response = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
468 .header(crate::headers::RTP_INFO, header)
469 .empty();
470
471 let infos = response.typed_header::<super::RtpInfos>().unwrap().unwrap();
472
473 assert_eq!(
474 infos,
475 RtpInfos::V2(vec![
476 v2::RtpInfo {
477 uri: url::Url::parse("rtsp://example.com/foo/audio").unwrap(),
478 ssrc_infos: vec![v2::SsrcInfo {
479 ssrc: 0x0A13C760,
480 seq: Some(45102),
481 rtptime: Some(12345678),
482 others: BTreeMap::new()
483 }],
484 },
485 v2::RtpInfo {
486 uri: url::Url::parse("rtsp://example.com/foo/video").unwrap(),
487 ssrc_infos: vec![v2::SsrcInfo {
488 ssrc: 0x9A9DE123,
489 seq: Some(30211),
490 rtptime: Some(29567112),
491 others: BTreeMap::new()
492 }],
493 }
494 ])
495 );
496
497 let response2 = crate::Response::builder(crate::Version::V2_0, crate::StatusCode::Ok)
498 .typed_header(&infos)
499 .empty();
500
501 assert_eq!(response, response2);
502 }
503
504 #[test]
505 fn test_info_v1() {
506 let header = "url=rtsp://example.com/foo/audio;seq=45102;rtptime=12345678";
507 let response = crate::Response::builder(crate::Version::V1_0, crate::StatusCode::Ok)
508 .header(crate::headers::RTP_INFO, header)
509 .empty();
510
511 let infos = response.typed_header::<super::RtpInfos>().unwrap().unwrap();
512
513 assert_eq!(
514 infos,
515 RtpInfos::V1(vec![v1::RtpInfo {
516 uri: url::Url::parse("rtsp://example.com/foo/audio").unwrap(),
517 seq: Some(45102),
518 rtptime: Some(12345678),
519 }])
520 );
521
522 let response2 = crate::Response::builder(crate::Version::V1_0, crate::StatusCode::Ok)
523 .typed_header(&infos)
524 .empty();
525
526 assert_eq!(response, response2);
527 }
528
529 #[test]
530 fn test_multiple_infos_v1() {
531 let header = "url=rtsp://example.com/foo/audio;seq=45102;rtptime=12345678,url=rtsp://example.com/foo/video;seq=30211;rtptime=29567112";
532 let response = crate::Response::builder(crate::Version::V1_0, crate::StatusCode::Ok)
533 .header(crate::headers::RTP_INFO, header)
534 .empty();
535
536 let infos = response.typed_header::<super::RtpInfos>().unwrap().unwrap();
537
538 assert_eq!(
539 infos,
540 RtpInfos::V1(vec![
541 v1::RtpInfo {
542 uri: url::Url::parse("rtsp://example.com/foo/audio").unwrap(),
543 seq: Some(45102),
544 rtptime: Some(12345678),
545 },
546 v1::RtpInfo {
547 uri: url::Url::parse("rtsp://example.com/foo/video").unwrap(),
548 seq: Some(30211),
549 rtptime: Some(29567112),
550 }
551 ])
552 );
553
554 let response2 = crate::Response::builder(crate::Version::V1_0, crate::StatusCode::Ok)
555 .typed_header(&infos)
556 .empty();
557
558 assert_eq!(response, response2);
559 }
560}