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