Skip to main content

rtcp_packet/
packet.rs

1//! RTCP control packets — RFC 3550 §6.
2//!
3//! Typed, symmetric [`Parse`]/[`Serialize`] for every RTCP packet type: SR
4//! (§6.4.1, PT 200), RR (§6.4.2, PT 201), SDES (§6.5, PT 202), BYE (§6.6, PT
5//! 203), APP (§6.7, PT 204), the [`RtcpPacket`] dispatch enum, and
6//! [`CompoundPacket`] (§6.1: a `Vec` of packets that must start with SR/RR).
7//!
8//! This crate implements exactly the wire structures described in the
9//! curated spec transcription at `rtcp-packet/docs/rtcp.md` (fetched
10//! directly from [RFC 3550](https://www.rfc-editor.org/rfc/rfc3550.txt),
11//! §6) — cite that file, not this doc comment, as the field-semantics
12//! oracle. It documents two known decode-completeness gaps (SR/RR
13//! profile-specific extensions, SDES PRIV sub-structure) that are not typed
14//! by this crate.
15//!
16//! RTCP carries **no media** — this is a standalone wire codec for the RTP
17//! control channel, not a hub `Package`/`Unpackage` spoke.
18//!
19//! # Wire formats
20//!
21//! - **Common header** (§6.1): `V(2)=2 | P(1) | RC/SC(5) | PT(8) | length(16)`,
22//!   where `length` is the packet size in 32-bit words **minus one**.
23//! - **SR** — Sender Report (§6.4.1, PT 200): 20-byte sender info
24//!   (SSRC, NTP MSW/LSW, RTP timestamp, packet count, octet count) then
25//!   `RC` × [`ReportBlock`].
26//! - **RR** — Receiver Report (§6.4.2, PT 201): reporter SSRC then
27//!   `RC` × [`ReportBlock`] (no sender info).
28//! - **[`ReportBlock`]** (§6.4.1, 24 bytes): `SSRC_n`, fraction lost,
29//!   cumulative lost (24-bit **signed**), extended highest sequence,
30//!   interarrival jitter, LSR, DLSR.
31//! - **SDES** — Source Description (§6.5, PT 202): `SC` chunks of
32//!   `SSRC/CSRC` + a list of `[type(8), length(8), text]` items, terminated by
33//!   a type-0 item and padded to a 32-bit boundary.
34//! - **BYE** (§6.6, PT 203): `SC` × `SSRC/CSRC` + an optional reason string.
35//! - **APP** (§6.7, PT 204): subtype (in the RC field), SSRC, 4-byte ASCII
36//!   name, application-dependent data (32-bit aligned).
37//! - **[`CompoundPacket`]** (§6.1): a sequence of RTCP packets that **must**
38//!   begin with an SR or RR.
39//!
40//! # Reserved-bit / version policy
41//!
42//! The version field is validated (must be 2). The padding (`P`) bit is parsed
43//! and preserved but this codec emits unpadded packets (`P=0`); padding bytes on
44//! the wire are consumed per the length field. `no_std` + `alloc`.
45
46use alloc::string::String;
47use alloc::vec::Vec;
48
49use broadcast_common::{Parse, Serialize};
50
51use crate::error::{Error, Result};
52
53// ---------------------------------------------------------------------------
54// Named constants (no magic numbers — RFC 3550 §6)
55// ---------------------------------------------------------------------------
56
57/// RTCP protocol version — always 2 (RFC 3550 §6.4.1).
58const RTCP_VERSION: u8 = 2;
59/// Byte 0 of the common header with `V=2 P=0` and a zero count field.
60const RTCP_BYTE0_V2: u8 = RTCP_VERSION << 6;
61/// Common-header length in bytes (`V/P/count | PT | length(16)`).
62const RTCP_HEADER_LEN: usize = 4;
63/// Padding-bit mask within byte 0 (`P` — RFC 3550 §6.4.1).
64const RTCP_PADDING_MASK: u8 = 0x20;
65/// Report-count / source-count mask within byte 0 (low 5 bits).
66const RTCP_COUNT_MASK: u8 = 0x1F;
67/// One 32-bit word, in bytes — the unit of the header `length` field.
68const WORD_LEN: usize = 4;
69
70/// Packet type: Sender Report (RFC 3550 §6.4.1).
71pub const PT_SENDER_REPORT: u8 = 200;
72/// Packet type: Receiver Report (RFC 3550 §6.4.2).
73pub const PT_RECEIVER_REPORT: u8 = 201;
74/// Packet type: Source Description (RFC 3550 §6.5).
75pub const PT_SOURCE_DESCRIPTION: u8 = 202;
76/// Packet type: Goodbye (RFC 3550 §6.6).
77pub const PT_BYE: u8 = 203;
78/// Packet type: Application-defined (RFC 3550 §6.7).
79pub const PT_APP: u8 = 204;
80
81/// Length of a single [`ReportBlock`] on the wire (RFC 3550 §6.4.1).
82pub const REPORT_BLOCK_LEN: usize = 24;
83/// Length of the SR sender-info block (RFC 3550 §6.4.1), excluding the SSRC.
84const SR_SENDER_INFO_LEN: usize = 20;
85/// Length of the APP `name` field — 4 ASCII characters (RFC 3550 §6.7).
86pub const APP_NAME_LEN: usize = 4;
87/// Maximum count encodable in the 5-bit `RC`/`SC` field.
88const MAX_COUNT: usize = RTCP_COUNT_MASK as usize;
89
90// ---------------------------------------------------------------------------
91// Big-endian read helpers (bounds-checked)
92// ---------------------------------------------------------------------------
93
94/// Read a big-endian `u32` at `off`, or `BufferTooShort`.
95fn be_u32(bytes: &[u8], off: usize, what: &'static str) -> Result<u32> {
96    bytes
97        .get(off..off + 4)
98        .map(|s| u32::from_be_bytes([s[0], s[1], s[2], s[3]]))
99        .ok_or(Error::BufferTooShort {
100            need: off + 4,
101            have: bytes.len(),
102            what,
103        })
104}
105
106// ---------------------------------------------------------------------------
107// RtcpPacketType — the PT byte, typed
108// ---------------------------------------------------------------------------
109
110/// The RTCP packet type carried in the common header `PT` byte (RFC 3550 §6).
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize))]
113#[non_exhaustive]
114pub enum RtcpPacketType {
115    /// Sender Report (PT 200).
116    SenderReport,
117    /// Receiver Report (PT 201).
118    ReceiverReport,
119    /// Source Description (PT 202).
120    SourceDescription,
121    /// Goodbye (PT 203).
122    Bye,
123    /// Application-defined (PT 204).
124    App,
125    /// A packet type outside the RFC 3550 §6 core set.
126    Unknown(u8),
127}
128
129impl RtcpPacketType {
130    /// Decode the wire `PT` byte.
131    pub fn from_pt(pt: u8) -> Self {
132        match pt {
133            PT_SENDER_REPORT => RtcpPacketType::SenderReport,
134            PT_RECEIVER_REPORT => RtcpPacketType::ReceiverReport,
135            PT_SOURCE_DESCRIPTION => RtcpPacketType::SourceDescription,
136            PT_BYE => RtcpPacketType::Bye,
137            PT_APP => RtcpPacketType::App,
138            other => RtcpPacketType::Unknown(other),
139        }
140    }
141
142    /// The wire `PT` byte for this packet type.
143    pub fn pt(&self) -> u8 {
144        match self {
145            RtcpPacketType::SenderReport => PT_SENDER_REPORT,
146            RtcpPacketType::ReceiverReport => PT_RECEIVER_REPORT,
147            RtcpPacketType::SourceDescription => PT_SOURCE_DESCRIPTION,
148            RtcpPacketType::Bye => PT_BYE,
149            RtcpPacketType::App => PT_APP,
150            RtcpPacketType::Unknown(pt) => *pt,
151        }
152    }
153
154    /// Spec token for this packet type.
155    pub fn name(&self) -> &'static str {
156        match self {
157            RtcpPacketType::SenderReport => "SR",
158            RtcpPacketType::ReceiverReport => "RR",
159            RtcpPacketType::SourceDescription => "SDES",
160            RtcpPacketType::Bye => "BYE",
161            RtcpPacketType::App => "APP",
162            RtcpPacketType::Unknown(_) => "reserved",
163        }
164    }
165}
166
167broadcast_common::impl_spec_display!(RtcpPacketType, Unknown);
168
169// ---------------------------------------------------------------------------
170// Common header (RFC 3550 §6.1 / §6.4.1)
171// ---------------------------------------------------------------------------
172
173/// The 4-byte RTCP common header shared by every packet type (RFC 3550 §6.1).
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize))]
176pub struct CommonHeader {
177    /// The `P` (padding) bit.
178    pub padding: bool,
179    /// The `RC`/`SC` 5-bit count (report count for SR/RR, source count for
180    /// SDES/BYE, subtype for APP).
181    pub count: u8,
182    /// The `PT` (packet type) byte.
183    pub packet_type: u8,
184    /// The `length` field: packet size in 32-bit words **minus one**.
185    pub length: u16,
186}
187
188impl CommonHeader {
189    /// Build a header from decoded fields (`V=2`, `P=0`).
190    fn new(count: u8, packet_type: u8, length_words_minus_one: u16) -> Self {
191        Self {
192            padding: false,
193            count: count & RTCP_COUNT_MASK,
194            packet_type,
195            length: length_words_minus_one,
196        }
197    }
198
199    /// Parse the common header, validating the version field.
200    fn parse(bytes: &[u8]) -> Result<Self> {
201        if bytes.len() < RTCP_HEADER_LEN {
202            return Err(Error::BufferTooShort {
203                need: RTCP_HEADER_LEN,
204                have: bytes.len(),
205                what: "RTCP common header",
206            });
207        }
208        let version = bytes[0] >> 6;
209        if version != RTCP_VERSION {
210            return Err(Error::InvalidValue {
211                field: "rtcp_version",
212                value: version as u64,
213                reason: "must be 2",
214            });
215        }
216        Ok(Self {
217            padding: bytes[0] & RTCP_PADDING_MASK != 0,
218            count: bytes[0] & RTCP_COUNT_MASK,
219            packet_type: bytes[1],
220            length: u16::from_be_bytes([bytes[2], bytes[3]]),
221        })
222    }
223
224    /// The total on-the-wire byte length of the packet this header describes:
225    /// `(length + 1) * 4`.
226    fn total_len(&self) -> usize {
227        (self.length as usize + 1) * WORD_LEN
228    }
229
230    /// Write the 4-byte header (`V=2`, given `P`/count/PT/length).
231    fn write(&self, buf: &mut [u8]) {
232        buf[0] = RTCP_BYTE0_V2
233            | (if self.padding { RTCP_PADDING_MASK } else { 0 })
234            | (self.count & RTCP_COUNT_MASK);
235        buf[1] = self.packet_type;
236        buf[2..4].copy_from_slice(&self.length.to_be_bytes());
237    }
238}
239
240/// Compute the header `length` field (32-bit words − 1) for a body whose total
241/// serialized length (header included) is `total_len` bytes. `total_len` is a
242/// multiple of 4 for every RTCP packet this codec emits.
243fn length_words_minus_one(total_len: usize) -> u16 {
244    (total_len / WORD_LEN).saturating_sub(1) as u16
245}
246
247// ---------------------------------------------------------------------------
248// ReportBlock (RFC 3550 §6.4.1)
249// ---------------------------------------------------------------------------
250
251/// A reception report block (RFC 3550 §6.4.1, 24 bytes). Carried by both SR and
252/// RR, one per reported source.
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize))]
255pub struct ReportBlock {
256    /// SSRC of the source this block reports on.
257    pub ssrc: u32,
258    /// Fraction of packets lost since the previous report (8.8 fixed-point num).
259    pub fraction_lost: u8,
260    /// Cumulative number of packets lost — a 24-bit **signed** value.
261    pub cumulative_lost: i32,
262    /// Extended highest sequence number received.
263    pub ext_highest_seq: u32,
264    /// Interarrival jitter estimate.
265    pub jitter: u32,
266    /// Last SR timestamp (middle 32 bits of the sender's NTP time), or 0.
267    pub lsr: u32,
268    /// Delay since last SR, in units of 1/65536 s, or 0.
269    pub dlsr: u32,
270}
271
272impl ReportBlock {
273    /// Sign-extend a 24-bit `cumulative_lost` field to `i32`.
274    fn decode_cumulative_lost(raw: u32) -> i32 {
275        // raw is a 24-bit two's-complement value; extend the sign bit (bit 23).
276        const SIGN_BIT: u32 = 1 << 23;
277        if raw & SIGN_BIT != 0 {
278            (raw | 0xFF00_0000) as i32
279        } else {
280            raw as i32
281        }
282    }
283
284    /// Encode a signed `cumulative_lost` back to its 24-bit field.
285    fn encode_cumulative_lost(&self) -> u32 {
286        (self.cumulative_lost as u32) & 0x00FF_FFFF
287    }
288}
289
290impl<'a> Parse<'a> for ReportBlock {
291    type Error = Error;
292
293    fn parse(bytes: &'a [u8]) -> Result<Self> {
294        if bytes.len() < REPORT_BLOCK_LEN {
295            return Err(Error::BufferTooShort {
296                need: REPORT_BLOCK_LEN,
297                have: bytes.len(),
298                what: "RTCP report block",
299            });
300        }
301        let ssrc = be_u32(bytes, 0, "report block ssrc")?;
302        let fraction_lost = bytes[4];
303        let cumulative_raw = u32::from_be_bytes([0, bytes[5], bytes[6], bytes[7]]);
304        Ok(ReportBlock {
305            ssrc,
306            fraction_lost,
307            cumulative_lost: ReportBlock::decode_cumulative_lost(cumulative_raw),
308            ext_highest_seq: be_u32(bytes, 8, "report block ext seq")?,
309            jitter: be_u32(bytes, 12, "report block jitter")?,
310            lsr: be_u32(bytes, 16, "report block lsr")?,
311            dlsr: be_u32(bytes, 20, "report block dlsr")?,
312        })
313    }
314}
315
316impl Serialize for ReportBlock {
317    type Error = Error;
318
319    fn serialized_len(&self) -> usize {
320        REPORT_BLOCK_LEN
321    }
322
323    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
324        if buf.len() < REPORT_BLOCK_LEN {
325            return Err(Error::OutputBufferTooSmall {
326                need: REPORT_BLOCK_LEN,
327                have: buf.len(),
328            });
329        }
330        buf[0..4].copy_from_slice(&self.ssrc.to_be_bytes());
331        buf[4] = self.fraction_lost;
332        let cum = self.encode_cumulative_lost().to_be_bytes();
333        buf[5..8].copy_from_slice(&cum[1..4]);
334        buf[8..12].copy_from_slice(&self.ext_highest_seq.to_be_bytes());
335        buf[12..16].copy_from_slice(&self.jitter.to_be_bytes());
336        buf[16..20].copy_from_slice(&self.lsr.to_be_bytes());
337        buf[20..24].copy_from_slice(&self.dlsr.to_be_bytes());
338        Ok(REPORT_BLOCK_LEN)
339    }
340}
341
342/// Parse `count` back-to-back report blocks from `bytes`.
343fn parse_report_blocks(bytes: &[u8], count: usize) -> Result<Vec<ReportBlock>> {
344    let mut blocks = Vec::with_capacity(count);
345    let mut off = 0;
346    for _ in 0..count {
347        let end = off + REPORT_BLOCK_LEN;
348        if end > bytes.len() {
349            return Err(Error::BufferTooShort {
350                need: end,
351                have: bytes.len(),
352                what: "RTCP report blocks",
353            });
354        }
355        blocks.push(ReportBlock::parse(&bytes[off..end])?);
356        off = end;
357    }
358    Ok(blocks)
359}
360
361/// Validate a report-block list fits the 5-bit `RC` count field.
362fn check_report_count(blocks: &[ReportBlock]) -> Result<u8> {
363    if blocks.len() > MAX_COUNT {
364        return Err(Error::InvalidValue {
365            field: "rtcp_report_count",
366            value: blocks.len() as u64,
367            reason: "exceeds 5-bit RC field",
368        });
369    }
370    Ok(blocks.len() as u8)
371}
372
373// ---------------------------------------------------------------------------
374// SenderReport (RFC 3550 §6.4.1, PT 200)
375// ---------------------------------------------------------------------------
376
377/// RTCP Sender Report (RFC 3550 §6.4.1, PT 200).
378#[derive(Debug, Clone, PartialEq, Eq)]
379#[cfg_attr(feature = "serde", derive(serde::Serialize))]
380pub struct SenderReport {
381    /// SSRC of the sender originating this report.
382    pub ssrc: u32,
383    /// NTP timestamp, most significant word (integer seconds).
384    pub ntp_msw: u32,
385    /// NTP timestamp, least significant word (fractional seconds).
386    pub ntp_lsw: u32,
387    /// RTP timestamp corresponding to the NTP wall-clock time.
388    pub rtp_timestamp: u32,
389    /// Sender's cumulative packet count.
390    pub packet_count: u32,
391    /// Sender's cumulative octet count.
392    pub octet_count: u32,
393    /// Reception report blocks (`RC` of them).
394    pub report_blocks: Vec<ReportBlock>,
395}
396
397impl<'a> Parse<'a> for SenderReport {
398    type Error = Error;
399
400    fn parse(bytes: &'a [u8]) -> Result<Self> {
401        let hdr = CommonHeader::parse(bytes)?;
402        if hdr.packet_type != PT_SENDER_REPORT {
403            return Err(Error::InvalidValue {
404                field: "rtcp_pt",
405                value: hdr.packet_type as u64,
406                reason: "expected SR (200)",
407            });
408        }
409        let total = hdr.total_len();
410        if bytes.len() < total {
411            return Err(Error::BufferTooShort {
412                need: total,
413                have: bytes.len(),
414                what: "RTCP SR",
415            });
416        }
417        let body = &bytes[RTCP_HEADER_LEN..total];
418        if body.len() < WORD_LEN + SR_SENDER_INFO_LEN {
419            return Err(Error::BufferTooShort {
420                need: WORD_LEN + SR_SENDER_INFO_LEN,
421                have: body.len(),
422                what: "RTCP SR sender info",
423            });
424        }
425        let ssrc = be_u32(body, 0, "SR ssrc")?;
426        let ntp_msw = be_u32(body, 4, "SR ntp msw")?;
427        let ntp_lsw = be_u32(body, 8, "SR ntp lsw")?;
428        let rtp_timestamp = be_u32(body, 12, "SR rtp ts")?;
429        let packet_count = be_u32(body, 16, "SR packet count")?;
430        let octet_count = be_u32(body, 20, "SR octet count")?;
431        let blocks_off = WORD_LEN + SR_SENDER_INFO_LEN;
432        let report_blocks = parse_report_blocks(&body[blocks_off..], hdr.count as usize)?;
433        Ok(SenderReport {
434            ssrc,
435            ntp_msw,
436            ntp_lsw,
437            rtp_timestamp,
438            packet_count,
439            octet_count,
440            report_blocks,
441        })
442    }
443}
444
445impl Serialize for SenderReport {
446    type Error = Error;
447
448    fn serialized_len(&self) -> usize {
449        RTCP_HEADER_LEN
450            + WORD_LEN
451            + SR_SENDER_INFO_LEN
452            + self.report_blocks.len() * REPORT_BLOCK_LEN
453    }
454
455    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
456        let len = self.serialized_len();
457        if buf.len() < len {
458            return Err(Error::OutputBufferTooSmall {
459                need: len,
460                have: buf.len(),
461            });
462        }
463        let rc = check_report_count(&self.report_blocks)?;
464        let hdr = CommonHeader::new(rc, PT_SENDER_REPORT, length_words_minus_one(len));
465        hdr.write(&mut buf[0..RTCP_HEADER_LEN]);
466        let mut off = RTCP_HEADER_LEN;
467        buf[off..off + 4].copy_from_slice(&self.ssrc.to_be_bytes());
468        buf[off + 4..off + 8].copy_from_slice(&self.ntp_msw.to_be_bytes());
469        buf[off + 8..off + 12].copy_from_slice(&self.ntp_lsw.to_be_bytes());
470        buf[off + 12..off + 16].copy_from_slice(&self.rtp_timestamp.to_be_bytes());
471        buf[off + 16..off + 20].copy_from_slice(&self.packet_count.to_be_bytes());
472        buf[off + 20..off + 24].copy_from_slice(&self.octet_count.to_be_bytes());
473        off += WORD_LEN + SR_SENDER_INFO_LEN;
474        for block in &self.report_blocks {
475            block.serialize_into(&mut buf[off..off + REPORT_BLOCK_LEN])?;
476            off += REPORT_BLOCK_LEN;
477        }
478        Ok(len)
479    }
480}
481
482// ---------------------------------------------------------------------------
483// ReceiverReport (RFC 3550 §6.4.2, PT 201)
484// ---------------------------------------------------------------------------
485
486/// RTCP Receiver Report (RFC 3550 §6.4.2, PT 201).
487#[derive(Debug, Clone, PartialEq, Eq)]
488#[cfg_attr(feature = "serde", derive(serde::Serialize))]
489pub struct ReceiverReport {
490    /// SSRC of the packet sender originating this report.
491    pub ssrc: u32,
492    /// Reception report blocks (`RC` of them).
493    pub report_blocks: Vec<ReportBlock>,
494}
495
496impl<'a> Parse<'a> for ReceiverReport {
497    type Error = Error;
498
499    fn parse(bytes: &'a [u8]) -> Result<Self> {
500        let hdr = CommonHeader::parse(bytes)?;
501        if hdr.packet_type != PT_RECEIVER_REPORT {
502            return Err(Error::InvalidValue {
503                field: "rtcp_pt",
504                value: hdr.packet_type as u64,
505                reason: "expected RR (201)",
506            });
507        }
508        let total = hdr.total_len();
509        if bytes.len() < total {
510            return Err(Error::BufferTooShort {
511                need: total,
512                have: bytes.len(),
513                what: "RTCP RR",
514            });
515        }
516        let body = &bytes[RTCP_HEADER_LEN..total];
517        let ssrc = be_u32(body, 0, "RR ssrc")?;
518        let report_blocks = parse_report_blocks(&body[WORD_LEN..], hdr.count as usize)?;
519        Ok(ReceiverReport {
520            ssrc,
521            report_blocks,
522        })
523    }
524}
525
526impl Serialize for ReceiverReport {
527    type Error = Error;
528
529    fn serialized_len(&self) -> usize {
530        RTCP_HEADER_LEN + WORD_LEN + self.report_blocks.len() * REPORT_BLOCK_LEN
531    }
532
533    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
534        let len = self.serialized_len();
535        if buf.len() < len {
536            return Err(Error::OutputBufferTooSmall {
537                need: len,
538                have: buf.len(),
539            });
540        }
541        let rc = check_report_count(&self.report_blocks)?;
542        let hdr = CommonHeader::new(rc, PT_RECEIVER_REPORT, length_words_minus_one(len));
543        hdr.write(&mut buf[0..RTCP_HEADER_LEN]);
544        let mut off = RTCP_HEADER_LEN;
545        buf[off..off + 4].copy_from_slice(&self.ssrc.to_be_bytes());
546        off += WORD_LEN;
547        for block in &self.report_blocks {
548            block.serialize_into(&mut buf[off..off + REPORT_BLOCK_LEN])?;
549            off += REPORT_BLOCK_LEN;
550        }
551        Ok(len)
552    }
553}
554
555// ---------------------------------------------------------------------------
556// SourceDescription (RFC 3550 §6.5, PT 202)
557// ---------------------------------------------------------------------------
558
559/// SDES item type (RFC 3550 §6.5). Byte-valued; type 0 is the item terminator.
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561#[cfg_attr(feature = "serde", derive(serde::Serialize))]
562#[non_exhaustive]
563pub enum SdesItemType {
564    /// Canonical end-point identifier (CNAME = 1).
565    CName,
566    /// User name (NAME = 2).
567    Name,
568    /// Electronic mail address (EMAIL = 3).
569    Email,
570    /// Phone number (PHONE = 4).
571    Phone,
572    /// Geographic location (LOC = 5).
573    Loc,
574    /// Application / tool name+version (TOOL = 6).
575    Tool,
576    /// Notice / status (NOTE = 7).
577    Note,
578    /// Private extension (PRIV = 8).
579    Priv,
580    /// A type outside the RFC 3550 §6.5 set (never the 0 terminator).
581    Unknown(u8),
582}
583
584/// SDES item type value: CNAME (RFC 3550 §6.5.1).
585pub const SDES_CNAME: u8 = 1;
586/// SDES item type value: NAME (RFC 3550 §6.5.2).
587pub const SDES_NAME: u8 = 2;
588/// SDES item type value: EMAIL (RFC 3550 §6.5.3).
589pub const SDES_EMAIL: u8 = 3;
590/// SDES item type value: PHONE (RFC 3550 §6.5.4).
591pub const SDES_PHONE: u8 = 4;
592/// SDES item type value: LOC (RFC 3550 §6.5.5).
593pub const SDES_LOC: u8 = 5;
594/// SDES item type value: TOOL (RFC 3550 §6.5.6).
595pub const SDES_TOOL: u8 = 6;
596/// SDES item type value: NOTE (RFC 3550 §6.5.7).
597pub const SDES_NOTE: u8 = 7;
598/// SDES item type value: PRIV (RFC 3550 §6.5.8).
599pub const SDES_PRIV: u8 = 8;
600/// SDES chunk item-list terminator (RFC 3550 §6.5).
601const SDES_TERMINATOR: u8 = 0;
602
603impl SdesItemType {
604    /// Decode the wire item-type byte.
605    pub fn from_type(t: u8) -> Self {
606        match t {
607            SDES_CNAME => SdesItemType::CName,
608            SDES_NAME => SdesItemType::Name,
609            SDES_EMAIL => SdesItemType::Email,
610            SDES_PHONE => SdesItemType::Phone,
611            SDES_LOC => SdesItemType::Loc,
612            SDES_TOOL => SdesItemType::Tool,
613            SDES_NOTE => SdesItemType::Note,
614            SDES_PRIV => SdesItemType::Priv,
615            other => SdesItemType::Unknown(other),
616        }
617    }
618
619    /// The wire item-type byte.
620    pub fn item_type(&self) -> u8 {
621        match self {
622            SdesItemType::CName => SDES_CNAME,
623            SdesItemType::Name => SDES_NAME,
624            SdesItemType::Email => SDES_EMAIL,
625            SdesItemType::Phone => SDES_PHONE,
626            SdesItemType::Loc => SDES_LOC,
627            SdesItemType::Tool => SDES_TOOL,
628            SdesItemType::Note => SDES_NOTE,
629            SdesItemType::Priv => SDES_PRIV,
630            SdesItemType::Unknown(t) => *t,
631        }
632    }
633
634    /// Spec token for this item type.
635    pub fn name(&self) -> &'static str {
636        match self {
637            SdesItemType::CName => "CNAME",
638            SdesItemType::Name => "NAME",
639            SdesItemType::Email => "EMAIL",
640            SdesItemType::Phone => "PHONE",
641            SdesItemType::Loc => "LOC",
642            SdesItemType::Tool => "TOOL",
643            SdesItemType::Note => "NOTE",
644            SdesItemType::Priv => "PRIV",
645            SdesItemType::Unknown(_) => "reserved",
646        }
647    }
648}
649
650broadcast_common::impl_spec_display!(SdesItemType, Unknown);
651
652/// A single SDES item: a typed, length-prefixed text field (RFC 3550 §6.5).
653#[derive(Debug, Clone, PartialEq, Eq)]
654#[cfg_attr(feature = "serde", derive(serde::Serialize))]
655pub struct SdesItem {
656    /// The item type.
657    pub item_type: SdesItemType,
658    /// The item text (up to 255 bytes; UTF-8 per §6.5).
659    pub text: String,
660}
661
662/// An SDES chunk: an SSRC/CSRC plus its list of items (RFC 3550 §6.5).
663#[derive(Debug, Clone, PartialEq, Eq)]
664#[cfg_attr(feature = "serde", derive(serde::Serialize))]
665pub struct SdesChunk {
666    /// The SSRC or CSRC this chunk describes.
667    pub source: u32,
668    /// The chunk's items (in wire order), before the type-0 terminator.
669    pub items: Vec<SdesItem>,
670}
671
672impl SdesChunk {
673    /// On-the-wire byte length of this chunk **before** 32-bit padding:
674    /// 4 (source) + Σ(2 + text.len()) + 1 (terminator).
675    fn unpadded_len(&self) -> usize {
676        WORD_LEN + self.items.iter().map(|it| 2 + it.text.len()).sum::<usize>() + 1
677    }
678
679    /// Padded (32-bit-aligned) length of this chunk on the wire.
680    fn padded_len(&self) -> usize {
681        self.unpadded_len().div_ceil(WORD_LEN) * WORD_LEN
682    }
683}
684
685/// RTCP Source Description (RFC 3550 §6.5, PT 202).
686#[derive(Debug, Clone, PartialEq, Eq)]
687#[cfg_attr(feature = "serde", derive(serde::Serialize))]
688pub struct SourceDescription {
689    /// The chunks (`SC` of them), one per described source.
690    pub chunks: Vec<SdesChunk>,
691}
692
693impl<'a> Parse<'a> for SourceDescription {
694    type Error = Error;
695
696    fn parse(bytes: &'a [u8]) -> Result<Self> {
697        let hdr = CommonHeader::parse(bytes)?;
698        if hdr.packet_type != PT_SOURCE_DESCRIPTION {
699            return Err(Error::InvalidValue {
700                field: "rtcp_pt",
701                value: hdr.packet_type as u64,
702                reason: "expected SDES (202)",
703            });
704        }
705        let total = hdr.total_len();
706        if bytes.len() < total {
707            return Err(Error::BufferTooShort {
708                need: total,
709                have: bytes.len(),
710                what: "RTCP SDES",
711            });
712        }
713        let body = &bytes[RTCP_HEADER_LEN..total];
714        let mut chunks = Vec::with_capacity(hdr.count as usize);
715        let mut off = 0;
716        for _ in 0..hdr.count {
717            let (chunk, consumed) = parse_sdes_chunk(&body[off..])?;
718            chunks.push(chunk);
719            off += consumed;
720        }
721        Ok(SourceDescription { chunks })
722    }
723}
724
725/// Parse one SDES chunk starting at `bytes[0]`; return it and bytes consumed
726/// (including the type-0 terminator and any 32-bit padding).
727fn parse_sdes_chunk(bytes: &[u8]) -> Result<(SdesChunk, usize)> {
728    let source = be_u32(bytes, 0, "SDES chunk source")?;
729    let mut off = WORD_LEN;
730    let mut items = Vec::new();
731    loop {
732        let t = *bytes.get(off).ok_or(Error::BufferTooShort {
733            need: off + 1,
734            have: bytes.len(),
735            what: "SDES item type",
736        })?;
737        off += 1;
738        if t == SDES_TERMINATOR {
739            break;
740        }
741        let len = *bytes.get(off).ok_or(Error::BufferTooShort {
742            need: off + 1,
743            have: bytes.len(),
744            what: "SDES item length",
745        })? as usize;
746        off += 1;
747        let end = off + len;
748        let text_bytes = bytes.get(off..end).ok_or(Error::BufferTooShort {
749            need: end,
750            have: bytes.len(),
751            what: "SDES item text",
752        })?;
753        let text = String::from_utf8(text_bytes.to_vec()).map_err(|_| Error::InvalidValue {
754            field: "sdes_item_text",
755            value: 0,
756            reason: "not valid UTF-8",
757        })?;
758        items.push(SdesItem {
759            item_type: SdesItemType::from_type(t),
760            text,
761        });
762        off = end;
763    }
764    // Advance past the type-0 terminator to the next 32-bit boundary.
765    let padded = off.div_ceil(WORD_LEN) * WORD_LEN;
766    if padded > bytes.len() {
767        return Err(Error::BufferTooShort {
768            need: padded,
769            have: bytes.len(),
770            what: "SDES chunk padding",
771        });
772    }
773    Ok((SdesChunk { source, items }, padded))
774}
775
776impl Serialize for SourceDescription {
777    type Error = Error;
778
779    fn serialized_len(&self) -> usize {
780        RTCP_HEADER_LEN + self.chunks.iter().map(SdesChunk::padded_len).sum::<usize>()
781    }
782
783    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
784        let len = self.serialized_len();
785        if buf.len() < len {
786            return Err(Error::OutputBufferTooSmall {
787                need: len,
788                have: buf.len(),
789            });
790        }
791        if self.chunks.len() > MAX_COUNT {
792            return Err(Error::InvalidValue {
793                field: "rtcp_source_count",
794                value: self.chunks.len() as u64,
795                reason: "exceeds 5-bit SC field",
796            });
797        }
798        let hdr = CommonHeader::new(
799            self.chunks.len() as u8,
800            PT_SOURCE_DESCRIPTION,
801            length_words_minus_one(len),
802        );
803        hdr.write(&mut buf[0..RTCP_HEADER_LEN]);
804        let mut off = RTCP_HEADER_LEN;
805        for chunk in &self.chunks {
806            let padded = chunk.padded_len();
807            // Zero the whole chunk region first so padding bytes are 0.
808            for b in buf[off..off + padded].iter_mut() {
809                *b = 0;
810            }
811            buf[off..off + 4].copy_from_slice(&chunk.source.to_be_bytes());
812            let mut io = off + WORD_LEN;
813            for item in &chunk.items {
814                if item.text.len() > u8::MAX as usize {
815                    return Err(Error::InvalidValue {
816                        field: "sdes_item_len",
817                        value: item.text.len() as u64,
818                        reason: "exceeds 8-bit SDES item length",
819                    });
820                }
821                buf[io] = item.item_type.item_type();
822                buf[io + 1] = item.text.len() as u8;
823                buf[io + 2..io + 2 + item.text.len()].copy_from_slice(item.text.as_bytes());
824                io += 2 + item.text.len();
825            }
826            // buf[io] terminator (already zeroed); remaining padding zeroed.
827            off += padded;
828        }
829        Ok(len)
830    }
831}
832
833// ---------------------------------------------------------------------------
834// Bye (RFC 3550 §6.6, PT 203)
835// ---------------------------------------------------------------------------
836
837/// RTCP Goodbye (RFC 3550 §6.6, PT 203).
838#[derive(Debug, Clone, PartialEq, Eq)]
839#[cfg_attr(feature = "serde", derive(serde::Serialize))]
840pub struct Bye {
841    /// The SSRC/CSRC sources leaving (`SC` of them).
842    pub sources: Vec<u32>,
843    /// Optional textual reason for leaving.
844    pub reason: Option<String>,
845}
846
847impl Bye {
848    /// Unpadded byte length of the reason field (length octet + text), if any.
849    fn reason_unpadded_len(&self) -> usize {
850        match &self.reason {
851            Some(r) => 1 + r.len(),
852            None => 0,
853        }
854    }
855}
856
857impl<'a> Parse<'a> for Bye {
858    type Error = Error;
859
860    fn parse(bytes: &'a [u8]) -> Result<Self> {
861        let hdr = CommonHeader::parse(bytes)?;
862        if hdr.packet_type != PT_BYE {
863            return Err(Error::InvalidValue {
864                field: "rtcp_pt",
865                value: hdr.packet_type as u64,
866                reason: "expected BYE (203)",
867            });
868        }
869        let total = hdr.total_len();
870        if bytes.len() < total {
871            return Err(Error::BufferTooShort {
872                need: total,
873                have: bytes.len(),
874                what: "RTCP BYE",
875            });
876        }
877        let body = &bytes[RTCP_HEADER_LEN..total];
878        let sc = hdr.count as usize;
879        if body.len() < sc * WORD_LEN {
880            return Err(Error::BufferTooShort {
881                need: sc * WORD_LEN,
882                have: body.len(),
883                what: "RTCP BYE sources",
884            });
885        }
886        let mut sources = Vec::with_capacity(sc);
887        let mut off = 0;
888        for _ in 0..sc {
889            sources.push(be_u32(body, off, "BYE source")?);
890            off += WORD_LEN;
891        }
892        // Optional reason: a length octet + text, if any bytes remain.
893        let reason = if off < body.len() {
894            let len = body[off] as usize;
895            off += 1;
896            let end = off + len;
897            if end > body.len() {
898                return Err(Error::BufferTooShort {
899                    need: end,
900                    have: body.len(),
901                    what: "RTCP BYE reason text",
902                });
903            }
904            let text =
905                String::from_utf8(body[off..end].to_vec()).map_err(|_| Error::InvalidValue {
906                    field: "bye_reason",
907                    value: 0,
908                    reason: "not valid UTF-8",
909                })?;
910            Some(text)
911        } else {
912            None
913        };
914        Ok(Bye { sources, reason })
915    }
916}
917
918impl Serialize for Bye {
919    type Error = Error;
920
921    fn serialized_len(&self) -> usize {
922        let raw = RTCP_HEADER_LEN + self.sources.len() * WORD_LEN + self.reason_unpadded_len();
923        raw.div_ceil(WORD_LEN) * WORD_LEN
924    }
925
926    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
927        let len = self.serialized_len();
928        if buf.len() < len {
929            return Err(Error::OutputBufferTooSmall {
930                need: len,
931                have: buf.len(),
932            });
933        }
934        if self.sources.len() > MAX_COUNT {
935            return Err(Error::InvalidValue {
936                field: "rtcp_source_count",
937                value: self.sources.len() as u64,
938                reason: "exceeds 5-bit SC field",
939            });
940        }
941        if let Some(r) = &self.reason {
942            if r.len() > u8::MAX as usize {
943                return Err(Error::InvalidValue {
944                    field: "bye_reason_len",
945                    value: r.len() as u64,
946                    reason: "exceeds 8-bit reason length",
947                });
948            }
949        }
950        // Zero the whole region so trailing padding bytes are 0.
951        for b in buf[..len].iter_mut() {
952            *b = 0;
953        }
954        let hdr = CommonHeader::new(
955            self.sources.len() as u8,
956            PT_BYE,
957            length_words_minus_one(len),
958        );
959        hdr.write(&mut buf[0..RTCP_HEADER_LEN]);
960        let mut off = RTCP_HEADER_LEN;
961        for src in &self.sources {
962            buf[off..off + 4].copy_from_slice(&src.to_be_bytes());
963            off += WORD_LEN;
964        }
965        if let Some(r) = &self.reason {
966            buf[off] = r.len() as u8;
967            off += 1;
968            buf[off..off + r.len()].copy_from_slice(r.as_bytes());
969        }
970        Ok(len)
971    }
972}
973
974// ---------------------------------------------------------------------------
975// App (RFC 3550 §6.7, PT 204)
976// ---------------------------------------------------------------------------
977
978/// RTCP Application-defined packet (RFC 3550 §6.7, PT 204).
979#[derive(Debug, Clone, PartialEq, Eq)]
980#[cfg_attr(feature = "serde", derive(serde::Serialize))]
981pub struct App {
982    /// Application subtype (carried in the header's `RC` field, 5 bits).
983    pub subtype: u8,
984    /// SSRC/CSRC of the source.
985    pub ssrc: u32,
986    /// The 4-byte ASCII application name.
987    pub name: [u8; APP_NAME_LEN],
988    /// Application-dependent data (must be a multiple of 4 bytes on the wire).
989    pub data: Vec<u8>,
990}
991
992impl<'a> Parse<'a> for App {
993    type Error = Error;
994
995    fn parse(bytes: &'a [u8]) -> Result<Self> {
996        let hdr = CommonHeader::parse(bytes)?;
997        if hdr.packet_type != PT_APP {
998            return Err(Error::InvalidValue {
999                field: "rtcp_pt",
1000                value: hdr.packet_type as u64,
1001                reason: "expected APP (204)",
1002            });
1003        }
1004        let total = hdr.total_len();
1005        if bytes.len() < total {
1006            return Err(Error::BufferTooShort {
1007                need: total,
1008                have: bytes.len(),
1009                what: "RTCP APP",
1010            });
1011        }
1012        let body = &bytes[RTCP_HEADER_LEN..total];
1013        if body.len() < WORD_LEN + APP_NAME_LEN {
1014            return Err(Error::BufferTooShort {
1015                need: WORD_LEN + APP_NAME_LEN,
1016                have: body.len(),
1017                what: "RTCP APP ssrc+name",
1018            });
1019        }
1020        let ssrc = be_u32(body, 0, "APP ssrc")?;
1021        let mut name = [0u8; APP_NAME_LEN];
1022        name.copy_from_slice(&body[WORD_LEN..WORD_LEN + APP_NAME_LEN]);
1023        let data = body[WORD_LEN + APP_NAME_LEN..].to_vec();
1024        Ok(App {
1025            subtype: hdr.count,
1026            ssrc,
1027            name,
1028            data,
1029        })
1030    }
1031}
1032
1033impl Serialize for App {
1034    type Error = Error;
1035
1036    fn serialized_len(&self) -> usize {
1037        RTCP_HEADER_LEN + WORD_LEN + APP_NAME_LEN + self.data.len()
1038    }
1039
1040    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1041        let len = self.serialized_len();
1042        if buf.len() < len {
1043            return Err(Error::OutputBufferTooSmall {
1044                need: len,
1045                have: buf.len(),
1046            });
1047        }
1048        if len % WORD_LEN != 0 {
1049            return Err(Error::InvalidValue {
1050                field: "app_data_len",
1051                value: self.data.len() as u64,
1052                reason: "APP data must be 32-bit aligned",
1053            });
1054        }
1055        if self.subtype > RTCP_COUNT_MASK {
1056            return Err(Error::InvalidValue {
1057                field: "app_subtype",
1058                value: self.subtype as u64,
1059                reason: "exceeds 5-bit subtype field",
1060            });
1061        }
1062        let hdr = CommonHeader::new(self.subtype, PT_APP, length_words_minus_one(len));
1063        hdr.write(&mut buf[0..RTCP_HEADER_LEN]);
1064        let mut off = RTCP_HEADER_LEN;
1065        buf[off..off + 4].copy_from_slice(&self.ssrc.to_be_bytes());
1066        off += WORD_LEN;
1067        buf[off..off + APP_NAME_LEN].copy_from_slice(&self.name);
1068        off += APP_NAME_LEN;
1069        buf[off..off + self.data.len()].copy_from_slice(&self.data);
1070        Ok(len)
1071    }
1072}
1073
1074// ---------------------------------------------------------------------------
1075// RtcpPacket — the dispatch enum
1076// ---------------------------------------------------------------------------
1077
1078/// Any single RTCP packet, dispatched by its common-header `PT` byte
1079/// (RFC 3550 §6).
1080#[derive(Debug, Clone, PartialEq, Eq)]
1081#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1082#[non_exhaustive]
1083pub enum RtcpPacket {
1084    /// A Sender Report (PT 200).
1085    SenderReport(SenderReport),
1086    /// A Receiver Report (PT 201).
1087    ReceiverReport(ReceiverReport),
1088    /// A Source Description (PT 202).
1089    SourceDescription(SourceDescription),
1090    /// A Goodbye (PT 203).
1091    Bye(Bye),
1092    /// An Application-defined packet (PT 204).
1093    App(App),
1094}
1095
1096impl RtcpPacket {
1097    /// The packet type of this packet.
1098    pub fn packet_type(&self) -> RtcpPacketType {
1099        match self {
1100            RtcpPacket::SenderReport(_) => RtcpPacketType::SenderReport,
1101            RtcpPacket::ReceiverReport(_) => RtcpPacketType::ReceiverReport,
1102            RtcpPacket::SourceDescription(_) => RtcpPacketType::SourceDescription,
1103            RtcpPacket::Bye(_) => RtcpPacketType::Bye,
1104            RtcpPacket::App(_) => RtcpPacketType::App,
1105        }
1106    }
1107
1108    /// Spec token for this packet (`SR`/`RR`/`SDES`/`BYE`/`APP`).
1109    pub fn name(&self) -> &'static str {
1110        self.packet_type().name()
1111    }
1112
1113    /// Whether this packet is a report (SR or RR) — the only valid *first*
1114    /// packet of a compound packet (RFC 3550 §6.1).
1115    fn is_report(&self) -> bool {
1116        matches!(
1117            self,
1118            RtcpPacket::SenderReport(_) | RtcpPacket::ReceiverReport(_)
1119        )
1120    }
1121}
1122
1123broadcast_common::impl_spec_display!(RtcpPacket);
1124
1125impl<'a> Parse<'a> for RtcpPacket {
1126    type Error = Error;
1127
1128    fn parse(bytes: &'a [u8]) -> Result<Self> {
1129        let hdr = CommonHeader::parse(bytes)?;
1130        Ok(match RtcpPacketType::from_pt(hdr.packet_type) {
1131            RtcpPacketType::SenderReport => RtcpPacket::SenderReport(SenderReport::parse(bytes)?),
1132            RtcpPacketType::ReceiverReport => {
1133                RtcpPacket::ReceiverReport(ReceiverReport::parse(bytes)?)
1134            }
1135            RtcpPacketType::SourceDescription => {
1136                RtcpPacket::SourceDescription(SourceDescription::parse(bytes)?)
1137            }
1138            RtcpPacketType::Bye => RtcpPacket::Bye(Bye::parse(bytes)?),
1139            RtcpPacketType::App => RtcpPacket::App(App::parse(bytes)?),
1140            RtcpPacketType::Unknown(pt) => {
1141                return Err(Error::InvalidValue {
1142                    field: "rtcp_pt",
1143                    value: pt as u64,
1144                    reason: "not an RFC 3550 §6 core packet type",
1145                });
1146            }
1147        })
1148    }
1149}
1150
1151impl Serialize for RtcpPacket {
1152    type Error = Error;
1153
1154    fn serialized_len(&self) -> usize {
1155        match self {
1156            RtcpPacket::SenderReport(p) => p.serialized_len(),
1157            RtcpPacket::ReceiverReport(p) => p.serialized_len(),
1158            RtcpPacket::SourceDescription(p) => p.serialized_len(),
1159            RtcpPacket::Bye(p) => p.serialized_len(),
1160            RtcpPacket::App(p) => p.serialized_len(),
1161        }
1162    }
1163
1164    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1165        match self {
1166            RtcpPacket::SenderReport(p) => p.serialize_into(buf),
1167            RtcpPacket::ReceiverReport(p) => p.serialize_into(buf),
1168            RtcpPacket::SourceDescription(p) => p.serialize_into(buf),
1169            RtcpPacket::Bye(p) => p.serialize_into(buf),
1170            RtcpPacket::App(p) => p.serialize_into(buf),
1171        }
1172    }
1173}
1174
1175// ---------------------------------------------------------------------------
1176// CompoundPacket (RFC 3550 §6.1)
1177// ---------------------------------------------------------------------------
1178
1179/// A compound RTCP packet (RFC 3550 §6.1): a sequence of RTCP packets sent in a
1180/// single lower-layer datagram. The first packet **must** be a report (SR or
1181/// RR); this is validated on both parse and serialize.
1182#[derive(Debug, Clone, PartialEq, Eq)]
1183#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1184pub struct CompoundPacket {
1185    /// The constituent packets, in wire order (first is SR/RR).
1186    pub packets: Vec<RtcpPacket>,
1187}
1188
1189impl CompoundPacket {
1190    /// Build a compound packet, validating the §6.1 first-packet rule.
1191    pub fn new(packets: Vec<RtcpPacket>) -> Result<Self> {
1192        let cp = CompoundPacket { packets };
1193        cp.check_leading_report()?;
1194        Ok(cp)
1195    }
1196
1197    /// Enforce RFC 3550 §6.1: a compound packet must start with SR or RR.
1198    fn check_leading_report(&self) -> Result<()> {
1199        match self.packets.first() {
1200            Some(p) if p.is_report() => Ok(()),
1201            Some(_) => Err(Error::InvalidValue {
1202                field: "rtcp_compound",
1203                value: self.packets[0].packet_type().pt() as u64,
1204                reason: "compound packet must begin with SR or RR (RFC 3550 §6.1)",
1205            }),
1206            None => Err(Error::InvalidInput("empty RTCP compound packet")),
1207        }
1208    }
1209}
1210
1211impl<'a> Parse<'a> for CompoundPacket {
1212    type Error = Error;
1213
1214    fn parse(bytes: &'a [u8]) -> Result<Self> {
1215        let mut packets = Vec::new();
1216        let mut off = 0;
1217        while off < bytes.len() {
1218            let hdr = CommonHeader::parse(&bytes[off..])?;
1219            let total = hdr.total_len();
1220            let end = off + total;
1221            if end > bytes.len() {
1222                return Err(Error::BufferTooShort {
1223                    need: end,
1224                    have: bytes.len(),
1225                    what: "RTCP compound sub-packet",
1226                });
1227            }
1228            packets.push(RtcpPacket::parse(&bytes[off..end])?);
1229            off = end;
1230        }
1231        let cp = CompoundPacket { packets };
1232        cp.check_leading_report()?;
1233        Ok(cp)
1234    }
1235}
1236
1237impl Serialize for CompoundPacket {
1238    type Error = Error;
1239
1240    fn serialized_len(&self) -> usize {
1241        self.packets.iter().map(RtcpPacket::serialized_len).sum()
1242    }
1243
1244    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1245        self.check_leading_report()?;
1246        let len = self.serialized_len();
1247        if buf.len() < len {
1248            return Err(Error::OutputBufferTooSmall {
1249                need: len,
1250                have: buf.len(),
1251            });
1252        }
1253        let mut off = 0;
1254        for pkt in &self.packets {
1255            let n = pkt.serialize_into(&mut buf[off..])?;
1256            off += n;
1257        }
1258        Ok(off)
1259    }
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264    use super::*;
1265    use alloc::string::ToString;
1266    use alloc::vec;
1267
1268    fn sample_block(ssrc: u32, jitter: u32, cumulative: i32) -> ReportBlock {
1269        ReportBlock {
1270            ssrc,
1271            fraction_lost: 12,
1272            cumulative_lost: cumulative,
1273            ext_highest_seq: 0x0001_2345,
1274            jitter,
1275            lsr: 0xAABB_CCDD,
1276            dlsr: 0x0000_1000,
1277        }
1278    }
1279
1280    fn sample_sr() -> SenderReport {
1281        SenderReport {
1282            ssrc: 0x1122_3344,
1283            ntp_msw: 0xE0E1_E2E3,
1284            ntp_lsw: 0x1020_3040,
1285            rtp_timestamp: 0x0009_0000,
1286            packet_count: 4321,
1287            octet_count: 999_999,
1288            report_blocks: vec![
1289                sample_block(0xAAAA_AAAA, 500, 17),
1290                sample_block(0xBBBB_BBBB, 750, -3),
1291            ],
1292        }
1293    }
1294
1295    #[test]
1296    fn report_block_round_trip() {
1297        let b = sample_block(0xDEAD_BEEF, 4242, -5);
1298        let bytes = b.to_bytes();
1299        assert_eq!(bytes.len(), REPORT_BLOCK_LEN);
1300        let parsed = ReportBlock::parse(&bytes).unwrap();
1301        assert_eq!(parsed, b);
1302        assert_eq!(parsed.to_bytes(), bytes);
1303    }
1304
1305    #[test]
1306    fn report_block_negative_cumulative_lost() {
1307        // Negative cumulative lost must survive the 24-bit signed field.
1308        for v in [-1_i32, -2, -100, -0x80_0000, 0, 5, 0x7F_FFFF] {
1309            let b = sample_block(1, 0, v);
1310            let parsed = ReportBlock::parse(&b.to_bytes()).unwrap();
1311            assert_eq!(parsed.cumulative_lost, v, "cumulative_lost {v} round-trip");
1312        }
1313    }
1314
1315    #[test]
1316    fn sr_round_trip_and_header_layout() {
1317        let sr = sample_sr();
1318        let bytes = sr.to_bytes();
1319        // V=2 in top 2 bits, RC=2 in low 5 bits.
1320        assert_eq!(bytes[0] >> 6, 2);
1321        assert_eq!(bytes[0] & 0x1F, 2);
1322        // PT byte == 200.
1323        assert_eq!(bytes[1], PT_SENDER_REPORT);
1324        // length field == total_words − 1.
1325        let total_words = bytes.len() / 4;
1326        assert_eq!(
1327            u16::from_be_bytes([bytes[2], bytes[3]]) as usize,
1328            total_words - 1
1329        );
1330        let parsed = SenderReport::parse(&bytes).unwrap();
1331        assert_eq!(parsed, sr);
1332        assert_eq!(parsed.to_bytes(), bytes);
1333    }
1334
1335    #[test]
1336    fn sr_two_report_blocks_boundary() {
1337        let sr = sample_sr();
1338        assert_eq!(sr.report_blocks.len(), 2);
1339        let bytes = sr.to_bytes();
1340        // 4 (hdr) + 24 (sender info incl ssrc) + 2*24 = 76 bytes = 19 words.
1341        assert_eq!(bytes.len(), 4 + 24 + 2 * REPORT_BLOCK_LEN);
1342        assert_eq!(bytes.len() % 4, 0);
1343        let parsed = SenderReport::parse(&bytes).unwrap();
1344        assert_eq!(parsed.report_blocks.len(), 2);
1345        assert_eq!(
1346            u16::from_be_bytes([bytes[2], bytes[3]]) as usize,
1347            bytes.len() / 4 - 1
1348        );
1349    }
1350
1351    #[test]
1352    fn rr_round_trip() {
1353        let rr = ReceiverReport {
1354            ssrc: 0x0102_0304,
1355            report_blocks: vec![sample_block(0xCAFE_BABE, 33, -7)],
1356        };
1357        let bytes = rr.to_bytes();
1358        assert_eq!(bytes[1], PT_RECEIVER_REPORT);
1359        let parsed = ReceiverReport::parse(&bytes).unwrap();
1360        assert_eq!(parsed, rr);
1361        assert_eq!(parsed.to_bytes(), bytes);
1362    }
1363
1364    #[test]
1365    fn sdes_round_trip_cname_tool() {
1366        let sdes = SourceDescription {
1367            chunks: vec![SdesChunk {
1368                source: 0x1234_5678,
1369                items: vec![
1370                    SdesItem {
1371                        item_type: SdesItemType::CName,
1372                        text: "alice@example.com".to_string(),
1373                    },
1374                    SdesItem {
1375                        item_type: SdesItemType::Tool,
1376                        text: "transmux/1.0".to_string(),
1377                    },
1378                ],
1379            }],
1380        };
1381        let bytes = sdes.to_bytes();
1382        assert_eq!(bytes[1], PT_SOURCE_DESCRIPTION);
1383        assert_eq!(bytes.len() % 4, 0);
1384        let parsed = SourceDescription::parse(&bytes).unwrap();
1385        assert_eq!(parsed, sdes);
1386        assert_eq!(parsed.to_bytes(), bytes);
1387    }
1388
1389    #[test]
1390    fn bye_round_trip_with_reason() {
1391        let bye = Bye {
1392            sources: vec![0x1111_1111, 0x2222_2222],
1393            reason: Some("teardown".to_string()),
1394        };
1395        let bytes = bye.to_bytes();
1396        assert_eq!(bytes[1], PT_BYE);
1397        assert_eq!(bytes[0] & 0x1F, 2); // SC = 2
1398        assert_eq!(bytes.len() % 4, 0);
1399        let parsed = Bye::parse(&bytes).unwrap();
1400        assert_eq!(parsed, bye);
1401        assert_eq!(parsed.to_bytes(), bytes);
1402    }
1403
1404    #[test]
1405    fn bye_round_trip_no_reason() {
1406        let bye = Bye {
1407            sources: vec![0xABCD_0000],
1408            reason: None,
1409        };
1410        let parsed = Bye::parse(&bye.to_bytes()).unwrap();
1411        assert_eq!(parsed, bye);
1412    }
1413
1414    #[test]
1415    fn app_round_trip() {
1416        let app = App {
1417            subtype: 3,
1418            ssrc: 0x9988_7766,
1419            name: *b"TMUX",
1420            data: vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02, 0x03, 0x04],
1421        };
1422        let bytes = app.to_bytes();
1423        assert_eq!(bytes[1], PT_APP);
1424        assert_eq!(bytes[0] & 0x1F, 3); // subtype in RC field
1425        let parsed = App::parse(&bytes).unwrap();
1426        assert_eq!(parsed, app);
1427        assert_eq!(parsed.to_bytes(), bytes);
1428    }
1429
1430    #[test]
1431    fn sr_mutation_bites_packet_count() {
1432        let sr = sample_sr();
1433        let mut bytes = sr.to_bytes();
1434        let orig = SenderReport::parse(&bytes).unwrap();
1435        // packet_count lives at offset 4(hdr)+16 = 20.
1436        let pc_off = RTCP_HEADER_LEN + 16;
1437        bytes[pc_off] ^= 0xFF;
1438        let mutated = SenderReport::parse(&bytes).unwrap();
1439        assert_ne!(mutated.packet_count, orig.packet_count);
1440        // The mutated value re-serializes to the mutated bytes.
1441        assert_eq!(mutated.to_bytes(), bytes);
1442    }
1443
1444    #[test]
1445    fn report_block_mutation_bites_jitter() {
1446        let mut sr = sample_sr();
1447        sr.report_blocks[0].jitter = 500;
1448        let before = sr.to_bytes();
1449        sr.report_blocks[0].jitter = 999;
1450        let after = sr.to_bytes();
1451        assert_ne!(before, after);
1452        let parsed = SenderReport::parse(&after).unwrap();
1453        assert_eq!(parsed.report_blocks[0].jitter, 999);
1454    }
1455
1456    #[test]
1457    fn compound_sr_sdes_round_trip() {
1458        let sdes = SourceDescription {
1459            chunks: vec![SdesChunk {
1460                source: 0x1122_3344,
1461                items: vec![SdesItem {
1462                    item_type: SdesItemType::CName,
1463                    text: "cn".to_string(),
1464                }],
1465            }],
1466        };
1467        let cp = CompoundPacket::new(vec![
1468            RtcpPacket::SenderReport(sample_sr()),
1469            RtcpPacket::SourceDescription(sdes),
1470        ])
1471        .unwrap();
1472        let bytes = cp.to_bytes();
1473        let parsed = CompoundPacket::parse(&bytes).unwrap();
1474        assert_eq!(parsed.packets.len(), 2);
1475        assert_eq!(parsed, cp);
1476        assert_eq!(parsed.to_bytes(), bytes);
1477    }
1478
1479    #[test]
1480    fn compound_must_start_with_report() {
1481        // A BYE-first compound is rejected on construction.
1482        let err = CompoundPacket::new(vec![RtcpPacket::Bye(Bye {
1483            sources: vec![1],
1484            reason: None,
1485        })]);
1486        assert!(err.is_err());
1487        // And on parse: hand-build a BYE packet and try to parse as compound.
1488        let bye = Bye {
1489            sources: vec![1],
1490            reason: None,
1491        };
1492        let bytes = bye.to_bytes();
1493        assert!(CompoundPacket::parse(&bytes).is_err());
1494    }
1495
1496    #[test]
1497    fn any_packet_dispatch() {
1498        let bytes = sample_sr().to_bytes();
1499        let any = RtcpPacket::parse(&bytes).unwrap();
1500        assert_eq!(any.packet_type(), RtcpPacketType::SenderReport);
1501        assert_eq!(any.name(), "SR");
1502        assert_eq!(any.to_bytes(), bytes);
1503    }
1504
1505    #[test]
1506    fn packet_type_display() {
1507        assert_eq!(RtcpPacketType::SenderReport.to_string(), "SR");
1508        assert_eq!(RtcpPacketType::Unknown(207).to_string(), "reserved(0xCF)");
1509        assert_eq!(SdesItemType::CName.to_string(), "CNAME");
1510    }
1511
1512    #[test]
1513    fn rejects_bad_version() {
1514        let mut bytes = sample_sr().to_bytes();
1515        bytes[0] = 0x40; // V=1
1516        assert!(SenderReport::parse(&bytes).is_err());
1517    }
1518}