Skip to main content

oxideav_mpegts/
descriptor.rs

1//! Program / elementary-stream descriptor parser per ISO/IEC 13818-1
2//! §2.6 ("Program and program element descriptors").
3//!
4//! Every descriptor on the wire is a TLV record:
5//!
6//! ```text
7//! descriptor_tag    (8)
8//! descriptor_length (8) — count of bytes after this field
9//! descriptor data   (descriptor_length × 8)
10//! ```
11//!
12//! `descriptor_length == 0` is legal (header-only descriptor). A
13//! descriptor list lives either inside a PMT's `program_info` block
14//! (program-wide) or inside one PMT entry's `ES_info` block
15//! (per-elementary-stream). The list ends when the enclosing block's
16//! length budget is exhausted.
17//!
18//! ## Scope
19//!
20//! The parser walks the TLV envelope generically — every descriptor
21//! surfaces as a [`Descriptor`] borrowing its data slice from the
22//! source — and additionally decodes a small set of tags that the
23//! crate's downstream pipeline routinely needs to identify a stream:
24//!
25//! | Tag    | Name                              | Decoded variant                                |
26//! |--------|-----------------------------------|-----------------------------------------------|
27//! | `0x02` | video_stream_descriptor (§2.6.2)  | [`DescriptorBody::VideoStream`]              |
28//! | `0x03` | audio_stream_descriptor (§2.6.4)  | [`DescriptorBody::AudioStream`]              |
29//! | `0x04` | hierarchy_descriptor (§2.6.6)     | [`DescriptorBody::Hierarchy`]                |
30//! | `0x05` | registration_descriptor (§2.6.8)  | [`DescriptorBody::Registration`]             |
31//! | `0x06` | data_stream_alignment_descriptor (§2.6.10) | [`DescriptorBody::DataStreamAlignment`] |
32//! | `0x07` | target_background_grid_descriptor (§2.6.12) | [`DescriptorBody::TargetBackgroundGrid`] |
33//! | `0x08` | video_window_descriptor (§2.6.14) | [`DescriptorBody::VideoWindow`]              |
34//! | `0x09` | CA_descriptor (§2.6.16)           | [`DescriptorBody::Ca`]                       |
35//! | `0x0A` | ISO_639_language_descriptor (§2.6.18) | [`DescriptorBody::Iso639Language`]       |
36//! | `0x0B` | system_clock_descriptor (§2.6.20) | [`DescriptorBody::SystemClock`]              |
37//! | `0x0C` | multiplex_buffer_utilization_descriptor (§2.6.22) | [`DescriptorBody::MultiplexBufferUtilization`] |
38//! | `0x0D` | copyright_descriptor (§2.6.24)    | [`DescriptorBody::Copyright`]                |
39//! | `0x0E` | maximum_bitrate_descriptor (§2.6.26) | [`DescriptorBody::MaximumBitrate`]        |
40//! | `0x10` | smoothing_buffer_descriptor (§2.6.30) | [`DescriptorBody::SmoothingBuffer`]      |
41//! | `0x11` | STD_descriptor (§2.6.32)          | [`DescriptorBody::Std`]                      |
42//! | `0x12` | IBP_descriptor (§2.6.34)          | [`DescriptorBody::Ibp`]                      |
43//! | `0x28` | AVC_video_descriptor (§2.6.64)    | [`DescriptorBody::AvcVideo`]                 |
44//! | `0x38` | HEVC_video_descriptor (Amd. 3 §2.6.95) | [`DescriptorBody::HevcVideo`]           |
45//! | `0x48` | service_descriptor (EN 300 468 §6.2.33) | [`DescriptorBody::Service`]            |
46//! | `0x4D` | short_event_descriptor (EN 300 468 §6.2.37) | [`DescriptorBody::ShortEvent`]     |
47//!
48//! Every other tag is preserved as [`DescriptorBody::Raw`] so callers
49//! still see the payload bytes without losing information.
50
51use crate::TsError;
52
53/// One descriptor decoded from a descriptor list.
54#[derive(Debug, Clone)]
55pub struct Descriptor<'a> {
56    /// 8-bit descriptor tag (Table 2-45).
57    pub tag: u8,
58    /// Raw descriptor payload — exactly `descriptor_length` bytes.
59    pub data: &'a [u8],
60    /// Decoded body for the tags this module knows how to interpret,
61    /// or [`DescriptorBody::Raw`] for unrecognised tags.
62    pub body: DescriptorBody<'a>,
63}
64
65/// Typed body for descriptors this module decodes.
66#[derive(Debug, Clone)]
67pub enum DescriptorBody<'a> {
68    /// `0x02` video_stream_descriptor (§2.6.2).
69    VideoStream(VideoStreamDescriptor),
70    /// `0x03` audio_stream_descriptor (§2.6.4).
71    AudioStream(AudioStreamDescriptor),
72    /// `0x04` hierarchy_descriptor (§2.6.6) — links one program element
73    /// to its embedded base layer for hierarchically-coded video / audio
74    /// / private streams (Table 2-43).
75    Hierarchy(HierarchyDescriptor),
76    /// `0x05` registration_descriptor (§2.6.8). `format_identifier` is
77    /// a 4-byte ASCII code (e.g. `b"HDMV"` on Blu-ray) plus optional
78    /// additional bytes whose semantics are scoped to the FOURCC.
79    Registration {
80        format_identifier: [u8; 4],
81        additional_identification_info: &'a [u8],
82    },
83    /// `0x06` data_stream_alignment_descriptor (§2.6.10).
84    DataStreamAlignment(DataStreamAlignmentDescriptor),
85    /// `0x07` target_background_grid_descriptor (§2.6.12) — describes a
86    /// grid of unit pixels projected onto the display area, against which
87    /// a paired video_window_descriptor places the stream's window
88    /// (Table 2-49).
89    TargetBackgroundGrid(TargetBackgroundGridDescriptor),
90    /// `0x08` video_window_descriptor (§2.6.14) — places the stream's
91    /// display window on the grid defined by its paired
92    /// target_background_grid_descriptor (Table 2-50).
93    VideoWindow(VideoWindowDescriptor),
94    /// `0x09` CA_descriptor (§2.6.16) — Conditional Access PID + system.
95    Ca(CaDescriptor<'a>),
96    /// `0x0A` ISO_639_language_descriptor (§2.6.18).
97    Iso639Language(Vec<Iso639Language>),
98    /// `0x0B` system_clock_descriptor (§2.6.20).
99    SystemClock(SystemClockDescriptor),
100    /// `0x0C` multiplex_buffer_utilization_descriptor (§2.6.22).
101    MultiplexBufferUtilization(MultiplexBufferUtilizationDescriptor),
102    /// `0x0D` copyright_descriptor (§2.6.24).
103    Copyright(CopyrightDescriptor<'a>),
104    /// `0x0E` maximum_bitrate_descriptor (§2.6.26).
105    MaximumBitrate(MaximumBitrateDescriptor),
106    /// `0x10` smoothing_buffer_descriptor (§2.6.30).
107    SmoothingBuffer(SmoothingBufferDescriptor),
108    /// `0x11` STD_descriptor (§2.6.32).
109    Std(StdDescriptor),
110    /// `0x12` IBP_descriptor (§2.6.34) — GOP-structure hints for the
111    /// associated video elementary stream (Table 2-61).
112    Ibp(IbpDescriptor),
113    /// `0x28` AVC_video_descriptor (§2.6.64).
114    AvcVideo(AvcVideoDescriptor),
115    /// `0x38` HEVC_video_descriptor (Amd. 3 §2.6.95).
116    HevcVideo(HevcVideoDescriptor),
117    /// `0x48` service_descriptor — DVB SI extension (ETSI EN 300 468
118    /// §6.2.33 Table 88). Carried in the SDT service loop; names the
119    /// service plus its provider in text form.
120    Service(ServiceDescriptor<'a>),
121    /// `0x4D` short_event_descriptor — DVB SI extension (ETSI EN 300 468
122    /// §6.2.37 Table 93). Carried in the EIT event loop; names the event
123    /// plus a short text description, both tagged with a language code.
124    ShortEvent(ShortEventDescriptor<'a>),
125    /// Unrecognised tag — payload bytes preserved verbatim.
126    Raw,
127}
128
129/// service_descriptor body (ETSI EN 300 468 §6.2.33 Table 88).
130///
131/// This is a DVB Service Information extension to the ISO/IEC 13818-1
132/// descriptor space, carried inside the per-service descriptor loop of
133/// a [`crate::psi::ServiceDescriptionTable`] section. It pairs the
134/// service's textual provider name and service name with an 8-bit
135/// `service_type` (Table 89).
136///
137/// The two name fields are exposed as **raw byte slices** rather than
138/// decoded strings: DVB text strings (EN 300 468 annex A) carry an
139/// optional leading character-table selector byte and may use any of
140/// several code tables, so charset interpretation is deliberately left
141/// to the caller. The bytes are exactly the `service_provider_name`
142/// and `service_name` runs as they appear on the wire.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct ServiceDescriptor<'a> {
145    /// 8-bit `service_type` (Table 89). Common values: `0x01` digital
146    /// television, `0x02` digital radio, `0x11` HD digital television,
147    /// `0x19` H.264/AVC HD, `0x1F` HEVC digital television.
148    pub service_type: u8,
149    /// Raw `service_provider_name` bytes (DVB text string, annex A).
150    pub service_provider_name: &'a [u8],
151    /// Raw `service_name` bytes (DVB text string, annex A).
152    pub service_name: &'a [u8],
153}
154
155/// short_event_descriptor body (ETSI EN 300 468 §6.2.37 Table 93).
156///
157/// A DVB Service Information extension to the ISO/IEC 13818-1 descriptor
158/// space, carried inside the per-event descriptor loop of an
159/// [`crate::psi::EventInformationTable`] section. It pairs the event's
160/// name with a short free-text description, both tagged by the same
161/// 3-character ISO 639-2 language code.
162///
163/// Like the `service_descriptor`, the `event_name` and `text` fields
164/// are exposed as **raw byte slices** rather than decoded strings: DVB
165/// text strings (EN 300 468 annex A) carry an optional leading
166/// character-table selector byte and may use any of several code
167/// tables, so charset interpretation is deliberately left to the
168/// caller. The bytes are exactly the `event_name` and `text` runs as
169/// they appear on the wire.
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct ShortEventDescriptor<'a> {
172    /// 24-bit `ISO_639_language_code` — three ISO 639-2 characters, each
173    /// coded as one ISO/IEC 8859-1 byte (e.g. `b"eng"`, `b"fre"`).
174    pub language_code: [u8; 3],
175    /// Raw `event_name` bytes (DVB text string, annex A).
176    pub event_name: &'a [u8],
177    /// Raw `text` bytes — the short event description (DVB text string,
178    /// annex A).
179    pub text: &'a [u8],
180}
181
182/// video_stream_descriptor body (§2.6.2 Table 2-46).
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub struct VideoStreamDescriptor {
185    /// `multiple_frame_rate_flag`.
186    pub multiple_frame_rate_flag: bool,
187    /// 4-bit `frame_rate_code` (Table 6-4 of ISO/IEC 13818-2).
188    pub frame_rate_code: u8,
189    /// `MPEG_1_only_flag` — when set, no MPEG-2 extension byte follows.
190    pub mpeg_1_only_flag: bool,
191    /// `constrained_parameter_flag`.
192    pub constrained_parameter_flag: bool,
193    /// `still_picture_flag`.
194    pub still_picture_flag: bool,
195    /// `profile_and_level_indication`, when `mpeg_1_only_flag = 0`.
196    pub profile_and_level_indication: Option<u8>,
197    /// 2-bit `chroma_format` (00=reserved, 01=4:2:0, 10=4:2:2, 11=4:4:4).
198    pub chroma_format: Option<u8>,
199    /// `frame_rate_extension_flag`.
200    pub frame_rate_extension_flag: Option<bool>,
201}
202
203/// audio_stream_descriptor body (§2.6.4 Table 2-47).
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub struct AudioStreamDescriptor {
206    /// `free_format_flag` — `1` when the bitrate is "free format".
207    pub free_format_flag: bool,
208    /// `ID` — `1` indicates ISO/IEC 11172-3 (MPEG-1) audio, `0`
209    /// indicates ISO/IEC 13818-3 (MPEG-2) audio extension.
210    pub id: bool,
211    /// 2-bit `layer` (00=reserved, 01=III, 10=II, 11=I).
212    pub layer: u8,
213    /// `variable_rate_audio_indicator`.
214    pub variable_rate_audio_indicator: bool,
215}
216
217/// hierarchy_descriptor body (§2.6.6 Table 2-43).
218///
219/// The descriptor labels one PMT entry as a layer in a hierarchically
220/// coded program — most commonly a video stream split into a base layer
221/// plus one or more enhancement layers (spatial / SNR / temporal /
222/// data-partitioning scalability per ITU-T H.262 | ISO/IEC 13818-2),
223/// but the framing also covers ISO/IEC 13818-3 audio extensions and
224/// 13818-1 private streams.
225///
226/// Wire payload (Table 2-43): exactly four bytes, two reserved bits
227/// then six payload bits in each. `hierarchy_layer_index` is a unique
228/// 6-bit index for this layer within the program;
229/// `hierarchy_embedded_layer_index` points at the layer this one
230/// depends on (undefined when `hierarchy_type == HierarchyType::BaseLayer`
231/// per §2.6.7); `hierarchy_channel` is the intended transmission
232/// channel rank — lower values are the more robust channel.
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub struct HierarchyDescriptor {
235    /// Typed `hierarchy_type` (Table 2-44). The wire value is preserved
236    /// in the [`HierarchyType::Reserved`] / [`HierarchyType::Unknown`]
237    /// variants for callers that need the raw nibble.
238    pub hierarchy_type: HierarchyType,
239    /// 6-bit `hierarchy_layer_index` — unique index of this layer in
240    /// the program's hierarchy table.
241    pub hierarchy_layer_index: u8,
242    /// 6-bit `hierarchy_embedded_layer_index` — index of the layer this
243    /// one is built on top of. Per §2.6.7 the value is **undefined**
244    /// when `hierarchy_type == HierarchyType::BaseLayer`; callers should
245    /// ignore it in that case rather than relying on the raw bits.
246    pub hierarchy_embedded_layer_index: u8,
247    /// 6-bit `hierarchy_channel` — the transmission channel number this
248    /// layer is intended to be carried on. The most robust channel is
249    /// the lowest value (§2.6.7).
250    pub hierarchy_channel: u8,
251}
252
253/// `hierarchy_type` values per Table 2-44.
254///
255/// The wire field is 4 bits; values 0 and 8..14 are reserved by the
256/// spec and surface as [`HierarchyType::Reserved`] preserving the raw
257/// nibble. Values outside the 0..=15 range cannot appear on the wire
258/// (the field is only four bits) but the parser still surfaces them as
259/// [`HierarchyType::Unknown`] for defensive coding.
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum HierarchyType {
262    /// `1` — ITU-T Rec. H.262 | ISO/IEC 13818-2 spatial scalability.
263    Mpeg2SpatialScalability,
264    /// `2` — ITU-T Rec. H.262 | ISO/IEC 13818-2 SNR scalability.
265    Mpeg2SnrScalability,
266    /// `3` — ITU-T Rec. H.262 | ISO/IEC 13818-2 temporal scalability.
267    Mpeg2TemporalScalability,
268    /// `4` — ITU-T Rec. H.262 | ISO/IEC 13818-2 data partitioning.
269    Mpeg2DataPartitioning,
270    /// `5` — ISO/IEC 13818-3 extension bitstream.
271    Mpeg2AudioExtension,
272    /// `6` — ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private stream.
273    PrivateStream,
274    /// `7` — ITU-T Rec. H.262 | ISO/IEC 13818-2 multi-view profile.
275    Mpeg2MultiviewProfile,
276    /// `15` — Base layer; per §2.6.7 the embedded-layer-index field is
277    /// undefined when this variant is set.
278    BaseLayer,
279    /// `0` and `8..=14` — reserved by Table 2-44. The raw 4-bit nibble
280    /// is preserved verbatim.
281    Reserved(u8),
282    /// Out-of-range value (cannot occur on a conformant 4-bit wire
283    /// field; surfaces here only if a future caller constructs the
284    /// struct directly).
285    Unknown(u8),
286}
287
288impl HierarchyType {
289    /// Decode a 4-bit `hierarchy_type` nibble (only the low four bits
290    /// are examined; the high four bits, if any, are ignored).
291    pub fn from_nibble(value: u8) -> Self {
292        match value & 0x0F {
293            1 => HierarchyType::Mpeg2SpatialScalability,
294            2 => HierarchyType::Mpeg2SnrScalability,
295            3 => HierarchyType::Mpeg2TemporalScalability,
296            4 => HierarchyType::Mpeg2DataPartitioning,
297            5 => HierarchyType::Mpeg2AudioExtension,
298            6 => HierarchyType::PrivateStream,
299            7 => HierarchyType::Mpeg2MultiviewProfile,
300            15 => HierarchyType::BaseLayer,
301            other => HierarchyType::Reserved(other),
302        }
303    }
304
305    /// Raw 4-bit value as it would appear on the wire. For
306    /// [`HierarchyType::Unknown`] the stored value is returned verbatim
307    /// (and may exceed 0xF, indicating an internally-constructed
308    /// non-wire value).
309    pub fn as_nibble(self) -> u8 {
310        match self {
311            HierarchyType::Mpeg2SpatialScalability => 1,
312            HierarchyType::Mpeg2SnrScalability => 2,
313            HierarchyType::Mpeg2TemporalScalability => 3,
314            HierarchyType::Mpeg2DataPartitioning => 4,
315            HierarchyType::Mpeg2AudioExtension => 5,
316            HierarchyType::PrivateStream => 6,
317            HierarchyType::Mpeg2MultiviewProfile => 7,
318            HierarchyType::BaseLayer => 15,
319            HierarchyType::Reserved(v) => v & 0x0F,
320            HierarchyType::Unknown(v) => v,
321        }
322    }
323
324    /// `true` when this variant is the §2.6.7 base layer; the
325    /// `hierarchy_embedded_layer_index` field is undefined in that case.
326    pub fn is_base_layer(self) -> bool {
327        matches!(self, HierarchyType::BaseLayer)
328    }
329}
330
331/// CA_descriptor body (§2.6.16 Table 2-50).
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub struct CaDescriptor<'a> {
334    /// `CA_system_ID`.
335    pub ca_system_id: u16,
336    /// 13-bit `CA_PID` carrying the ECM/EMM stream.
337    pub ca_pid: u16,
338    /// `private_data_bytes` — payload bytes after the PID.
339    pub private_data: &'a [u8],
340}
341
342/// One language entry from an ISO_639_language_descriptor (§2.6.18).
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub struct Iso639Language {
345    /// 3-byte ISO 639-2 language code (typically lowercase ASCII).
346    pub language: [u8; 3],
347    /// `audio_type` — `0x00` undefined, `0x01` clean effects,
348    /// `0x02` hearing impaired, `0x03` visual impaired commentary.
349    pub audio_type: u8,
350}
351
352/// AVC_video_descriptor body (§2.6.64 Table 2-71).
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub struct AvcVideoDescriptor {
355    /// `profile_idc`.
356    pub profile_idc: u8,
357    /// Compound flags byte (`constraint_set0..2_flag` + 5 bits of
358    /// `AVC_compatible_flags`).
359    pub constraint_set_and_compat: u8,
360    /// `level_idc`.
361    pub level_idc: u8,
362    /// `AVC_still_present`.
363    pub avc_still_present: bool,
364    /// `AVC_24_hour_picture_flag`.
365    pub avc_24_hour_picture_flag: bool,
366    /// `frame_packing_SEI_not_present_flag` (introduced in
367    /// Amendment 1).
368    pub frame_packing_sei_not_present_flag: bool,
369}
370
371/// data_stream_alignment_descriptor body (§2.6.10 Table 2-46).
372///
373/// `alignment_type` semantics depend on whether the referenced ES is
374/// video (Table 2-47) or audio (Table 2-48):
375///
376/// | Value | Video                       | Audio       |
377/// |-------|-----------------------------|-------------|
378/// | 0x00  | Reserved                    | Reserved    |
379/// | 0x01  | Slice, or video access unit | Sync word   |
380/// | 0x02  | Video access unit           | (Reserved)  |
381/// | 0x03  | GOP, or SEQ                 | (Reserved)  |
382/// | 0x04  | SEQ                         | (Reserved)  |
383/// | 0x05..0xFF | Reserved               | Reserved    |
384///
385/// The descriptor itself doesn't carry the video/audio distinction; the
386/// caller selects the table based on the enclosing PMT entry's
387/// `stream_type`.
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub struct DataStreamAlignmentDescriptor {
390    /// Raw 8-bit `alignment_type` byte (Tables 2-47 / 2-48).
391    pub alignment_type: u8,
392}
393
394/// target_background_grid_descriptor body (§2.6.12 Table 2-49).
395///
396/// Describes a grid of unit pixels projected onto the display area
397/// (e.g. a monitor) for a video stream that is not intended to occupy
398/// the full display. A paired video_window_descriptor (§2.6.14) then
399/// places the stream's window on this grid. Per §2.6.13:
400///
401/// * `horizontal_size` — horizontal size of the target background grid
402///   in pixels.
403/// * `vertical_size` — vertical size of the target background grid in
404///   pixels.
405/// * `aspect_ratio_information` — sample or display aspect ratio of the
406///   grid, encoded per the `aspect_ratio_information` field of
407///   ITU-T Rec. H.262 | ISO/IEC 13818-2 (Table 2-49 cross-reference).
408///   Surfaced as the raw 4-bit code; this crate does not decode the
409///   H.262 table.
410///
411/// On the wire the body is exactly 4 bytes (Table 2-49):
412///
413/// ```text
414/// byte 0: horizontal_size bits 13..6                      (8)
415/// byte 1: horizontal_size bits 5..0 (6) | vertical_size bits 13..12 (2)
416/// byte 2: vertical_size bits 11..4                        (8)
417/// byte 3: vertical_size bits 3..0 (4) | aspect_ratio_information (4)
418/// ```
419///
420/// Both size fields are big-endian 14-bit unsigned values; the table
421/// carries no reserved bits, so all 32 bits are payload.
422#[derive(Debug, Clone, Copy, PartialEq, Eq)]
423pub struct TargetBackgroundGridDescriptor {
424    /// `horizontal_size` (14 bits) — grid width in pixels.
425    pub horizontal_size: u16,
426    /// `vertical_size` (14 bits) — grid height in pixels.
427    pub vertical_size: u16,
428    /// `aspect_ratio_information` (4 bits) — raw H.262 aspect-ratio code.
429    pub aspect_ratio_information: u8,
430}
431
432/// video_window_descriptor body (§2.6.14 Table 2-50).
433///
434/// Describes the window characteristics of the associated video
435/// elementary stream; its offsets reference the grid established by the
436/// stream's paired target_background_grid_descriptor (§2.6.12). Per
437/// §2.6.15:
438///
439/// * `horizontal_offset` — horizontal position of the top-left pixel of
440///   the video display window (or display rectangle) on the target
441///   background grid. The top-left pixel shall coincide with a grid
442///   pixel.
443/// * `vertical_offset` — vertical position of the same top-left pixel on
444///   the grid.
445/// * `window_priority` — overlap order, `0` lowest through `15` highest;
446///   windows at priority `15` are always visible.
447///
448/// On the wire the body is exactly 4 bytes (Table 2-50), laid out
449/// identically to the target_background_grid_descriptor's first three
450/// fields:
451///
452/// ```text
453/// byte 0: horizontal_offset bits 13..6                     (8)
454/// byte 1: horizontal_offset bits 5..0 (6) | vertical_offset bits 13..12 (2)
455/// byte 2: vertical_offset bits 11..4                       (8)
456/// byte 3: vertical_offset bits 3..0 (4) | window_priority  (4)
457/// ```
458///
459/// Both offset fields are big-endian 14-bit unsigned values; the table
460/// carries no reserved bits, so all 32 bits are payload.
461#[derive(Debug, Clone, Copy, PartialEq, Eq)]
462pub struct VideoWindowDescriptor {
463    /// `horizontal_offset` (14 bits) — top-left X on the grid, in pixels.
464    pub horizontal_offset: u16,
465    /// `vertical_offset` (14 bits) — top-left Y on the grid, in pixels.
466    pub vertical_offset: u16,
467    /// `window_priority` (4 bits) — overlap order, `0` lowest, `15`
468    /// highest (always visible).
469    pub window_priority: u8,
470}
471
472/// system_clock_descriptor body (§2.6.20 Table 2-54).
473///
474/// `clock_accuracy_integer × 10^(-clock_accuracy_exponent)` gives the
475/// fractional frequency accuracy of the program clock in parts per
476/// million (§2.6.21). When `clock_accuracy_integer == 0` the spec
477/// defines the accuracy as 30 ppm.
478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479pub struct SystemClockDescriptor {
480    /// `external_clock_reference_indicator` — when `true`, the clock
481    /// is derived from an external frequency reference at the decoder.
482    pub external_clock_reference: bool,
483    /// 6-bit `clock_accuracy_integer`.
484    pub clock_accuracy_integer: u8,
485    /// 3-bit `clock_accuracy_exponent`.
486    pub clock_accuracy_exponent: u8,
487}
488
489/// copyright_descriptor body (§2.6.24 Table 2-56).
490///
491/// Surfaces the 32-bit `copyright_identifier` issued by the §2.9
492/// Registration Authority plus the trailing `additional_copyright_info`
493/// bytes. Per §2.6.25 the meaning of those trailing bytes is scoped to
494/// the assignee of the `copyright_identifier` and stays opaque to this
495/// crate. A descriptor body shorter than 4 bytes (the fixed
496/// `copyright_identifier` field) falls back to
497/// [`DescriptorBody::Raw`].
498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
499pub struct CopyrightDescriptor<'a> {
500    /// 32-bit `copyright_identifier` value obtained from the §2.9
501    /// Registration Authority.
502    pub copyright_identifier: u32,
503    /// `additional_copyright_info` — descriptor bytes after the
504    /// fixed-size identifier. May be empty.
505    pub additional_copyright_info: &'a [u8],
506}
507
508/// maximum_bitrate_descriptor body (§2.6.26 Table 2-57).
509///
510/// `maximum_bitrate` is a 22-bit field in units of 50 bytes/s (§2.6.27).
511/// Multiply by 50 to get bytes/s, or by 400 to get bits/s.
512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
513pub struct MaximumBitrateDescriptor {
514    /// Raw 22-bit `maximum_bitrate` value (units of 50 bytes/s).
515    pub maximum_bitrate: u32,
516}
517
518impl MaximumBitrateDescriptor {
519    /// Convert to bits/s. `maximum_bitrate` units are 50 bytes/s, i.e.
520    /// 400 bits/s, so the conversion is a multiply by 400 (which fits
521    /// in `u64` for the full 22-bit range).
522    pub fn bits_per_second(self) -> u64 {
523        (self.maximum_bitrate as u64) * 400
524    }
525}
526
527/// smoothing_buffer_descriptor body (§2.6.30 Table 2-59).
528///
529/// Optional PMT descriptor that declares the size of a smoothing
530/// buffer SBn and the rate at which bytes leak out of it for the
531/// program element(s) the descriptor refers to. Per §2.6.31:
532///
533/// * `sb_leak_rate` is a 22-bit value in units of 400 bits/s — i.e.
534///   multiply by 400 to recover bits/s (multiply by 50 to recover
535///   bytes/s).
536/// * `sb_size` is a 22-bit value in units of 1 byte and gives the
537///   capacity of SBn directly.
538///
539/// On the wire (§2.6.30 Table 2-59) the body is exactly 6 bytes:
540/// two reserved bits then the 22-bit leak rate, then two reserved
541/// bits then the 22-bit buffer size, both big-endian. The descriptor
542/// is only defined inside a PMT or Program Stream Map; an SBn that
543/// would overflow is a spec violation, not something this typed view
544/// enforces.
545#[derive(Debug, Clone, Copy, PartialEq, Eq)]
546pub struct SmoothingBufferDescriptor {
547    /// Raw 22-bit `sb_leak_rate` value (units of 400 bits/s — i.e.
548    /// the same 50 bytes/s units as `maximum_bitrate_descriptor`).
549    pub sb_leak_rate: u32,
550    /// Raw 22-bit `sb_size` value (units of 1 byte; this is the
551    /// buffer capacity in bytes already).
552    pub sb_size: u32,
553}
554
555impl SmoothingBufferDescriptor {
556    /// Convert `sb_leak_rate` to bits per second. The wire field is in
557    /// units of 400 bits/s, so the conversion is a multiply by 400
558    /// (fits in `u64` for the full 22-bit range).
559    pub fn leak_rate_bits_per_second(self) -> u64 {
560        (self.sb_leak_rate as u64) * 400
561    }
562
563    /// Convert `sb_leak_rate` to bytes per second (the same 50 bytes/s
564    /// quanta `maximum_bitrate_descriptor` uses).
565    pub fn leak_rate_bytes_per_second(self) -> u64 {
566        (self.sb_leak_rate as u64) * 50
567    }
568
569    /// `sb_size` in bytes — the field's wire units are already 1 byte
570    /// per count, so this is just a widened copy for symmetry with the
571    /// leak-rate helpers.
572    pub fn buffer_size_bytes(self) -> u64 {
573        self.sb_size as u64
574    }
575}
576
577/// STD_descriptor body (§2.6.32 Table 2-60).
578///
579/// When `leak_valid_flag == true`, the transfer of data from buffer
580/// MBn to EBn in the T-STD uses the leak method defined in §2.4.2.3;
581/// when `false`, the transfer uses the `vbv_delay` method (provided
582/// the per-frame `vbv_delay` is not `0xFFFF`).
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub struct StdDescriptor {
585    /// `leak_valid_flag` — 1 bit.
586    pub leak_valid_flag: bool,
587}
588
589/// multiplex_buffer_utilization_descriptor body (§2.6.22 Table 2-55).
590///
591/// An optional PMT descriptor that bounds the LTW (legal time window)
592/// offset values a re-multiplexer can expect to see for the program
593/// element(s) the descriptor refers to. Per §2.6.23:
594///
595/// * `bound_valid_flag = 0` marks the two bound fields as undefined —
596///   callers must ignore them in that case.
597/// * `bound_valid_flag = 1` makes both bounds valid. Each bound is in
598///   units of `(27 MHz / 300) = 90 kHz` clock periods (the LTW_offset
599///   unit defined in §2.4.3.4) and bounds the value any future
600///   `ltw_offset` field would carry on this stream until the next
601///   occurrence of this descriptor.
602///
603/// On the wire the body is exactly 4 bytes:
604///
605/// ```text
606/// byte 0: bound_valid_flag (1) | LTW_offset_lower_bound bits 14..8  (7)
607/// byte 1: LTW_offset_lower_bound bits 7..0                          (8)
608/// byte 2: reserved          (1) | LTW_offset_upper_bound bits 14..8 (7)
609/// byte 3: LTW_offset_upper_bound bits 7..0                          (8)
610/// ```
611///
612/// **Spec note.** The §2.6.22 Table 2-55 syntax row labels
613/// `LTW_offset_upper_bound` as a 14-bit field, but the surrounding
614/// semantic text (§2.6.23) describes it as a 15-bit field — the same
615/// width as the adaptation-field `ltw_offset` in §2.4.3.4 / Table 2-7.
616/// Together with the fixed reserved bit before the upper bound, the
617/// 15-bit width is the only width that lets the body sum to a clean
618/// four-byte boundary (`1 + 15 + 1 + 15 = 32`). This parser follows the
619/// 15-bit reading because it matches both the semantic text and the
620/// adaptation-field source field; a future spec corrigendum is the
621/// expected path to close the typo.
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623pub struct MultiplexBufferUtilizationDescriptor {
624    /// `bound_valid_flag` — when `false`, the two bound fields are
625    /// undefined and callers should ignore them.
626    pub bound_valid_flag: bool,
627    /// Raw 15-bit `LTW_offset_lower_bound` value (units of 90 kHz clock
628    /// periods). Meaningful only when `bound_valid_flag == true`.
629    pub ltw_offset_lower_bound: u16,
630    /// Raw 15-bit `LTW_offset_upper_bound` value (units of 90 kHz clock
631    /// periods). Meaningful only when `bound_valid_flag == true`.
632    pub ltw_offset_upper_bound: u16,
633}
634
635/// IBP_descriptor body (§2.6.34 Table 2-61).
636///
637/// Optional PMT-resident descriptor that summarises the GOP structure
638/// of the associated video elementary stream. Two boolean hints plus a
639/// 14-bit GOP-length cap let a downstream consumer pre-allocate
640/// reorder buffers and seek-index slots without first scanning the
641/// elementary stream.
642///
643/// On the wire the body is exactly 2 bytes:
644///
645/// ```text
646/// byte 0: closed_gop_flag    (1)
647///       | identical_gop_flag (1)
648///       | max_gop_length bits 13..8 (6)
649/// byte 1: max_gop_length bits 7..0   (8)
650/// ```
651///
652/// Per §2.6.35:
653///
654/// * `closed_gop_flag = 1` — a GOP header is encoded before every
655///   I-frame and every such GOP carries `closed_gop = 1`.
656/// * `identical_gop_flag = 1` — the picture-type sequence between any
657///   two I-pictures is identical throughout the stream (except possibly
658///   for pictures up to the second I-picture).
659/// * `max_gop_length` — maximum number of coded pictures between any
660///   two consecutive I-pictures in the sequence. The value `0` is
661///   forbidden by the spec; [`Self::is_well_formed`] flags that.
662#[derive(Debug, Clone, Copy, PartialEq, Eq)]
663pub struct IbpDescriptor {
664    /// `closed_gop_flag` (1 bit).
665    pub closed_gop_flag: bool,
666    /// `identical_gop_flag` (1 bit).
667    pub identical_gop_flag: bool,
668    /// `max_gop_length` (14 bits). Spec forbids `0`; see
669    /// [`Self::is_well_formed`].
670    pub max_gop_length: u16,
671}
672
673impl IbpDescriptor {
674    /// `true` when `max_gop_length` carries a spec-legal non-zero value.
675    /// `false` flags the §2.6.35 "value of 0 is forbidden" condition,
676    /// which the parser still surfaces so callers can detect malformed
677    /// streams rather than silently substituting a sentinel.
678    pub fn is_well_formed(self) -> bool {
679        self.max_gop_length != 0
680    }
681}
682
683/// HEVC_video_descriptor body (Amd. 3 §2.6.95 Table 2-96).
684#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685pub struct HevcVideoDescriptor {
686    /// `profile_space`, 2 bits.
687    pub profile_space: u8,
688    /// `tier_flag`.
689    pub tier_flag: bool,
690    /// `profile_idc`, 5 bits.
691    pub profile_idc: u8,
692    /// `profile_compatibility_indication` (32 bits).
693    pub profile_compatibility_indication: u32,
694    /// `progressive_source_flag`.
695    pub progressive_source_flag: bool,
696    /// `interlaced_source_flag`.
697    pub interlaced_source_flag: bool,
698    /// `non_packed_constraint_flag`.
699    pub non_packed_constraint_flag: bool,
700    /// `frame_only_constraint_flag`.
701    pub frame_only_constraint_flag: bool,
702    /// `level_idc`.
703    pub level_idc: u8,
704    /// `temporal_layer_subset_flag`.
705    pub temporal_layer_subset_flag: bool,
706    /// `HEVC_still_present_flag`.
707    pub hevc_still_present_flag: bool,
708    /// `HEVC_24hr_picture_present_flag`.
709    pub hevc_24hr_picture_present_flag: bool,
710    /// `temporal_id_min` (3 bits), populated only when
711    /// `temporal_layer_subset_flag = 1`.
712    pub temporal_id_min: Option<u8>,
713    /// `temporal_id_max` (3 bits), populated only when
714    /// `temporal_layer_subset_flag = 1`.
715    pub temporal_id_max: Option<u8>,
716}
717
718/// Iterate the descriptor TLV records carried inside `block`.
719///
720/// `block` is the exact slice that follows a `_info_length` field —
721/// typically `pmt.program_info` or `pmt_stream.descriptors`. The
722/// iterator stops cleanly once the slice is exhausted; a truncated
723/// trailer (a descriptor header that claims more bytes than remain)
724/// surfaces as [`TsError::Truncated`] on the next `next()` call.
725pub fn iter_descriptors(block: &[u8]) -> DescriptorIter<'_> {
726    DescriptorIter { rest: block }
727}
728
729/// Iterator returned by [`iter_descriptors`].
730#[derive(Debug)]
731pub struct DescriptorIter<'a> {
732    rest: &'a [u8],
733}
734
735impl<'a> Iterator for DescriptorIter<'a> {
736    type Item = Result<Descriptor<'a>, TsError>;
737
738    fn next(&mut self) -> Option<Self::Item> {
739        if self.rest.is_empty() {
740            return None;
741        }
742        if self.rest.len() < 2 {
743            let have = self.rest.len();
744            self.rest = &[][..];
745            return Some(Err(TsError::Truncated {
746                what: "descriptor header",
747                have,
748                need: 2,
749            }));
750        }
751        let tag = self.rest[0];
752        let len = self.rest[1] as usize;
753        if self.rest.len() < 2 + len {
754            let have = self.rest.len() - 2;
755            self.rest = &[][..];
756            return Some(Err(TsError::Truncated {
757                what: "descriptor body",
758                have,
759                need: len,
760            }));
761        }
762        let data = &self.rest[2..2 + len];
763        self.rest = &self.rest[2 + len..];
764        Some(Ok(Descriptor {
765            tag,
766            data,
767            body: decode_body(tag, data),
768        }))
769    }
770}
771
772/// Collect every descriptor in `block` into a `Vec`, returning the
773/// first parse error if any.
774pub fn parse_descriptors(block: &[u8]) -> Result<Vec<Descriptor<'_>>, TsError> {
775    iter_descriptors(block).collect()
776}
777
778fn decode_body<'a>(tag: u8, data: &'a [u8]) -> DescriptorBody<'a> {
779    match tag {
780        0x02 => decode_video_stream(data).unwrap_or(DescriptorBody::Raw),
781        0x03 => decode_audio_stream(data).unwrap_or(DescriptorBody::Raw),
782        0x04 => decode_hierarchy(data).unwrap_or(DescriptorBody::Raw),
783        0x05 => decode_registration(data).unwrap_or(DescriptorBody::Raw),
784        0x06 => decode_data_stream_alignment(data).unwrap_or(DescriptorBody::Raw),
785        0x07 => decode_target_background_grid(data).unwrap_or(DescriptorBody::Raw),
786        0x08 => decode_video_window(data).unwrap_or(DescriptorBody::Raw),
787        0x09 => decode_ca(data).unwrap_or(DescriptorBody::Raw),
788        0x0A => decode_iso639_language(data).unwrap_or(DescriptorBody::Raw),
789        0x0B => decode_system_clock(data).unwrap_or(DescriptorBody::Raw),
790        0x0C => decode_multiplex_buffer_utilization(data).unwrap_or(DescriptorBody::Raw),
791        0x0D => decode_copyright(data).unwrap_or(DescriptorBody::Raw),
792        0x0E => decode_maximum_bitrate(data).unwrap_or(DescriptorBody::Raw),
793        0x10 => decode_smoothing_buffer(data).unwrap_or(DescriptorBody::Raw),
794        0x11 => decode_std(data).unwrap_or(DescriptorBody::Raw),
795        0x12 => decode_ibp(data).unwrap_or(DescriptorBody::Raw),
796        0x28 => decode_avc_video(data).unwrap_or(DescriptorBody::Raw),
797        0x38 => decode_hevc_video(data).unwrap_or(DescriptorBody::Raw),
798        0x48 => decode_service(data).unwrap_or(DescriptorBody::Raw),
799        0x4D => decode_short_event(data).unwrap_or(DescriptorBody::Raw),
800        _ => DescriptorBody::Raw,
801    }
802}
803
804fn decode_service(data: &[u8]) -> Option<DescriptorBody<'_>> {
805    // ETSI EN 300 468 §6.2.33 Table 88:
806    //   service_type                 (8)
807    //   service_provider_name_length (8)
808    //   service_provider_name        (provider_len bytes)
809    //   service_name_length          (8)
810    //   service_name                 (name_len bytes)
811    if data.len() < 2 {
812        return None;
813    }
814    let service_type = data[0];
815    let provider_len = data[1] as usize;
816    let provider_start = 2usize;
817    let provider_end = provider_start.checked_add(provider_len)?;
818    // The name_length byte must still fit after the provider run.
819    if provider_end >= data.len() {
820        return None;
821    }
822    let service_provider_name = &data[provider_start..provider_end];
823    let name_len = data[provider_end] as usize;
824    let name_start = provider_end + 1;
825    let name_end = name_start.checked_add(name_len)?;
826    if name_end > data.len() {
827        return None;
828    }
829    let service_name = &data[name_start..name_end];
830    Some(DescriptorBody::Service(ServiceDescriptor {
831        service_type,
832        service_provider_name,
833        service_name,
834    }))
835}
836
837fn decode_short_event(data: &[u8]) -> Option<DescriptorBody<'_>> {
838    // ETSI EN 300 468 §6.2.37 Table 93:
839    //   ISO_639_language_code (24)
840    //   event_name_length     (8)
841    //   event_name            (name_len bytes)
842    //   text_length           (8)
843    //   text                  (text_len bytes)
844    if data.len() < 4 {
845        return None;
846    }
847    let language_code = [data[0], data[1], data[2]];
848    let name_len = data[3] as usize;
849    let name_start = 4usize;
850    let name_end = name_start.checked_add(name_len)?;
851    // The text_length byte must still fit after the event_name run.
852    if name_end >= data.len() {
853        return None;
854    }
855    let event_name = &data[name_start..name_end];
856    let text_len = data[name_end] as usize;
857    let text_start = name_end + 1;
858    let text_end = text_start.checked_add(text_len)?;
859    if text_end > data.len() {
860        return None;
861    }
862    let text = &data[text_start..text_end];
863    Some(DescriptorBody::ShortEvent(ShortEventDescriptor {
864        language_code,
865        event_name,
866        text,
867    }))
868}
869
870fn decode_video_stream(data: &[u8]) -> Option<DescriptorBody<'_>> {
871    if data.is_empty() {
872        return None;
873    }
874    let b0 = data[0];
875    let multiple_frame_rate_flag = (b0 & 0b1000_0000) != 0;
876    let frame_rate_code = (b0 >> 3) & 0b0000_1111;
877    let mpeg_1_only_flag = (b0 & 0b0000_0100) != 0;
878    let constrained_parameter_flag = (b0 & 0b0000_0010) != 0;
879    let still_picture_flag = (b0 & 0b0000_0001) != 0;
880    let (profile_and_level_indication, chroma_format, frame_rate_extension_flag) =
881        if !mpeg_1_only_flag && data.len() >= 3 {
882            let pli = data[1];
883            let b2 = data[2];
884            let chroma = (b2 >> 6) & 0b11;
885            let fre = (b2 & 0b0010_0000) != 0;
886            (Some(pli), Some(chroma), Some(fre))
887        } else {
888            (None, None, None)
889        };
890    Some(DescriptorBody::VideoStream(VideoStreamDescriptor {
891        multiple_frame_rate_flag,
892        frame_rate_code,
893        mpeg_1_only_flag,
894        constrained_parameter_flag,
895        still_picture_flag,
896        profile_and_level_indication,
897        chroma_format,
898        frame_rate_extension_flag,
899    }))
900}
901
902fn decode_audio_stream(data: &[u8]) -> Option<DescriptorBody<'_>> {
903    if data.is_empty() {
904        return None;
905    }
906    let b0 = data[0];
907    let free_format_flag = (b0 & 0b1000_0000) != 0;
908    let id = (b0 & 0b0100_0000) != 0;
909    let layer = (b0 >> 4) & 0b0000_0011;
910    let variable_rate_audio_indicator = (b0 & 0b0000_1000) != 0;
911    Some(DescriptorBody::AudioStream(AudioStreamDescriptor {
912        free_format_flag,
913        id,
914        layer,
915        variable_rate_audio_indicator,
916    }))
917}
918
919fn decode_hierarchy(data: &[u8]) -> Option<DescriptorBody<'_>> {
920    // Table 2-43: four bytes total. Each carries two reserved high bits
921    // then a six-bit payload field. Byte 0 differs — the high nibble is
922    // reserved and the low nibble is the hierarchy_type. All four
923    // reserved chunks are ignored on the read path per §2.6 generic
924    // forwards-compatibility rules.
925    if data.len() < 4 {
926        return None;
927    }
928    let hierarchy_type = HierarchyType::from_nibble(data[0]);
929    let hierarchy_layer_index = data[1] & 0b0011_1111;
930    let hierarchy_embedded_layer_index = data[2] & 0b0011_1111;
931    let hierarchy_channel = data[3] & 0b0011_1111;
932    Some(DescriptorBody::Hierarchy(HierarchyDescriptor {
933        hierarchy_type,
934        hierarchy_layer_index,
935        hierarchy_embedded_layer_index,
936        hierarchy_channel,
937    }))
938}
939
940fn decode_registration(data: &[u8]) -> Option<DescriptorBody<'_>> {
941    if data.len() < 4 {
942        return None;
943    }
944    let mut format_identifier = [0u8; 4];
945    format_identifier.copy_from_slice(&data[..4]);
946    Some(DescriptorBody::Registration {
947        format_identifier,
948        additional_identification_info: &data[4..],
949    })
950}
951
952fn decode_ca(data: &[u8]) -> Option<DescriptorBody<'_>> {
953    if data.len() < 4 {
954        return None;
955    }
956    let ca_system_id = u16::from_be_bytes([data[0], data[1]]);
957    let ca_pid = (((data[2] & 0b0001_1111) as u16) << 8) | (data[3] as u16);
958    Some(DescriptorBody::Ca(CaDescriptor {
959        ca_system_id,
960        ca_pid,
961        private_data: &data[4..],
962    }))
963}
964
965fn decode_iso639_language(data: &[u8]) -> Option<DescriptorBody<'_>> {
966    if data.len() % 4 != 0 {
967        return None;
968    }
969    let mut langs = Vec::with_capacity(data.len() / 4);
970    for chunk in data.chunks_exact(4) {
971        let mut language = [0u8; 3];
972        language.copy_from_slice(&chunk[..3]);
973        langs.push(Iso639Language {
974            language,
975            audio_type: chunk[3],
976        });
977    }
978    Some(DescriptorBody::Iso639Language(langs))
979}
980
981fn decode_data_stream_alignment(data: &[u8]) -> Option<DescriptorBody<'_>> {
982    if data.is_empty() {
983        return None;
984    }
985    Some(DescriptorBody::DataStreamAlignment(
986        DataStreamAlignmentDescriptor {
987            alignment_type: data[0],
988        },
989    ))
990}
991
992fn decode_target_background_grid(data: &[u8]) -> Option<DescriptorBody<'_>> {
993    // Four-byte payload (Table 2-49):
994    //   byte 0: horizontal_size bits 13..6                       (8)
995    //   byte 1: horizontal_size bits 5..0 (6) | vertical_size 13..12 (2)
996    //   byte 2: vertical_size bits 11..4                         (8)
997    //   byte 3: vertical_size bits 3..0 (4) | aspect_ratio_information (4)
998    // Both size fields are big-endian 14-bit unsigneds; the table carries
999    // no reserved bits, so all 32 bits are payload.
1000    if data.len() < 4 {
1001        return None;
1002    }
1003    let horizontal_size = ((data[0] as u16) << 6) | ((data[1] >> 2) as u16);
1004    let vertical_size = (((data[1] & 0b0000_0011) as u16) << 12)
1005        | ((data[2] as u16) << 4)
1006        | ((data[3] >> 4) as u16);
1007    let aspect_ratio_information = data[3] & 0b0000_1111;
1008    Some(DescriptorBody::TargetBackgroundGrid(
1009        TargetBackgroundGridDescriptor {
1010            horizontal_size,
1011            vertical_size,
1012            aspect_ratio_information,
1013        },
1014    ))
1015}
1016
1017fn decode_video_window(data: &[u8]) -> Option<DescriptorBody<'_>> {
1018    // Four-byte payload (Table 2-50):
1019    //   byte 0: horizontal_offset bits 13..6                     (8)
1020    //   byte 1: horizontal_offset bits 5..0 (6) | vertical_offset 13..12 (2)
1021    //   byte 2: vertical_offset bits 11..4                       (8)
1022    //   byte 3: vertical_offset bits 3..0 (4) | window_priority  (4)
1023    // Both offset fields are big-endian 14-bit unsigneds; the table carries
1024    // no reserved bits, so all 32 bits are payload (same layout as the
1025    // target_background_grid_descriptor's size + aspect-ratio fields).
1026    if data.len() < 4 {
1027        return None;
1028    }
1029    let horizontal_offset = ((data[0] as u16) << 6) | ((data[1] >> 2) as u16);
1030    let vertical_offset = (((data[1] & 0b0000_0011) as u16) << 12)
1031        | ((data[2] as u16) << 4)
1032        | ((data[3] >> 4) as u16);
1033    let window_priority = data[3] & 0b0000_1111;
1034    Some(DescriptorBody::VideoWindow(VideoWindowDescriptor {
1035        horizontal_offset,
1036        vertical_offset,
1037        window_priority,
1038    }))
1039}
1040
1041fn decode_system_clock(data: &[u8]) -> Option<DescriptorBody<'_>> {
1042    // Two-byte payload: byte 0 = external_clock_reference_indicator (1)
1043    //                            | reserved (1) | clock_accuracy_integer (6);
1044    //                  byte 1 = clock_accuracy_exponent (3) | reserved (5).
1045    if data.len() < 2 {
1046        return None;
1047    }
1048    let b0 = data[0];
1049    let b1 = data[1];
1050    let external_clock_reference = (b0 & 0b1000_0000) != 0;
1051    let clock_accuracy_integer = b0 & 0b0011_1111;
1052    let clock_accuracy_exponent = (b1 >> 5) & 0b0000_0111;
1053    Some(DescriptorBody::SystemClock(SystemClockDescriptor {
1054        external_clock_reference,
1055        clock_accuracy_integer,
1056        clock_accuracy_exponent,
1057    }))
1058}
1059
1060fn decode_multiplex_buffer_utilization(data: &[u8]) -> Option<DescriptorBody<'_>> {
1061    // Four-byte payload (Table 2-55, with the §2.6.23 15-bit-upper-bound
1062    // reading documented on the struct):
1063    //   byte 0: bound_valid_flag (1) | LTW_offset_lower_bound bits 14..8 (7)
1064    //   byte 1: LTW_offset_lower_bound bits 7..0                         (8)
1065    //   byte 2: reserved          (1) | LTW_offset_upper_bound bits 14..8 (7)
1066    //   byte 3: LTW_offset_upper_bound bits 7..0                         (8)
1067    // Both bounds are big-endian 15-bit unsigneds packed against the LSB
1068    // of their leading byte. Reserved bits in byte 2 are ignored on the
1069    // read path per §2.6 forwards-compatibility.
1070    if data.len() < 4 {
1071        return None;
1072    }
1073    let bound_valid_flag = (data[0] & 0b1000_0000) != 0;
1074    let ltw_offset_lower_bound = (((data[0] & 0b0111_1111) as u16) << 8) | (data[1] as u16);
1075    let ltw_offset_upper_bound = (((data[2] & 0b0111_1111) as u16) << 8) | (data[3] as u16);
1076    Some(DescriptorBody::MultiplexBufferUtilization(
1077        MultiplexBufferUtilizationDescriptor {
1078            bound_valid_flag,
1079            ltw_offset_lower_bound,
1080            ltw_offset_upper_bound,
1081        },
1082    ))
1083}
1084
1085fn decode_copyright(data: &[u8]) -> Option<DescriptorBody<'_>> {
1086    // Table 2-56: 32-bit copyright_identifier (big-endian) followed by
1087    // zero or more additional_copyright_info bytes.
1088    if data.len() < 4 {
1089        return None;
1090    }
1091    let copyright_identifier = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
1092    Some(DescriptorBody::Copyright(CopyrightDescriptor {
1093        copyright_identifier,
1094        additional_copyright_info: &data[4..],
1095    }))
1096}
1097
1098fn decode_maximum_bitrate(data: &[u8]) -> Option<DescriptorBody<'_>> {
1099    // Three-byte payload: 2 reserved bits then 22-bit maximum_bitrate
1100    // (big-endian, MSBs in byte 0 after the reserved nibble).
1101    if data.len() < 3 {
1102        return None;
1103    }
1104    let maximum_bitrate =
1105        (((data[0] & 0b0011_1111) as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
1106    Some(DescriptorBody::MaximumBitrate(MaximumBitrateDescriptor {
1107        maximum_bitrate,
1108    }))
1109}
1110
1111fn decode_smoothing_buffer(data: &[u8]) -> Option<DescriptorBody<'_>> {
1112    // Six-byte payload (Table 2-59):
1113    //   byte 0: 2 reserved bits | top 6 bits of sb_leak_rate
1114    //   byte 1: middle 8 bits of sb_leak_rate
1115    //   byte 2: low 8 bits of sb_leak_rate
1116    //   byte 3: 2 reserved bits | top 6 bits of sb_size
1117    //   byte 4: middle 8 bits of sb_size
1118    //   byte 5: low 8 bits of sb_size
1119    // Both fields are big-endian; both are 22-bit unsigned.
1120    if data.len() < 6 {
1121        return None;
1122    }
1123    let sb_leak_rate =
1124        (((data[0] & 0b0011_1111) as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32);
1125    let sb_size =
1126        (((data[3] & 0b0011_1111) as u32) << 16) | ((data[4] as u32) << 8) | (data[5] as u32);
1127    Some(DescriptorBody::SmoothingBuffer(SmoothingBufferDescriptor {
1128        sb_leak_rate,
1129        sb_size,
1130    }))
1131}
1132
1133fn decode_std(data: &[u8]) -> Option<DescriptorBody<'_>> {
1134    // One-byte payload: 7 reserved bits then 1-bit leak_valid_flag.
1135    if data.is_empty() {
1136        return None;
1137    }
1138    Some(DescriptorBody::Std(StdDescriptor {
1139        leak_valid_flag: (data[0] & 0b0000_0001) != 0,
1140    }))
1141}
1142
1143fn decode_ibp(data: &[u8]) -> Option<DescriptorBody<'_>> {
1144    // Two-byte payload (Table 2-61):
1145    //   byte 0: closed_gop_flag    (1)
1146    //         | identical_gop_flag (1)
1147    //         | max_gop_length bits 13..8 (6)
1148    //   byte 1: max_gop_length bits 7..0 (8)
1149    // max_gop_length is a big-endian 14-bit unsigned; spec §2.6.35
1150    // forbids the value 0 but the parser still surfaces it so callers
1151    // can detect malformed streams via IbpDescriptor::is_well_formed.
1152    if data.len() < 2 {
1153        return None;
1154    }
1155    let closed_gop_flag = (data[0] & 0b1000_0000) != 0;
1156    let identical_gop_flag = (data[0] & 0b0100_0000) != 0;
1157    let max_gop_length = (((data[0] & 0b0011_1111) as u16) << 8) | (data[1] as u16);
1158    Some(DescriptorBody::Ibp(IbpDescriptor {
1159        closed_gop_flag,
1160        identical_gop_flag,
1161        max_gop_length,
1162    }))
1163}
1164
1165fn decode_avc_video(data: &[u8]) -> Option<DescriptorBody<'_>> {
1166    if data.len() < 4 {
1167        return None;
1168    }
1169    let profile_idc = data[0];
1170    let constraint_set_and_compat = data[1];
1171    let level_idc = data[2];
1172    let flags = data[3];
1173    let avc_still_present = (flags & 0b1000_0000) != 0;
1174    let avc_24_hour_picture_flag = (flags & 0b0100_0000) != 0;
1175    let frame_packing_sei_not_present_flag = (flags & 0b0010_0000) != 0;
1176    Some(DescriptorBody::AvcVideo(AvcVideoDescriptor {
1177        profile_idc,
1178        constraint_set_and_compat,
1179        level_idc,
1180        avc_still_present,
1181        avc_24_hour_picture_flag,
1182        frame_packing_sei_not_present_flag,
1183    }))
1184}
1185
1186fn decode_hevc_video(data: &[u8]) -> Option<DescriptorBody<'_>> {
1187    // 13 fixed bytes; +1 byte when temporal_layer_subset_flag = 1.
1188    if data.len() < 13 {
1189        return None;
1190    }
1191    let b0 = data[0];
1192    let profile_space = (b0 >> 6) & 0b11;
1193    let tier_flag = (b0 & 0b0010_0000) != 0;
1194    let profile_idc = b0 & 0b0001_1111;
1195    let profile_compatibility_indication = u32::from_be_bytes([data[1], data[2], data[3], data[4]]);
1196    let b5 = data[5];
1197    let progressive_source_flag = (b5 & 0b1000_0000) != 0;
1198    let interlaced_source_flag = (b5 & 0b0100_0000) != 0;
1199    let non_packed_constraint_flag = (b5 & 0b0010_0000) != 0;
1200    let frame_only_constraint_flag = (b5 & 0b0001_0000) != 0;
1201    // bytes 6..=11 carry the 44-bit `reserved_zero_44bits` field, which
1202    // we don't surface; per spec it must be zero on conformant streams
1203    // but we don't enforce.
1204    let level_idc = data[12];
1205    let (temporal_layer_subset_flag, hevc_still_present_flag, hevc_24hr_picture_present_flag) =
1206        if data.len() >= 14 {
1207            let b13 = data[13];
1208            (
1209                (b13 & 0b1000_0000) != 0,
1210                (b13 & 0b0100_0000) != 0,
1211                (b13 & 0b0010_0000) != 0,
1212            )
1213        } else {
1214            (false, false, false)
1215        };
1216    let (temporal_id_min, temporal_id_max) = if temporal_layer_subset_flag && data.len() >= 16 {
1217        // Two bytes: `temporal_id_min (3) | reserved (5)` then
1218        // `temporal_id_max (3) | reserved (5)`.
1219        let lo = (data[14] >> 5) & 0b111;
1220        let hi = (data[15] >> 5) & 0b111;
1221        (Some(lo), Some(hi))
1222    } else {
1223        (None, None)
1224    };
1225    Some(DescriptorBody::HevcVideo(HevcVideoDescriptor {
1226        profile_space,
1227        tier_flag,
1228        profile_idc,
1229        profile_compatibility_indication,
1230        progressive_source_flag,
1231        interlaced_source_flag,
1232        non_packed_constraint_flag,
1233        frame_only_constraint_flag,
1234        level_idc,
1235        temporal_layer_subset_flag,
1236        hevc_still_present_flag,
1237        hevc_24hr_picture_present_flag,
1238        temporal_id_min,
1239        temporal_id_max,
1240    }))
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245    use super::*;
1246
1247    fn tlv(tag: u8, body: &[u8]) -> Vec<u8> {
1248        let mut v = vec![tag, body.len() as u8];
1249        v.extend_from_slice(body);
1250        v
1251    }
1252
1253    #[test]
1254    fn iter_empty_block_yields_none() {
1255        let mut it = iter_descriptors(&[]);
1256        assert!(it.next().is_none());
1257    }
1258
1259    #[test]
1260    fn unknown_tag_passes_through_as_raw() {
1261        let block = tlv(0x42, &[0xAA, 0xBB, 0xCC]);
1262        let d = iter_descriptors(&block).next().unwrap().unwrap();
1263        assert_eq!(d.tag, 0x42);
1264        assert_eq!(d.data, &[0xAA, 0xBB, 0xCC]);
1265        assert!(matches!(d.body, DescriptorBody::Raw));
1266    }
1267
1268    #[test]
1269    fn two_descriptors_back_to_back() {
1270        let mut block = Vec::new();
1271        block.extend_from_slice(&tlv(0x05, b"HDMV"));
1272        block.extend_from_slice(&tlv(0x09, &[0x09, 0x00, 0xE5, 0x00, 0xAB, 0xCD]));
1273        let v: Vec<_> = iter_descriptors(&block).collect();
1274        assert_eq!(v.len(), 2);
1275        let d0 = v[0].as_ref().unwrap();
1276        assert_eq!(d0.tag, 0x05);
1277        match &d0.body {
1278            DescriptorBody::Registration {
1279                format_identifier,
1280                additional_identification_info,
1281            } => {
1282                assert_eq!(format_identifier, b"HDMV");
1283                assert!(additional_identification_info.is_empty());
1284            }
1285            other => panic!("expected Registration, got {other:?}"),
1286        }
1287        let d1 = v[1].as_ref().unwrap();
1288        assert_eq!(d1.tag, 0x09);
1289        match &d1.body {
1290            DescriptorBody::Ca(ca) => {
1291                assert_eq!(ca.ca_system_id, 0x0900);
1292                assert_eq!(ca.ca_pid, 0x0500);
1293                assert_eq!(ca.private_data, &[0xAB, 0xCD]);
1294            }
1295            other => panic!("expected Ca, got {other:?}"),
1296        }
1297    }
1298
1299    #[test]
1300    fn truncated_header_surfaces_error_then_stops() {
1301        let block = [0x05u8]; // only 1 byte — no length follows
1302        let mut it = iter_descriptors(&block);
1303        let err = it.next().expect("one error").unwrap_err();
1304        assert!(matches!(err, TsError::Truncated { .. }));
1305        assert!(it.next().is_none());
1306    }
1307
1308    #[test]
1309    fn truncated_body_surfaces_error_then_stops() {
1310        let mut block = vec![0x05u8, 0x08]; // claims 8 bytes
1311        block.extend_from_slice(&[0xAA, 0xBB]); // only 2 supplied
1312        let mut it = iter_descriptors(&block);
1313        let err = it.next().expect("one error").unwrap_err();
1314        assert!(matches!(err, TsError::Truncated { .. }));
1315        assert!(it.next().is_none());
1316    }
1317
1318    #[test]
1319    fn registration_descriptor_decodes_hdmv_with_trailer() {
1320        let block = tlv(0x05, b"HDMV\x88\x99");
1321        let d = iter_descriptors(&block).next().unwrap().unwrap();
1322        match &d.body {
1323            DescriptorBody::Registration {
1324                format_identifier,
1325                additional_identification_info,
1326            } => {
1327                assert_eq!(format_identifier, b"HDMV");
1328                assert_eq!(additional_identification_info, &[0x88, 0x99]);
1329            }
1330            other => panic!("expected Registration, got {other:?}"),
1331        }
1332    }
1333
1334    #[test]
1335    fn iso639_language_descriptor_two_entries() {
1336        // "eng" audio_type=0, "jpn" audio_type=2
1337        let body = [b'e', b'n', b'g', 0x00, b'j', b'p', b'n', 0x02];
1338        let block = tlv(0x0A, &body);
1339        let d = iter_descriptors(&block).next().unwrap().unwrap();
1340        match &d.body {
1341            DescriptorBody::Iso639Language(langs) => {
1342                assert_eq!(langs.len(), 2);
1343                assert_eq!(&langs[0].language, b"eng");
1344                assert_eq!(langs[0].audio_type, 0);
1345                assert_eq!(&langs[1].language, b"jpn");
1346                assert_eq!(langs[1].audio_type, 2);
1347            }
1348            other => panic!("expected Iso639Language, got {other:?}"),
1349        }
1350    }
1351
1352    #[test]
1353    fn iso639_language_bad_length_falls_back_to_raw() {
1354        // 5 bytes — not a multiple of 4.
1355        let body = [b'e', b'n', b'g', 0x00, 0xFF];
1356        let block = tlv(0x0A, &body);
1357        let d = iter_descriptors(&block).next().unwrap().unwrap();
1358        assert!(matches!(d.body, DescriptorBody::Raw));
1359        assert_eq!(d.data, &body);
1360    }
1361
1362    #[test]
1363    fn video_stream_descriptor_mpeg2_three_bytes() {
1364        // multiple_frame_rate=0, frame_rate_code=4 (29.97), MPEG_1_only=0,
1365        // CPF=0, still=0, profile_and_level=0x44 (Main@Main),
1366        // chroma=01 (4:2:0), fre=0.
1367        let body = [4u8 << 3, 0x44u8, 0b01 << 6];
1368        let block = tlv(0x02, &body);
1369        let d = iter_descriptors(&block).next().unwrap().unwrap();
1370        match &d.body {
1371            DescriptorBody::VideoStream(v) => {
1372                assert!(!v.multiple_frame_rate_flag);
1373                assert_eq!(v.frame_rate_code, 4);
1374                assert!(!v.mpeg_1_only_flag);
1375                assert_eq!(v.profile_and_level_indication, Some(0x44));
1376                assert_eq!(v.chroma_format, Some(0b01));
1377                assert_eq!(v.frame_rate_extension_flag, Some(false));
1378            }
1379            other => panic!("expected VideoStream, got {other:?}"),
1380        }
1381    }
1382
1383    #[test]
1384    fn video_stream_descriptor_mpeg1_one_byte() {
1385        // mpeg_1_only_flag = 1, frame_rate_code = 3, still = 1.
1386        let body = [(3 << 3) | 0b0000_0101];
1387        let block = tlv(0x02, &body);
1388        let d = iter_descriptors(&block).next().unwrap().unwrap();
1389        match &d.body {
1390            DescriptorBody::VideoStream(v) => {
1391                assert!(v.mpeg_1_only_flag);
1392                assert_eq!(v.frame_rate_code, 3);
1393                assert!(v.still_picture_flag);
1394                assert!(v.profile_and_level_indication.is_none());
1395                assert!(v.chroma_format.is_none());
1396                assert!(v.frame_rate_extension_flag.is_none());
1397            }
1398            other => panic!("expected VideoStream, got {other:?}"),
1399        }
1400    }
1401
1402    #[test]
1403    fn audio_stream_descriptor_layer_ii() {
1404        // free_format=0, ID=1 (MPEG-1), layer=10 (Layer II), VRA=1.
1405        let body = [(1u8 << 6) | (0b10 << 4) | (1 << 3)];
1406        let block = tlv(0x03, &body);
1407        let d = iter_descriptors(&block).next().unwrap().unwrap();
1408        match &d.body {
1409            DescriptorBody::AudioStream(a) => {
1410                assert!(!a.free_format_flag);
1411                assert!(a.id);
1412                assert_eq!(a.layer, 0b10);
1413                assert!(a.variable_rate_audio_indicator);
1414            }
1415            other => panic!("expected AudioStream, got {other:?}"),
1416        }
1417    }
1418
1419    #[test]
1420    fn ca_descriptor_min_no_private_data() {
1421        // CA_system_ID=0x4AAA, CA_PID=0x123 (with reserved=111 in high byte).
1422        let body = [0x4A, 0xAA, 0xE1 /* 111_00001 */, 0x23];
1423        let block = tlv(0x09, &body);
1424        let d = iter_descriptors(&block).next().unwrap().unwrap();
1425        match &d.body {
1426            DescriptorBody::Ca(ca) => {
1427                assert_eq!(ca.ca_system_id, 0x4AAA);
1428                assert_eq!(ca.ca_pid, 0x0123);
1429                assert!(ca.private_data.is_empty());
1430            }
1431            other => panic!("expected Ca, got {other:?}"),
1432        }
1433    }
1434
1435    #[test]
1436    fn avc_video_descriptor_typical_values() {
1437        // profile_idc=100 (High), constraint+compat=0, level_idc=41,
1438        // flags: still=0, 24h=0, fp_sei_npresent=0.
1439        let body = [100, 0x00, 41, 0x00];
1440        let block = tlv(0x28, &body);
1441        let d = iter_descriptors(&block).next().unwrap().unwrap();
1442        match &d.body {
1443            DescriptorBody::AvcVideo(a) => {
1444                assert_eq!(a.profile_idc, 100);
1445                assert_eq!(a.level_idc, 41);
1446                assert!(!a.avc_still_present);
1447                assert!(!a.frame_packing_sei_not_present_flag);
1448            }
1449            other => panic!("expected AvcVideo, got {other:?}"),
1450        }
1451    }
1452
1453    #[test]
1454    fn hevc_video_descriptor_min_13_bytes() {
1455        // profile_space=0, tier_flag=1, profile_idc=2 (Main 10):
1456        let b0 = (1u8 << 5) | 2;
1457        let compat: u32 = 0x6000_0000;
1458        // progressive=1, interlaced=0, non_packed=1, frame_only=1, rest 0.
1459        let b5: u8 = 0b1011_0000;
1460        let level_idc: u8 = 153;
1461        let body = vec![
1462            b0,
1463            (compat >> 24) as u8,
1464            (compat >> 16) as u8,
1465            (compat >> 8) as u8,
1466            compat as u8,
1467            b5,
1468            0,
1469            0,
1470            0,
1471            0,
1472            0,
1473            0,
1474            level_idc,
1475        ];
1476        // No temporal_layer_subset byte.
1477        let block = tlv(0x38, &body);
1478        let d = iter_descriptors(&block).next().unwrap().unwrap();
1479        match &d.body {
1480            DescriptorBody::HevcVideo(h) => {
1481                assert_eq!(h.profile_space, 0);
1482                assert!(h.tier_flag);
1483                assert_eq!(h.profile_idc, 2);
1484                assert_eq!(h.profile_compatibility_indication, compat);
1485                assert!(h.progressive_source_flag);
1486                assert!(!h.interlaced_source_flag);
1487                assert!(h.non_packed_constraint_flag);
1488                assert!(h.frame_only_constraint_flag);
1489                assert_eq!(h.level_idc, level_idc);
1490                assert!(!h.temporal_layer_subset_flag);
1491                assert!(h.temporal_id_min.is_none());
1492                assert!(h.temporal_id_max.is_none());
1493            }
1494            other => panic!("expected HevcVideo, got {other:?}"),
1495        }
1496    }
1497
1498    #[test]
1499    fn hevc_video_descriptor_with_temporal_layer_subset() {
1500        let mut body = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 120];
1501        // temporal_layer_subset_flag=1, still=0, 24h=1, rest 0.
1502        body.push(0b1010_0000);
1503        // temporal_id_min = 1, temporal_id_max = 4.
1504        body.push(1 << 5);
1505        body.push(4 << 5);
1506        let block = tlv(0x38, &body);
1507        let d = iter_descriptors(&block).next().unwrap().unwrap();
1508        match &d.body {
1509            DescriptorBody::HevcVideo(h) => {
1510                assert!(h.temporal_layer_subset_flag);
1511                assert!(h.hevc_24hr_picture_present_flag);
1512                assert_eq!(h.temporal_id_min, Some(1));
1513                assert_eq!(h.temporal_id_max, Some(4));
1514            }
1515            other => panic!("expected HevcVideo, got {other:?}"),
1516        }
1517    }
1518
1519    #[test]
1520    fn data_stream_alignment_video_access_unit() {
1521        // alignment_type = 0x02 = "video access unit" per Table 2-47.
1522        let block = tlv(0x06, &[0x02]);
1523        let d = iter_descriptors(&block).next().unwrap().unwrap();
1524        match &d.body {
1525            DescriptorBody::DataStreamAlignment(a) => {
1526                assert_eq!(a.alignment_type, 0x02);
1527            }
1528            other => panic!("expected DataStreamAlignment, got {other:?}"),
1529        }
1530    }
1531
1532    #[test]
1533    fn data_stream_alignment_empty_body_falls_back_to_raw() {
1534        let block = tlv(0x06, &[]);
1535        let d = iter_descriptors(&block).next().unwrap().unwrap();
1536        assert!(matches!(d.body, DescriptorBody::Raw));
1537    }
1538
1539    #[test]
1540    fn system_clock_descriptor_external_ref_off() {
1541        // external=0, reserved=1, clock_accuracy_integer=0b00_1010 (=10);
1542        // clock_accuracy_exponent=0b011 (=3), reserved=0b00000.
1543        let b0 = 0b0100_1010u8;
1544        let b1 = 0b0110_0000u8;
1545        let block = tlv(0x0B, &[b0, b1]);
1546        let d = iter_descriptors(&block).next().unwrap().unwrap();
1547        match &d.body {
1548            DescriptorBody::SystemClock(s) => {
1549                assert!(!s.external_clock_reference);
1550                assert_eq!(s.clock_accuracy_integer, 10);
1551                assert_eq!(s.clock_accuracy_exponent, 3);
1552            }
1553            other => panic!("expected SystemClock, got {other:?}"),
1554        }
1555    }
1556
1557    #[test]
1558    fn system_clock_descriptor_external_ref_on() {
1559        // external=1, reserved=0, clock_accuracy_integer=0b00_0000;
1560        // clock_accuracy_exponent=0b000.
1561        let block = tlv(0x0B, &[0b1000_0000, 0b0000_0000]);
1562        let d = iter_descriptors(&block).next().unwrap().unwrap();
1563        match &d.body {
1564            DescriptorBody::SystemClock(s) => {
1565                assert!(s.external_clock_reference);
1566                assert_eq!(s.clock_accuracy_integer, 0);
1567                assert_eq!(s.clock_accuracy_exponent, 0);
1568            }
1569            other => panic!("expected SystemClock, got {other:?}"),
1570        }
1571    }
1572
1573    #[test]
1574    fn maximum_bitrate_descriptor_typical() {
1575        // maximum_bitrate = 0x12_3456 (1_193_046 × 50 B/s = 59_652_300 B/s).
1576        // Encode: 2 reserved bits then 22 bits big-endian.
1577        // 0x12_3456 = 0b00_010010_00110100_01010110.
1578        let b0 = 0b1100_0000 | 0b0001_0010; // 2 reserved (set to 1) | top 6 bits
1579        let block = tlv(0x0E, &[b0, 0x34, 0x56]);
1580        let d = iter_descriptors(&block).next().unwrap().unwrap();
1581        match &d.body {
1582            DescriptorBody::MaximumBitrate(m) => {
1583                assert_eq!(m.maximum_bitrate, 0x12_3456);
1584                // 0x12_3456 × 400 = 477_218_400 bits/s
1585                assert_eq!(m.bits_per_second(), 0x12_3456u64 * 400);
1586            }
1587            other => panic!("expected MaximumBitrate, got {other:?}"),
1588        }
1589    }
1590
1591    #[test]
1592    fn maximum_bitrate_descriptor_zero_bitrate() {
1593        // All-zero except the reserved high bits (set to 1 per spec).
1594        let block = tlv(0x0E, &[0b1100_0000, 0x00, 0x00]);
1595        let d = iter_descriptors(&block).next().unwrap().unwrap();
1596        match &d.body {
1597            DescriptorBody::MaximumBitrate(m) => {
1598                assert_eq!(m.maximum_bitrate, 0);
1599                assert_eq!(m.bits_per_second(), 0);
1600            }
1601            other => panic!("expected MaximumBitrate, got {other:?}"),
1602        }
1603    }
1604
1605    #[test]
1606    fn maximum_bitrate_descriptor_short_body_falls_back_to_raw() {
1607        let block = tlv(0x0E, &[0xC0, 0x00]); // only 2 bytes
1608        let d = iter_descriptors(&block).next().unwrap().unwrap();
1609        assert!(matches!(d.body, DescriptorBody::Raw));
1610    }
1611
1612    #[test]
1613    fn std_descriptor_leak_valid_set() {
1614        // 7 reserved bits high, leak_valid_flag = 1.
1615        let block = tlv(0x11, &[0b1111_1111]);
1616        let d = iter_descriptors(&block).next().unwrap().unwrap();
1617        match &d.body {
1618            DescriptorBody::Std(s) => assert!(s.leak_valid_flag),
1619            other => panic!("expected Std, got {other:?}"),
1620        }
1621    }
1622
1623    #[test]
1624    fn std_descriptor_leak_valid_unset() {
1625        let block = tlv(0x11, &[0b1111_1110]);
1626        let d = iter_descriptors(&block).next().unwrap().unwrap();
1627        match &d.body {
1628            DescriptorBody::Std(s) => assert!(!s.leak_valid_flag),
1629            other => panic!("expected Std, got {other:?}"),
1630        }
1631    }
1632
1633    #[test]
1634    fn smoothing_buffer_descriptor_typical_values() {
1635        // sb_leak_rate = 0x09_C400 = 640_000 (units of 400 bits/s →
1636        //   256 Mbit/s, units of 50 bytes/s → 32 MB/s).
1637        // sb_size      = 0x00_C000 = 49_152 bytes (= 48 KiB).
1638        // Reserved bits set to 1 (per spec they are reserved-for-future-
1639        // use; our parser must ignore them on the read path).
1640        let leak: u32 = 0x09_C400;
1641        let size: u32 = 0x00_C000;
1642        let body = [
1643            0b1100_0000 | ((leak >> 16) as u8 & 0b0011_1111),
1644            (leak >> 8) as u8,
1645            leak as u8,
1646            0b1100_0000 | ((size >> 16) as u8 & 0b0011_1111),
1647            (size >> 8) as u8,
1648            size as u8,
1649        ];
1650        let block = tlv(0x10, &body);
1651        let d = iter_descriptors(&block).next().unwrap().unwrap();
1652        match &d.body {
1653            DescriptorBody::SmoothingBuffer(sb) => {
1654                assert_eq!(sb.sb_leak_rate, leak);
1655                assert_eq!(sb.sb_size, size);
1656                // 640_000 × 400 = 256_000_000 bits/s
1657                assert_eq!(sb.leak_rate_bits_per_second(), 256_000_000);
1658                // 640_000 × 50 = 32_000_000 bytes/s
1659                assert_eq!(sb.leak_rate_bytes_per_second(), 32_000_000);
1660                assert_eq!(sb.buffer_size_bytes(), 49_152);
1661            }
1662            other => panic!("expected SmoothingBuffer, got {other:?}"),
1663        }
1664    }
1665
1666    #[test]
1667    fn smoothing_buffer_descriptor_zero_fields() {
1668        // All-zero leak rate and size; reserved bits left at 0 too —
1669        // both should still parse, since the reserved bits are ignored.
1670        let block = tlv(0x10, &[0u8; 6]);
1671        let d = iter_descriptors(&block).next().unwrap().unwrap();
1672        match &d.body {
1673            DescriptorBody::SmoothingBuffer(sb) => {
1674                assert_eq!(sb.sb_leak_rate, 0);
1675                assert_eq!(sb.sb_size, 0);
1676                assert_eq!(sb.leak_rate_bits_per_second(), 0);
1677                assert_eq!(sb.buffer_size_bytes(), 0);
1678            }
1679            other => panic!("expected SmoothingBuffer, got {other:?}"),
1680        }
1681    }
1682
1683    #[test]
1684    fn smoothing_buffer_descriptor_max_22bit_fields() {
1685        // Both fields at their 22-bit ceilings (0x3F_FFFF). Confirms
1686        // that the parser masks off reserved bits correctly and that
1687        // the rate conversion stays inside `u64`.
1688        let max22: u32 = 0x003F_FFFF;
1689        let body = [0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8];
1690        let block = tlv(0x10, &body);
1691        let d = iter_descriptors(&block).next().unwrap().unwrap();
1692        match &d.body {
1693            DescriptorBody::SmoothingBuffer(sb) => {
1694                assert_eq!(sb.sb_leak_rate, max22);
1695                assert_eq!(sb.sb_size, max22);
1696                assert_eq!(sb.leak_rate_bits_per_second(), (max22 as u64) * 400);
1697                assert_eq!(sb.buffer_size_bytes(), max22 as u64);
1698            }
1699            other => panic!("expected SmoothingBuffer, got {other:?}"),
1700        }
1701    }
1702
1703    #[test]
1704    fn smoothing_buffer_descriptor_short_body_falls_back_to_raw() {
1705        // Five bytes is one short of the 6-byte payload.
1706        let block = tlv(0x10, &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
1707        let d = iter_descriptors(&block).next().unwrap().unwrap();
1708        assert!(matches!(d.body, DescriptorBody::Raw));
1709        assert_eq!(d.data.len(), 5);
1710    }
1711
1712    #[test]
1713    fn smoothing_buffer_descriptor_lives_in_pmt_program_info() {
1714        // A PMT's program_info block may carry a smoothing buffer
1715        // descriptor alongside an ISO-639 language descriptor; check
1716        // that `iter_descriptors` walks both and surfaces the typed
1717        // smoothing-buffer body in mid-list.
1718        let mut block = Vec::new();
1719        block.extend_from_slice(&tlv(0x0A, &[b'f', b'r', b'a', 0x00]));
1720        let leak: u32 = 0x0001_0000; // 65_536 × 400 = 26_214_400 bits/s
1721        let size: u32 = 0x0000_4000; // 16 KiB
1722        let sb_body = [
1723            ((leak >> 16) as u8) & 0b0011_1111,
1724            (leak >> 8) as u8,
1725            leak as u8,
1726            ((size >> 16) as u8) & 0b0011_1111,
1727            (size >> 8) as u8,
1728            size as u8,
1729        ];
1730        block.extend_from_slice(&tlv(0x10, &sb_body));
1731        block.extend_from_slice(&tlv(0x11, &[0b1111_1111]));
1732        let v = parse_descriptors(&block).unwrap();
1733        assert_eq!(v.len(), 3);
1734        assert_eq!(v[0].tag, 0x0A);
1735        assert_eq!(v[1].tag, 0x10);
1736        match &v[1].body {
1737            DescriptorBody::SmoothingBuffer(sb) => {
1738                assert_eq!(sb.sb_leak_rate, leak);
1739                assert_eq!(sb.sb_size, size);
1740            }
1741            other => panic!("expected SmoothingBuffer, got {other:?}"),
1742        }
1743        assert_eq!(v[2].tag, 0x11);
1744        match &v[2].body {
1745            DescriptorBody::Std(s) => assert!(s.leak_valid_flag),
1746            other => panic!("expected Std, got {other:?}"),
1747        }
1748    }
1749
1750    #[test]
1751    fn copyright_descriptor_identifier_only_no_trailer() {
1752        // `copyright_identifier` = 0x4953'4243 ("ISBC" — illustrative
1753        // four-byte FOURCC). No trailing additional_copyright_info.
1754        let body = [0x49, 0x53, 0x42, 0x43];
1755        let block = tlv(0x0D, &body);
1756        let d = iter_descriptors(&block).next().unwrap().unwrap();
1757        match &d.body {
1758            DescriptorBody::Copyright(c) => {
1759                assert_eq!(c.copyright_identifier, 0x4953_4243);
1760                assert!(c.additional_copyright_info.is_empty());
1761            }
1762            other => panic!("expected Copyright, got {other:?}"),
1763        }
1764    }
1765
1766    #[test]
1767    fn copyright_descriptor_with_trailing_info() {
1768        // 4-byte identifier + 5 trailing bytes that the assignee
1769        // defines per §2.6.25. The parser surfaces them verbatim.
1770        let body = [0x00, 0x00, 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF, 0x42];
1771        let block = tlv(0x0D, &body);
1772        let d = iter_descriptors(&block).next().unwrap().unwrap();
1773        match &d.body {
1774            DescriptorBody::Copyright(c) => {
1775                assert_eq!(c.copyright_identifier, 0x0000_0001);
1776                assert_eq!(c.additional_copyright_info, &[0xDE, 0xAD, 0xBE, 0xEF, 0x42]);
1777            }
1778            other => panic!("expected Copyright, got {other:?}"),
1779        }
1780    }
1781
1782    #[test]
1783    fn copyright_descriptor_short_body_falls_back_to_raw() {
1784        // Three bytes — one short of the mandatory 32-bit identifier.
1785        let block = tlv(0x0D, &[0xAA, 0xBB, 0xCC]);
1786        let d = iter_descriptors(&block).next().unwrap().unwrap();
1787        assert!(matches!(d.body, DescriptorBody::Raw));
1788        assert_eq!(d.data, &[0xAA, 0xBB, 0xCC]);
1789    }
1790
1791    #[test]
1792    fn copyright_descriptor_zero_body_falls_back_to_raw() {
1793        // descriptor_length == 0 — still legal per §2.6 generic TLV
1794        // framing but doesn't satisfy the copyright_descriptor's
1795        // 4-byte minimum, so the typed body falls back to Raw.
1796        let block = tlv(0x0D, &[]);
1797        let d = iter_descriptors(&block).next().unwrap().unwrap();
1798        assert!(matches!(d.body, DescriptorBody::Raw));
1799        assert!(d.data.is_empty());
1800    }
1801
1802    #[test]
1803    fn copyright_descriptor_in_pmt_program_info_alongside_other_tags() {
1804        // A realistic PMT program_info block: ISO-639 language +
1805        // copyright + smoothing-buffer. Each is a TLV; iter_descriptors
1806        // walks them in order and surfaces the typed Copyright body
1807        // mid-list without disturbing the surrounding decoders.
1808        let mut block = Vec::new();
1809        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
1810        let cid: u32 = 0x4953_524E; // ISRN — another four-byte FOURCC
1811        let trailer = [0x12, 0x34];
1812        let mut copy_body = Vec::new();
1813        copy_body.extend_from_slice(&cid.to_be_bytes());
1814        copy_body.extend_from_slice(&trailer);
1815        block.extend_from_slice(&tlv(0x0D, &copy_body));
1816        let leak: u32 = 0x0001_0000;
1817        let size: u32 = 0x0000_4000;
1818        let sb_body = [
1819            ((leak >> 16) as u8) & 0b0011_1111,
1820            (leak >> 8) as u8,
1821            leak as u8,
1822            ((size >> 16) as u8) & 0b0011_1111,
1823            (size >> 8) as u8,
1824            size as u8,
1825        ];
1826        block.extend_from_slice(&tlv(0x10, &sb_body));
1827        let v = parse_descriptors(&block).unwrap();
1828        assert_eq!(v.len(), 3);
1829        assert_eq!(v[0].tag, 0x0A);
1830        assert_eq!(v[1].tag, 0x0D);
1831        match &v[1].body {
1832            DescriptorBody::Copyright(c) => {
1833                assert_eq!(c.copyright_identifier, cid);
1834                assert_eq!(c.additional_copyright_info, &trailer);
1835            }
1836            other => panic!("expected Copyright, got {other:?}"),
1837        }
1838        assert_eq!(v[2].tag, 0x10);
1839        assert!(matches!(v[2].body, DescriptorBody::SmoothingBuffer(_)));
1840    }
1841
1842    #[test]
1843    fn hierarchy_descriptor_spatial_scalability_layer() {
1844        // hierarchy_type = 1 (spatial scalability), layer_index = 5,
1845        // embedded_layer_index = 15, channel = 0 (most robust).
1846        // Reserved bits set to 1 so the parser must mask them off.
1847        let body = [
1848            0b1111_0001,      // reserved=1111 | hierarchy_type=0001
1849            0b1100_0000 | 5,  // reserved=11 | layer_index=000101
1850            0b1100_0000 | 15, // reserved=11 | embedded_layer_index=001111
1851            0b1100_0000,      // reserved=11 | hierarchy_channel=000000
1852        ];
1853        let block = tlv(0x04, &body);
1854        let d = iter_descriptors(&block).next().unwrap().unwrap();
1855        match &d.body {
1856            DescriptorBody::Hierarchy(h) => {
1857                assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SpatialScalability);
1858                assert_eq!(h.hierarchy_type.as_nibble(), 1);
1859                assert!(!h.hierarchy_type.is_base_layer());
1860                assert_eq!(h.hierarchy_layer_index, 5);
1861                assert_eq!(h.hierarchy_embedded_layer_index, 15);
1862                assert_eq!(h.hierarchy_channel, 0);
1863            }
1864            other => panic!("expected Hierarchy, got {other:?}"),
1865        }
1866    }
1867
1868    #[test]
1869    fn hierarchy_descriptor_base_layer_marker() {
1870        // hierarchy_type = 15 (base layer). Per §2.6.7 the
1871        // embedded_layer_index is undefined; the parser still surfaces
1872        // whatever the wire bytes carry, and `is_base_layer` reports
1873        // `true` so the caller knows to disregard it.
1874        let body = [
1875            0b0000_1111, // reserved=0000 | hierarchy_type=1111
1876            0b0000_0001, // layer_index = 1
1877            0b0011_1111, // embedded_layer_index = 0x3F (undefined per §2.6.7)
1878            0b0000_0010, // hierarchy_channel = 2
1879        ];
1880        let block = tlv(0x04, &body);
1881        let d = iter_descriptors(&block).next().unwrap().unwrap();
1882        match &d.body {
1883            DescriptorBody::Hierarchy(h) => {
1884                assert_eq!(h.hierarchy_type, HierarchyType::BaseLayer);
1885                assert!(h.hierarchy_type.is_base_layer());
1886                assert_eq!(h.hierarchy_layer_index, 1);
1887                assert_eq!(h.hierarchy_embedded_layer_index, 0x3F);
1888                assert_eq!(h.hierarchy_channel, 2);
1889            }
1890            other => panic!("expected Hierarchy, got {other:?}"),
1891        }
1892    }
1893
1894    #[test]
1895    fn hierarchy_descriptor_all_typed_variants_round_trip() {
1896        // Walk every Table 2-44 mapping that has a named variant: 1..7
1897        // and 15. Confirm `from_nibble` round-trips through `as_nibble`
1898        // for each.
1899        let mapped = [
1900            (1u8, HierarchyType::Mpeg2SpatialScalability),
1901            (2, HierarchyType::Mpeg2SnrScalability),
1902            (3, HierarchyType::Mpeg2TemporalScalability),
1903            (4, HierarchyType::Mpeg2DataPartitioning),
1904            (5, HierarchyType::Mpeg2AudioExtension),
1905            (6, HierarchyType::PrivateStream),
1906            (7, HierarchyType::Mpeg2MultiviewProfile),
1907            (15, HierarchyType::BaseLayer),
1908        ];
1909        for (raw, expected) in mapped {
1910            let decoded = HierarchyType::from_nibble(raw);
1911            assert_eq!(decoded, expected, "wire nibble {raw}");
1912            assert_eq!(decoded.as_nibble(), raw, "round-trip for {raw}");
1913        }
1914        assert!(HierarchyType::BaseLayer.is_base_layer());
1915        // None of the non-base mappings flip `is_base_layer`.
1916        for (_, t) in mapped[..7].iter().copied() {
1917            assert!(!t.is_base_layer());
1918        }
1919    }
1920
1921    #[test]
1922    fn hierarchy_descriptor_reserved_values_preserve_raw_nibble() {
1923        // Values 0 and 8..=14 are reserved per Table 2-44. The parser
1924        // must keep them round-trippable so a caller can tell which
1925        // reserved nibble showed up on the wire.
1926        for raw in [0u8, 8, 9, 10, 11, 12, 13, 14] {
1927            let decoded = HierarchyType::from_nibble(raw);
1928            assert_eq!(decoded, HierarchyType::Reserved(raw));
1929            assert_eq!(decoded.as_nibble(), raw);
1930            assert!(!decoded.is_base_layer());
1931        }
1932        // The Unknown variant is only reachable when callers build a
1933        // value directly; `as_nibble` preserves whatever they stored.
1934        assert_eq!(HierarchyType::Unknown(0xAB).as_nibble(), 0xAB);
1935    }
1936
1937    #[test]
1938    fn hierarchy_descriptor_high_nibble_ignored_in_type_byte() {
1939        // The high four bits of byte 0 are reserved. Whatever value they
1940        // carry, the parser must mask them off before consulting
1941        // Table 2-44.
1942        let body = [0b1010_0011, 0, 0, 0]; // high nibble = 1010 → ignore
1943        let block = tlv(0x04, &body);
1944        let d = iter_descriptors(&block).next().unwrap().unwrap();
1945        match &d.body {
1946            DescriptorBody::Hierarchy(h) => {
1947                assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2TemporalScalability);
1948            }
1949            other => panic!("expected Hierarchy, got {other:?}"),
1950        }
1951    }
1952
1953    #[test]
1954    fn hierarchy_descriptor_short_body_falls_back_to_raw() {
1955        // Three bytes — one short of the four-byte Table 2-43 payload.
1956        let block = tlv(0x04, &[0x0F, 0xC0, 0xC0]);
1957        let d = iter_descriptors(&block).next().unwrap().unwrap();
1958        assert!(matches!(d.body, DescriptorBody::Raw));
1959        assert_eq!(d.data, &[0x0F, 0xC0, 0xC0]);
1960    }
1961
1962    #[test]
1963    fn hierarchy_descriptor_max_6bit_index_fields() {
1964        // Layer / embedded-layer / channel each saturated at the 6-bit
1965        // ceiling (0x3F). Reserved high bits left at 1 to verify the
1966        // mask. hierarchy_type = 2 (SNR scalability).
1967        let body = [
1968            0b0000_0010, // reserved=0 | hierarchy_type=0010
1969            0xFF,        // reserved=11 | layer_index=111111 = 63
1970            0xFF,        // reserved=11 | embedded_layer_index=111111 = 63
1971            0xFF,        // reserved=11 | hierarchy_channel=111111 = 63
1972        ];
1973        let block = tlv(0x04, &body);
1974        let d = iter_descriptors(&block).next().unwrap().unwrap();
1975        match &d.body {
1976            DescriptorBody::Hierarchy(h) => {
1977                assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SnrScalability);
1978                assert_eq!(h.hierarchy_layer_index, 0x3F);
1979                assert_eq!(h.hierarchy_embedded_layer_index, 0x3F);
1980                assert_eq!(h.hierarchy_channel, 0x3F);
1981            }
1982            other => panic!("expected Hierarchy, got {other:?}"),
1983        }
1984    }
1985
1986    #[test]
1987    fn hierarchy_descriptor_in_pmt_es_info_alongside_other_tags() {
1988        // Realistic ES_info block on an enhancement-layer PMT entry:
1989        // ISO-639 language + hierarchy + max-bitrate. The hierarchy
1990        // descriptor lands mid-list and surfaces its typed body without
1991        // disturbing neighbours.
1992        let mut block = Vec::new();
1993        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
1994        let h_body = [
1995            0b0000_0001, // hierarchy_type = spatial scalability
1996            0b0000_0010, // layer_index = 2
1997            0b0000_0000, // embedded_layer_index = 0 (base)
1998            0b0000_0001, // hierarchy_channel = 1
1999        ];
2000        block.extend_from_slice(&tlv(0x04, &h_body));
2001        block.extend_from_slice(&tlv(0x0E, &[0b1100_0000, 0x00, 0x64]));
2002        let v = parse_descriptors(&block).unwrap();
2003        assert_eq!(v.len(), 3);
2004        assert_eq!(v[0].tag, 0x0A);
2005        assert_eq!(v[1].tag, 0x04);
2006        match &v[1].body {
2007            DescriptorBody::Hierarchy(h) => {
2008                assert_eq!(h.hierarchy_type, HierarchyType::Mpeg2SpatialScalability);
2009                assert_eq!(h.hierarchy_layer_index, 2);
2010                assert_eq!(h.hierarchy_embedded_layer_index, 0);
2011                assert_eq!(h.hierarchy_channel, 1);
2012            }
2013            other => panic!("expected Hierarchy, got {other:?}"),
2014        }
2015        assert_eq!(v[2].tag, 0x0E);
2016        assert!(matches!(v[2].body, DescriptorBody::MaximumBitrate(_)));
2017    }
2018
2019    #[test]
2020    fn multiplex_buffer_utilization_descriptor_bounds_valid() {
2021        // bound_valid=1; pick two distinct 15-bit values that exercise
2022        // both halves of each bound. The 7-bit-then-8-bit packing means
2023        // byte 0 carries bits 14..8 of the lower bound (high 7 bits)
2024        // and byte 1 carries bits 7..0 (low 8 bits); same for byte 2/3.
2025        let lower: u16 = 0x1234; // 15-bit clean (top bit 0)
2026        let upper: u16 = 0x5678; // 15-bit clean (top bit 0)
2027        let body = [
2028            0x80 | ((lower >> 8) as u8 & 0x7F),
2029            lower as u8,
2030            (upper >> 8) as u8 & 0x7F,
2031            upper as u8,
2032        ];
2033        let block = tlv(0x0C, &body);
2034        let d = iter_descriptors(&block).next().unwrap().unwrap();
2035        match &d.body {
2036            DescriptorBody::MultiplexBufferUtilization(m) => {
2037                assert!(m.bound_valid_flag);
2038                assert_eq!(m.ltw_offset_lower_bound, lower);
2039                assert_eq!(m.ltw_offset_upper_bound, upper);
2040            }
2041            other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
2042        }
2043    }
2044
2045    #[test]
2046    fn multiplex_buffer_utilization_descriptor_bounds_invalid() {
2047        // bound_valid=0 -- bound fields are still surfaced verbatim per
2048        // §2.6.23 ("undefined when flag = 0"), but the caller is expected
2049        // to discard them. We still verify the parser doesn't crash on a
2050        // populated body.
2051        let body = [0x7F, 0xFF, 0x7F, 0xFF];
2052        let block = tlv(0x0C, &body);
2053        let d = iter_descriptors(&block).next().unwrap().unwrap();
2054        match &d.body {
2055            DescriptorBody::MultiplexBufferUtilization(m) => {
2056                assert!(!m.bound_valid_flag);
2057                assert_eq!(m.ltw_offset_lower_bound, 0x7FFF);
2058                assert_eq!(m.ltw_offset_upper_bound, 0x7FFF);
2059            }
2060            other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
2061        }
2062    }
2063
2064    #[test]
2065    fn multiplex_buffer_utilization_descriptor_short_body_falls_back_to_raw() {
2066        // Three bytes — one short of the four-byte Table 2-55 payload.
2067        let block = tlv(0x0C, &[0xA0, 0x00, 0x00]);
2068        let d = iter_descriptors(&block).next().unwrap().unwrap();
2069        assert!(matches!(d.body, DescriptorBody::Raw));
2070        assert_eq!(d.data, &[0xA0, 0x00, 0x00]);
2071    }
2072
2073    #[test]
2074    fn multiplex_buffer_utilization_descriptor_reserved_bit_ignored_on_read() {
2075        // The reserved bit at byte 2 MSB is ignored. Set bound_valid=1 +
2076        // a clean lower bound, and flip the reserved bit both ways on
2077        // the upper-bound byte — the upper bound must read the same.
2078        let lower = 0x0000u16;
2079        let upper = 0x0001u16; // smallest non-zero value
2080        let with_reserved_zero = [0x80, 0x00, (upper >> 8) as u8, upper as u8];
2081        let with_reserved_one = [0x80, 0x00, ((upper >> 8) as u8) | 0x80, upper as u8];
2082        let block0 = tlv(0x0C, &with_reserved_zero);
2083        let block1 = tlv(0x0C, &with_reserved_one);
2084        let d0 = iter_descriptors(&block0).next().unwrap().unwrap();
2085        let d1 = iter_descriptors(&block1).next().unwrap().unwrap();
2086        let unwrap = |b: &DescriptorBody<'_>| match b {
2087            DescriptorBody::MultiplexBufferUtilization(m) => *m,
2088            other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
2089        };
2090        let m0 = unwrap(&d0.body);
2091        let m1 = unwrap(&d1.body);
2092        assert_eq!(m0.ltw_offset_lower_bound, lower);
2093        assert_eq!(m0.ltw_offset_upper_bound, upper);
2094        assert_eq!(m0, m1);
2095    }
2096
2097    #[test]
2098    fn multiplex_buffer_utilization_descriptor_max_15bit_bounds() {
2099        // Saturate both bounds at the 15-bit ceiling (0x7FFF), with
2100        // bound_valid=1.
2101        // byte 0: 1 | 0x7F            = 0xFF
2102        // byte 1: 0xFF                = 0xFF
2103        // byte 2: reserved=1 | 0x7F   = 0xFF
2104        // byte 3: 0xFF                = 0xFF
2105        let body = [0xFF, 0xFF, 0xFF, 0xFF];
2106        let block = tlv(0x0C, &body);
2107        let d = iter_descriptors(&block).next().unwrap().unwrap();
2108        match &d.body {
2109            DescriptorBody::MultiplexBufferUtilization(m) => {
2110                assert!(m.bound_valid_flag);
2111                assert_eq!(m.ltw_offset_lower_bound, 0x7FFF);
2112                assert_eq!(m.ltw_offset_upper_bound, 0x7FFF);
2113            }
2114            other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
2115        }
2116    }
2117
2118    #[test]
2119    fn multiplex_buffer_utilization_descriptor_in_es_info_list() {
2120        // Realistic PMT ES_info block: language + smoothing-buffer +
2121        // multiplex-buffer-utilization. The 0x0C descriptor lands at the
2122        // end and decodes alongside neighbours without disturbing them.
2123        let mut block = Vec::new();
2124        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
2125        // smoothing_buffer: leak_rate = 0x010203, sb_size = 0x040506
2126        block.extend_from_slice(&tlv(0x10, &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]));
2127        // multiplex_buffer_utilization: bound_valid=1, lower=0x0100, upper=0x0200
2128        // byte 0 = 0x80 | (0x0100 >> 8) = 0x81 ; byte 1 = 0x00
2129        // byte 2 = (0x0200 >> 8) = 0x02 ; byte 3 = 0x00
2130        block.extend_from_slice(&tlv(0x0C, &[0x81, 0x00, 0x02, 0x00]));
2131        let v = parse_descriptors(&block).unwrap();
2132        assert_eq!(v.len(), 3);
2133        assert_eq!(v[0].tag, 0x0A);
2134        assert_eq!(v[1].tag, 0x10);
2135        assert_eq!(v[2].tag, 0x0C);
2136        match &v[2].body {
2137            DescriptorBody::MultiplexBufferUtilization(m) => {
2138                assert!(m.bound_valid_flag);
2139                assert_eq!(m.ltw_offset_lower_bound, 0x0100);
2140                assert_eq!(m.ltw_offset_upper_bound, 0x0200);
2141            }
2142            other => panic!("expected MultiplexBufferUtilization, got {other:?}"),
2143        }
2144    }
2145
2146    #[test]
2147    fn parse_descriptors_helper_collects_all() {
2148        let mut block = Vec::new();
2149        block.extend_from_slice(&tlv(0x05, b"HDMV"));
2150        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
2151        block.extend_from_slice(&tlv(0xFF, &[]));
2152        let v = parse_descriptors(&block).unwrap();
2153        assert_eq!(v.len(), 3);
2154        assert_eq!(v[0].tag, 0x05);
2155        assert_eq!(v[1].tag, 0x0A);
2156        assert_eq!(v[2].tag, 0xFF);
2157        assert!(matches!(v[2].body, DescriptorBody::Raw));
2158        assert!(v[2].data.is_empty());
2159    }
2160
2161    #[test]
2162    fn ibp_descriptor_all_flags_set_max_length() {
2163        // closed=1, identical=1, max_gop_length = 0x3FFF (largest 14-bit).
2164        // byte 0: 1 | 1 | 0x3F          = 0xFF
2165        // byte 1: 0xFF                  = 0xFF
2166        let block = tlv(0x12, &[0xFF, 0xFF]);
2167        let d = iter_descriptors(&block).next().unwrap().unwrap();
2168        assert_eq!(d.tag, 0x12);
2169        match &d.body {
2170            DescriptorBody::Ibp(ibp) => {
2171                assert!(ibp.closed_gop_flag);
2172                assert!(ibp.identical_gop_flag);
2173                assert_eq!(ibp.max_gop_length, 0x3FFF);
2174                assert!(ibp.is_well_formed());
2175            }
2176            other => panic!("expected Ibp, got {other:?}"),
2177        }
2178    }
2179
2180    #[test]
2181    fn ibp_descriptor_flags_clear_typical_length() {
2182        // closed=0, identical=0, max_gop_length = 15 (typical IBP GOP of 15).
2183        // byte 0: 0 | 0 | 0x00          = 0x00
2184        // byte 1: 0x0F                  = 0x0F
2185        let block = tlv(0x12, &[0x00, 0x0F]);
2186        let d = iter_descriptors(&block).next().unwrap().unwrap();
2187        match &d.body {
2188            DescriptorBody::Ibp(ibp) => {
2189                assert!(!ibp.closed_gop_flag);
2190                assert!(!ibp.identical_gop_flag);
2191                assert_eq!(ibp.max_gop_length, 15);
2192                assert!(ibp.is_well_formed());
2193            }
2194            other => panic!("expected Ibp, got {other:?}"),
2195        }
2196    }
2197
2198    #[test]
2199    fn ibp_descriptor_closed_only_mid_length() {
2200        // closed=1, identical=0, max_gop_length = 0x012C (300).
2201        // byte 0: 1 | 0 | (0x012C >> 8 = 0x01)  = 0x81
2202        // byte 1: 0x2C                           = 0x2C
2203        let block = tlv(0x12, &[0x81, 0x2C]);
2204        let d = iter_descriptors(&block).next().unwrap().unwrap();
2205        match &d.body {
2206            DescriptorBody::Ibp(ibp) => {
2207                assert!(ibp.closed_gop_flag);
2208                assert!(!ibp.identical_gop_flag);
2209                assert_eq!(ibp.max_gop_length, 0x012C);
2210            }
2211            other => panic!("expected Ibp, got {other:?}"),
2212        }
2213    }
2214
2215    #[test]
2216    fn ibp_descriptor_identical_only_mid_length() {
2217        // closed=0, identical=1, max_gop_length = 0x002A (42).
2218        // byte 0: 0 | 1 | 0x00          = 0x40
2219        // byte 1: 0x2A                  = 0x2A
2220        let block = tlv(0x12, &[0x40, 0x2A]);
2221        let d = iter_descriptors(&block).next().unwrap().unwrap();
2222        match &d.body {
2223            DescriptorBody::Ibp(ibp) => {
2224                assert!(!ibp.closed_gop_flag);
2225                assert!(ibp.identical_gop_flag);
2226                assert_eq!(ibp.max_gop_length, 42);
2227            }
2228            other => panic!("expected Ibp, got {other:?}"),
2229        }
2230    }
2231
2232    #[test]
2233    fn ibp_descriptor_forbidden_zero_max_gop_length_flagged() {
2234        // Spec §2.6.35 forbids max_gop_length = 0; parser still surfaces
2235        // the value so callers can detect malformed streams.
2236        let block = tlv(0x12, &[0x00, 0x00]);
2237        let d = iter_descriptors(&block).next().unwrap().unwrap();
2238        match &d.body {
2239            DescriptorBody::Ibp(ibp) => {
2240                assert_eq!(ibp.max_gop_length, 0);
2241                assert!(!ibp.is_well_formed());
2242            }
2243            other => panic!("expected Ibp, got {other:?}"),
2244        }
2245    }
2246
2247    #[test]
2248    fn ibp_descriptor_truncated_falls_back_to_raw() {
2249        // 1-byte body — short of the fixed 2-byte payload, so the
2250        // typed decoder declines and the generic TLV layer keeps the
2251        // raw bytes.
2252        let block = tlv(0x12, &[0xFF]);
2253        let d = iter_descriptors(&block).next().unwrap().unwrap();
2254        assert_eq!(d.tag, 0x12);
2255        assert!(matches!(d.body, DescriptorBody::Raw));
2256        assert_eq!(d.data, &[0xFF]);
2257    }
2258
2259    #[test]
2260    fn ibp_descriptor_in_es_info_list_with_neighbours() {
2261        // Realistic PMT ES_info block carrying language + STD + IBP.
2262        // The IBP record lands in the middle and decodes alongside
2263        // neighbours without disturbing them.
2264        let mut block = Vec::new();
2265        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
2266        // ibp: closed=1, identical=0, max_gop_length=18 (typical
2267        // broadcast-MPEG-2 closed-GOP cadence).
2268        block.extend_from_slice(&tlv(0x12, &[0x80, 0x12]));
2269        // STD: leak_valid_flag=1
2270        block.extend_from_slice(&tlv(0x11, &[0x01]));
2271        let v = parse_descriptors(&block).unwrap();
2272        assert_eq!(v.len(), 3);
2273        assert_eq!(v[0].tag, 0x0A);
2274        assert_eq!(v[1].tag, 0x12);
2275        assert_eq!(v[2].tag, 0x11);
2276        match &v[1].body {
2277            DescriptorBody::Ibp(ibp) => {
2278                assert!(ibp.closed_gop_flag);
2279                assert!(!ibp.identical_gop_flag);
2280                assert_eq!(ibp.max_gop_length, 18);
2281                assert!(ibp.is_well_formed());
2282            }
2283            other => panic!("expected Ibp, got {other:?}"),
2284        }
2285    }
2286
2287    #[test]
2288    fn target_background_grid_descriptor_typical_sd() {
2289        // horizontal_size = 720, vertical_size = 576, aspect = 3 (4:3
2290        // per H.262 aspect_ratio_information). 720 = 0x2D0, 576 = 0x240.
2291        //   byte 0: horizontal[13..6] = 0x2D0 >> 6 = 0x0B          = 0x0B
2292        //   byte 1: horizontal[5..0]<<2 | vertical[13..12]
2293        //           = (0x2D0 & 0x3F) << 2 | (0x240 >> 12)
2294        //           = (0x10) << 2 | 0 = 0x40                       = 0x40
2295        //   byte 2: vertical[11..4] = (0x240 >> 4) & 0xFF = 0x24   = 0x24
2296        //   byte 3: vertical[3..0]<<4 | aspect = (0x240 & 0xF)<<4 | 3
2297        //           = 0x00 | 0x03                                  = 0x03
2298        let block = tlv(0x07, &[0x0B, 0x40, 0x24, 0x03]);
2299        let d = iter_descriptors(&block).next().unwrap().unwrap();
2300        assert_eq!(d.tag, 0x07);
2301        match &d.body {
2302            DescriptorBody::TargetBackgroundGrid(g) => {
2303                assert_eq!(g.horizontal_size, 720);
2304                assert_eq!(g.vertical_size, 576);
2305                assert_eq!(g.aspect_ratio_information, 3);
2306            }
2307            other => panic!("expected TargetBackgroundGrid, got {other:?}"),
2308        }
2309    }
2310
2311    #[test]
2312    fn target_background_grid_descriptor_max_fields() {
2313        // horizontal_size = 0x3FFF, vertical_size = 0x3FFF, aspect = 0xF
2314        // — every payload bit set.
2315        //   byte 0: 0x3FFF >> 6 = 0xFF
2316        //   byte 1: (0x3FFF & 0x3F) << 2 | (0x3FFF >> 12)
2317        //           = (0x3F << 2) | 0x3 = 0xFC | 0x3 = 0xFF
2318        //   byte 2: (0x3FFF >> 4) & 0xFF = 0xFF
2319        //   byte 3: (0x3FFF & 0xF) << 4 | 0xF = 0xF0 | 0xF = 0xFF
2320        let block = tlv(0x07, &[0xFF, 0xFF, 0xFF, 0xFF]);
2321        let d = iter_descriptors(&block).next().unwrap().unwrap();
2322        match &d.body {
2323            DescriptorBody::TargetBackgroundGrid(g) => {
2324                assert_eq!(g.horizontal_size, 0x3FFF);
2325                assert_eq!(g.vertical_size, 0x3FFF);
2326                assert_eq!(g.aspect_ratio_information, 0xF);
2327            }
2328            other => panic!("expected TargetBackgroundGrid, got {other:?}"),
2329        }
2330    }
2331
2332    #[test]
2333    fn target_background_grid_descriptor_all_zero() {
2334        let block = tlv(0x07, &[0x00, 0x00, 0x00, 0x00]);
2335        let d = iter_descriptors(&block).next().unwrap().unwrap();
2336        match &d.body {
2337            DescriptorBody::TargetBackgroundGrid(g) => {
2338                assert_eq!(g.horizontal_size, 0);
2339                assert_eq!(g.vertical_size, 0);
2340                assert_eq!(g.aspect_ratio_information, 0);
2341            }
2342            other => panic!("expected TargetBackgroundGrid, got {other:?}"),
2343        }
2344    }
2345
2346    #[test]
2347    fn target_background_grid_descriptor_short_body_falls_back_to_raw() {
2348        // 3-byte body — short of the fixed 4-byte payload, so the typed
2349        // decoder declines and the generic TLV layer keeps the raw bytes.
2350        let block = tlv(0x07, &[0x0B, 0x40, 0x24]);
2351        let d = iter_descriptors(&block).next().unwrap().unwrap();
2352        assert_eq!(d.tag, 0x07);
2353        assert!(matches!(d.body, DescriptorBody::Raw));
2354        assert_eq!(d.data, &[0x0B, 0x40, 0x24]);
2355    }
2356
2357    #[test]
2358    fn target_background_grid_descriptor_in_es_info_list_with_neighbours() {
2359        // Realistic PMT ES_info block: language + target_background_grid
2360        // + STD. The grid record lands in the middle and decodes alongside
2361        // neighbours without disturbing them. 1920×1080, aspect = 1.
2362        // 1920 = 0x780, 1080 = 0x438.
2363        //   byte 0: 0x780 >> 6 = 0x1E
2364        //   byte 1: (0x780 & 0x3F) << 2 | (0x438 >> 12)
2365        //           = (0x00) << 2 | 0 = 0x00
2366        //   byte 2: (0x438 >> 4) & 0xFF = 0x43
2367        //   byte 3: (0x438 & 0xF) << 4 | 1 = 0x80 | 0x01 = 0x81
2368        let mut block = Vec::new();
2369        block.extend_from_slice(&tlv(0x0A, &[b'e', b'n', b'g', 0x00]));
2370        block.extend_from_slice(&tlv(0x07, &[0x1E, 0x00, 0x43, 0x81]));
2371        block.extend_from_slice(&tlv(0x11, &[0x01]));
2372        let v = parse_descriptors(&block).unwrap();
2373        assert_eq!(v.len(), 3);
2374        assert_eq!(v[0].tag, 0x0A);
2375        assert_eq!(v[1].tag, 0x07);
2376        assert_eq!(v[2].tag, 0x11);
2377        match &v[1].body {
2378            DescriptorBody::TargetBackgroundGrid(g) => {
2379                assert_eq!(g.horizontal_size, 1920);
2380                assert_eq!(g.vertical_size, 1080);
2381                assert_eq!(g.aspect_ratio_information, 1);
2382            }
2383            other => panic!("expected TargetBackgroundGrid, got {other:?}"),
2384        }
2385    }
2386
2387    #[test]
2388    fn video_window_descriptor_typical_offsets() {
2389        // horizontal_offset = 320, vertical_offset = 240, priority = 5.
2390        // 320 = 0x140, 240 = 0x0F0.
2391        //   byte 0: horizontal[13..6] = 0x140 >> 6 = 0x05         = 0x05
2392        //   byte 1: horizontal[5..0]<<2 | vertical[13..12]
2393        //           = (0x140 & 0x3F) << 2 | (0x0F0 >> 12)
2394        //           = (0x00) << 2 | 0 = 0x00                      = 0x00
2395        //   byte 2: vertical[11..4] = (0x0F0 >> 4) & 0xFF = 0x0F  = 0x0F
2396        //   byte 3: vertical[3..0]<<4 | priority = (0x0F0 & 0xF)<<4 | 5
2397        //           = 0x00 | 0x05                                 = 0x05
2398        let block = tlv(0x08, &[0x05, 0x00, 0x0F, 0x05]);
2399        let d = iter_descriptors(&block).next().unwrap().unwrap();
2400        assert_eq!(d.tag, 0x08);
2401        match &d.body {
2402            DescriptorBody::VideoWindow(w) => {
2403                assert_eq!(w.horizontal_offset, 320);
2404                assert_eq!(w.vertical_offset, 240);
2405                assert_eq!(w.window_priority, 5);
2406            }
2407            other => panic!("expected VideoWindow, got {other:?}"),
2408        }
2409    }
2410
2411    #[test]
2412    fn video_window_descriptor_max_fields() {
2413        // horizontal_offset = 0x3FFF, vertical_offset = 0x3FFF,
2414        // priority = 0xF — every payload bit set.
2415        //   byte 0: 0x3FFF >> 6 = 0xFF
2416        //   byte 1: (0x3FFF & 0x3F) << 2 | (0x3FFF >> 12) = 0xFC | 0x3 = 0xFF
2417        //   byte 2: (0x3FFF >> 4) & 0xFF = 0xFF
2418        //   byte 3: (0x3FFF & 0xF) << 4 | 0xF = 0xF0 | 0xF = 0xFF
2419        let block = tlv(0x08, &[0xFF, 0xFF, 0xFF, 0xFF]);
2420        let d = iter_descriptors(&block).next().unwrap().unwrap();
2421        match &d.body {
2422            DescriptorBody::VideoWindow(w) => {
2423                assert_eq!(w.horizontal_offset, 0x3FFF);
2424                assert_eq!(w.vertical_offset, 0x3FFF);
2425                assert_eq!(w.window_priority, 0xF);
2426            }
2427            other => panic!("expected VideoWindow, got {other:?}"),
2428        }
2429    }
2430
2431    #[test]
2432    fn video_window_descriptor_all_zero() {
2433        let block = tlv(0x08, &[0x00, 0x00, 0x00, 0x00]);
2434        let d = iter_descriptors(&block).next().unwrap().unwrap();
2435        match &d.body {
2436            DescriptorBody::VideoWindow(w) => {
2437                assert_eq!(w.horizontal_offset, 0);
2438                assert_eq!(w.vertical_offset, 0);
2439                assert_eq!(w.window_priority, 0);
2440            }
2441            other => panic!("expected VideoWindow, got {other:?}"),
2442        }
2443    }
2444
2445    #[test]
2446    fn video_window_descriptor_short_body_falls_back_to_raw() {
2447        // 3-byte body — short of the fixed 4-byte payload, so the typed
2448        // decoder declines and the generic TLV layer keeps the raw bytes.
2449        let block = tlv(0x08, &[0x05, 0x00, 0x0F]);
2450        let d = iter_descriptors(&block).next().unwrap().unwrap();
2451        assert_eq!(d.tag, 0x08);
2452        assert!(matches!(d.body, DescriptorBody::Raw));
2453        assert_eq!(d.data, &[0x05, 0x00, 0x0F]);
2454    }
2455
2456    #[test]
2457    fn video_window_descriptor_pairs_with_grid_in_es_info_list() {
2458        // Realistic PMT ES_info block: a target_background_grid_descriptor
2459        // (the 1920×1080 grid) immediately followed by the
2460        // video_window_descriptor that places this stream's window on it.
2461        // Window at offset (100, 50), priority 7. 100 = 0x064, 50 = 0x032.
2462        //   byte 0: 0x064 >> 6 = 0x01
2463        //   byte 1: (0x064 & 0x3F) << 2 | (0x032 >> 12)
2464        //           = (0x24) << 2 | 0 = 0x90
2465        //   byte 2: (0x032 >> 4) & 0xFF = 0x03
2466        //   byte 3: (0x032 & 0xF) << 4 | 7 = 0x20 | 0x07 = 0x27
2467        let mut block = Vec::new();
2468        block.extend_from_slice(&tlv(0x07, &[0x1E, 0x00, 0x43, 0x81]));
2469        block.extend_from_slice(&tlv(0x08, &[0x01, 0x90, 0x03, 0x27]));
2470        let v = parse_descriptors(&block).unwrap();
2471        assert_eq!(v.len(), 2);
2472        assert_eq!(v[0].tag, 0x07);
2473        assert_eq!(v[1].tag, 0x08);
2474        match &v[0].body {
2475            DescriptorBody::TargetBackgroundGrid(g) => {
2476                assert_eq!(g.horizontal_size, 1920);
2477                assert_eq!(g.vertical_size, 1080);
2478            }
2479            other => panic!("expected TargetBackgroundGrid, got {other:?}"),
2480        }
2481        match &v[1].body {
2482            DescriptorBody::VideoWindow(w) => {
2483                assert_eq!(w.horizontal_offset, 100);
2484                assert_eq!(w.vertical_offset, 50);
2485                assert_eq!(w.window_priority, 7);
2486            }
2487            other => panic!("expected VideoWindow, got {other:?}"),
2488        }
2489    }
2490
2491    #[test]
2492    fn service_descriptor_decodes_names_and_type() {
2493        // service_type = 0x01 (digital television),
2494        // provider = "Prov", service = "Channel 1".
2495        let mut body = vec![0x01];
2496        body.push(4);
2497        body.extend_from_slice(b"Prov");
2498        body.push(9);
2499        body.extend_from_slice(b"Channel 1");
2500        let block = tlv(0x48, &body);
2501        let d = iter_descriptors(&block).next().unwrap().unwrap();
2502        assert_eq!(d.tag, 0x48);
2503        match d.body {
2504            DescriptorBody::Service(s) => {
2505                assert_eq!(s.service_type, 0x01);
2506                assert_eq!(s.service_provider_name, b"Prov");
2507                assert_eq!(s.service_name, b"Channel 1");
2508            }
2509            other => panic!("expected Service, got {other:?}"),
2510        }
2511    }
2512
2513    #[test]
2514    fn service_descriptor_empty_provider_name() {
2515        // Zero-length provider, non-empty service name.
2516        let mut body = vec![0x11]; // HD digital television
2517        body.push(0);
2518        body.push(3);
2519        body.extend_from_slice(b"HD1");
2520        let block = tlv(0x48, &body);
2521        let d = iter_descriptors(&block).next().unwrap().unwrap();
2522        match d.body {
2523            DescriptorBody::Service(s) => {
2524                assert_eq!(s.service_type, 0x11);
2525                assert!(s.service_provider_name.is_empty());
2526                assert_eq!(s.service_name, b"HD1");
2527            }
2528            other => panic!("expected Service, got {other:?}"),
2529        }
2530    }
2531
2532    #[test]
2533    fn service_descriptor_truncated_falls_back_to_raw() {
2534        // provider_name_length claims 9 bytes but only 2 follow.
2535        let block = tlv(0x48, &[0x01, 0x09, b'X', b'Y']);
2536        let d = iter_descriptors(&block).next().unwrap().unwrap();
2537        assert_eq!(d.tag, 0x48);
2538        assert!(matches!(d.body, DescriptorBody::Raw));
2539        assert_eq!(d.data, &[0x01, 0x09, b'X', b'Y']);
2540    }
2541
2542    #[test]
2543    fn short_event_descriptor_decodes_name_and_text() {
2544        // ISO_639 = "eng", name = "News", text = "Daily bulletin".
2545        let mut body = Vec::new();
2546        body.extend_from_slice(b"eng");
2547        body.push(4);
2548        body.extend_from_slice(b"News");
2549        body.push(14);
2550        body.extend_from_slice(b"Daily bulletin");
2551        let block = tlv(0x4D, &body);
2552        let d = iter_descriptors(&block).next().unwrap().unwrap();
2553        assert_eq!(d.tag, 0x4D);
2554        match d.body {
2555            DescriptorBody::ShortEvent(s) => {
2556                assert_eq!(&s.language_code, b"eng");
2557                assert_eq!(s.event_name, b"News");
2558                assert_eq!(s.text, b"Daily bulletin");
2559            }
2560            other => panic!("expected ShortEvent, got {other:?}"),
2561        }
2562    }
2563
2564    #[test]
2565    fn short_event_descriptor_empty_text() {
2566        // Zero-length text run after a non-empty name.
2567        let mut body = Vec::new();
2568        body.extend_from_slice(b"fre");
2569        body.push(5);
2570        body.extend_from_slice(b"Match");
2571        body.push(0);
2572        let block = tlv(0x4D, &body);
2573        let d = iter_descriptors(&block).next().unwrap().unwrap();
2574        match d.body {
2575            DescriptorBody::ShortEvent(s) => {
2576                assert_eq!(&s.language_code, b"fre");
2577                assert_eq!(s.event_name, b"Match");
2578                assert!(s.text.is_empty());
2579            }
2580            other => panic!("expected ShortEvent, got {other:?}"),
2581        }
2582    }
2583
2584    #[test]
2585    fn short_event_descriptor_truncated_falls_back_to_raw() {
2586        // name_length claims 9 bytes but only 2 follow the language code.
2587        let block = tlv(0x4D, &[b'e', b'n', b'g', 0x09, b'X', b'Y']);
2588        let d = iter_descriptors(&block).next().unwrap().unwrap();
2589        assert_eq!(d.tag, 0x4D);
2590        assert!(matches!(d.body, DescriptorBody::Raw));
2591    }
2592}