Skip to main content

rtc_rtcp/
header.rs

1use shared::{
2    error::{Error, Result},
3    marshal::{Marshal, MarshalSize, Unmarshal},
4};
5
6use bytes::{Buf, BufMut};
7
8/// PacketType specifies the type of an RTCP packet
9/// RTCP packet types registered with IANA. See: <https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4>
10#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
11#[repr(u8)]
12pub enum PacketType {
13    #[default]
14    Unsupported = 0,
15    SenderReport = 200,              // RFC 3550, 6.4.1
16    ReceiverReport = 201,            // RFC 3550, 6.4.2
17    SourceDescription = 202,         // RFC 3550, 6.5
18    Goodbye = 203,                   // RFC 3550, 6.6
19    ApplicationDefined = 204,        // RFC 3550, 6.7 (unimplemented)
20    TransportSpecificFeedback = 205, // RFC 4585, 6051
21    PayloadSpecificFeedback = 206,   // RFC 4585, 6.3
22    ExtendedReport = 207,            // RFC 3611
23}
24
25/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
26pub const FORMAT_SLI: u8 = 2;
27/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
28pub const FORMAT_PLI: u8 = 1;
29/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
30pub const FORMAT_FIR: u8 = 4;
31/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
32pub const FORMAT_TLN: u8 = 1;
33/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
34pub const FORMAT_RRR: u8 = 5;
35/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
36pub const FORMAT_REMB: u8 = 15;
37/// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here.
38/// <https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5>
39pub const FORMAT_TCC: u8 = 15;
40/// FMT value for CCFB (Congestion Control Feedback) per RFC 8888
41pub const FORMAT_CCFB: u8 = 11;
42
43impl std::fmt::Display for PacketType {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let s = match self {
46            PacketType::Unsupported => "Unsupported",
47            PacketType::SenderReport => "SR",
48            PacketType::ReceiverReport => "RR",
49            PacketType::SourceDescription => "SDES",
50            PacketType::Goodbye => "BYE",
51            PacketType::ApplicationDefined => "APP",
52            PacketType::TransportSpecificFeedback => "TSFB",
53            PacketType::PayloadSpecificFeedback => "PSFB",
54            PacketType::ExtendedReport => "XR",
55        };
56        write!(f, "{s}")
57    }
58}
59
60impl From<u8> for PacketType {
61    fn from(b: u8) -> Self {
62        match b {
63            200 => PacketType::SenderReport,              // RFC 3550, 6.4.1
64            201 => PacketType::ReceiverReport,            // RFC 3550, 6.4.2
65            202 => PacketType::SourceDescription,         // RFC 3550, 6.5
66            203 => PacketType::Goodbye,                   // RFC 3550, 6.6
67            204 => PacketType::ApplicationDefined,        // RFC 3550, 6.7 (unimplemented)
68            205 => PacketType::TransportSpecificFeedback, // RFC 4585, 6051
69            206 => PacketType::PayloadSpecificFeedback,   // RFC 4585, 6.3
70            207 => PacketType::ExtendedReport,            // RFC 3611
71            _ => PacketType::Unsupported,
72        }
73    }
74}
75
76pub const RTP_VERSION: u8 = 2;
77pub const VERSION_SHIFT: u8 = 6;
78pub const VERSION_MASK: u8 = 0x3;
79pub const PADDING_SHIFT: u8 = 5;
80pub const PADDING_MASK: u8 = 0x1;
81pub const COUNT_SHIFT: u8 = 0;
82pub const COUNT_MASK: u8 = 0x1f;
83
84pub const HEADER_LENGTH: usize = 4;
85pub const COUNT_MAX: usize = (1 << 5) - 1;
86pub const SSRC_LENGTH: usize = 4;
87pub const SDES_MAX_OCTET_COUNT: usize = (1 << 8) - 1;
88
89// https://datatracker.ietf.org/doc/html/rfc5104#section-4.3.1
90//
91// The FCI field MUST contain one or more FIR entries.
92//
93// https://datatracker.ietf.org/doc/html/rfc5104#section-4.3.1.1
94//
95// The length of the FIR feedback message MUST be set to
96//    2+2*N, where N is the number of FCI entries.
97pub const FIR_MIN_OCTET_COUNT: usize = 20;
98
99/// A Header is the common header shared by all RTCP packets
100#[derive(Debug, PartialEq, Eq, Default, Clone)]
101pub struct Header {
102    /// If the padding bit is set, this individual RTCP packet contains
103    /// some additional padding octets at the end which are not part of
104    /// the control information but are included in the length field.
105    pub padding: bool,
106    /// The number of reception reports, sources contained or FMT in this packet (depending on the Type)
107    pub count: u8,
108    /// The RTCP packet type for this packet
109    pub packet_type: PacketType,
110    /// The length of this RTCP packet in 32-bit words minus one,
111    /// including the header and any padding.
112    pub length: u16,
113}
114
115/// Marshal encodes the Header in binary
116impl MarshalSize for Header {
117    fn marshal_size(&self) -> usize {
118        HEADER_LENGTH
119    }
120}
121
122impl Marshal for Header {
123    fn marshal_to(&self, mut buf: &mut [u8]) -> Result<usize> {
124        if self.count > 31 {
125            return Err(Error::InvalidHeader);
126        }
127        if buf.remaining_mut() < HEADER_LENGTH {
128            return Err(Error::BufferTooShort);
129        }
130
131        /*
132         *  0                   1                   2                   3
133         *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
134         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135         * |V=2|P|    RC   |   PT=SR=200   |             length            |
136         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
137         */
138        let b0 = (RTP_VERSION << VERSION_SHIFT)
139            | ((self.padding as u8) << PADDING_SHIFT)
140            | (self.count << COUNT_SHIFT);
141
142        buf.put_u8(b0);
143        buf.put_u8(self.packet_type as u8);
144        buf.put_u16(self.length);
145
146        Ok(HEADER_LENGTH)
147    }
148}
149
150impl Unmarshal for Header {
151    /// Unmarshal decodes the Header from binary
152    fn unmarshal<B>(raw_packet: &mut B) -> Result<Self>
153    where
154        Self: Sized,
155        B: Buf,
156    {
157        if raw_packet.remaining() < HEADER_LENGTH {
158            return Err(Error::PacketTooShort);
159        }
160
161        /*
162         *  0                   1                   2                   3
163         *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
164         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
165         * |V=2|P|    RC   |      PT       |             length            |
166         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
167         */
168        let b0 = raw_packet.get_u8();
169        let version = (b0 >> VERSION_SHIFT) & VERSION_MASK;
170        if version != RTP_VERSION {
171            return Err(Error::BadVersion);
172        }
173
174        let padding = ((b0 >> PADDING_SHIFT) & PADDING_MASK) > 0;
175        let count = (b0 >> COUNT_SHIFT) & COUNT_MASK;
176        let packet_type = PacketType::from(raw_packet.get_u8());
177        let length = raw_packet.get_u16();
178
179        Ok(Header {
180            padding,
181            count,
182            packet_type,
183            length,
184        })
185    }
186}
187
188#[cfg(test)]
189mod test {
190    use super::*;
191    use bytes::Bytes;
192
193    #[test]
194    fn test_header_unmarshal() {
195        let tests = vec![
196            (
197                "valid",
198                Bytes::from_static(&[
199                    // v=2, p=0, count=1, RR, len=7
200                    0x81u8, 0xc9, 0x00, 0x07,
201                ]),
202                Header {
203                    padding: false,
204                    count: 1,
205                    packet_type: PacketType::ReceiverReport,
206                    length: 7,
207                },
208                None,
209            ),
210            (
211                "also valid",
212                Bytes::from_static(&[
213                    // v=2, p=1, count=1, BYE, len=7
214                    0xa1, 0xcc, 0x00, 0x07,
215                ]),
216                Header {
217                    padding: true,
218                    count: 1,
219                    packet_type: PacketType::ApplicationDefined,
220                    length: 7,
221                },
222                None,
223            ),
224            (
225                "bad version",
226                Bytes::from_static(&[
227                    // v=0, p=0, count=0, RR, len=4
228                    0x00, 0xc9, 0x00, 0x04,
229                ]),
230                Header {
231                    padding: false,
232                    count: 0,
233                    packet_type: PacketType::Unsupported,
234                    length: 0,
235                },
236                Some(Error::BadVersion),
237            ),
238        ];
239
240        for (name, data, want, want_error) in tests {
241            let buf = &mut data.clone();
242            let got = Header::unmarshal(buf);
243
244            assert_eq!(
245                got.is_err(),
246                want_error.is_some(),
247                "Unmarshal {name}: err = {got:?}, want {want_error:?}"
248            );
249
250            if let Some(want_error) = want_error {
251                let got_err = got.err().unwrap();
252                assert_eq!(
253                    want_error, got_err,
254                    "Unmarshal {name}: err = {got_err:?}, want {want_error:?}",
255                );
256            } else {
257                let actual = got.unwrap();
258                assert_eq!(
259                    actual, want,
260                    "Unmarshal {name}: got {actual:?}, want {want:?}"
261                );
262            }
263        }
264    }
265
266    #[test]
267    fn test_header_roundtrip() {
268        let tests = vec![
269            (
270                "valid",
271                Header {
272                    padding: true,
273                    count: 31,
274                    packet_type: PacketType::SenderReport,
275                    length: 4,
276                },
277                None,
278            ),
279            (
280                "also valid",
281                Header {
282                    padding: false,
283                    count: 28,
284                    packet_type: PacketType::ReceiverReport,
285                    length: 65535,
286                },
287                None,
288            ),
289            (
290                "invalid count",
291                Header {
292                    padding: false,
293                    count: 40,
294                    packet_type: PacketType::Unsupported,
295                    length: 0,
296                },
297                Some(Error::InvalidHeader),
298            ),
299        ];
300
301        for (name, want, want_error) in tests {
302            let got = want.marshal();
303
304            assert_eq!(
305                got.is_ok(),
306                want_error.is_none(),
307                "Marshal {name}: err = {got:?}, want {want_error:?}"
308            );
309
310            if let Some(err) = want_error {
311                let got_err = got.err().unwrap();
312                assert_eq!(
313                    err, got_err,
314                    "Unmarshal {name} rr: err = {got_err:?}, want {err:?}",
315                );
316            } else {
317                let data = got.ok().unwrap();
318                let buf = &mut data.clone();
319                let actual = Header::unmarshal(buf).unwrap_or_else(|_| panic!("Unmarshal {name}"));
320
321                assert_eq!(
322                    actual, want,
323                    "{name} round trip: got {actual:?}, want {want:?}"
324                )
325            }
326        }
327    }
328}