rtsp_types/headers/
rtp_info.rs

1// Copyright (C) 2021 Sebastian Dröge <sebastian@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5use super::*;
6
7use std::collections::BTreeMap;
8
9/// `RTP-Info` header ([RFC 7826 section 18.45](https://tools.ietf.org/html/rfc7826#section-18.45)).
10#[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    /// Try converting into a RTSP 1.0 RTP-Info header.
19    ///
20    /// Note that this potentially loses extra information that can't be represented.
21    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    /// RTP-Info.
48    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50    pub struct RtpInfo {
51        /// Stream URI.
52        pub uri: url::Url,
53        /// Sequence number of the first packet that is a direct result of the request.
54        pub seq: Option<u16>,
55        /// RTP timestamp corresponding to the start time in the `Range` header.
56        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    /// RTP-Info.
134    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
135    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136    pub struct RtpInfo {
137        /// Stream URI.
138        pub uri: url::Url,
139        /// SSRC information.
140        pub ssrc_infos: Vec<SsrcInfo>,
141    }
142
143    /// SSRC Information.
144    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
145    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146    pub struct SsrcInfo {
147        /// SSRC of this stream.
148        pub ssrc: u32,
149        /// Sequence number of the first packet that is a direct result of the request.
150        pub seq: Option<u16>,
151        /// RTP timestamp corresponding to the start time in the `Range` header.
152        pub rtptime: Option<u32>,
153        /// Other parameters.
154        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}