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