Skip to main content

dvb_si/tables/
pmt.rs

1//! Program Map Table — MPEG-2 ISO/IEC 13818-1 §2.4.4.8.
2//!
3//! PMT describes the elementary streams that make up one programme.
4//! Carried on a per-programme PID signalled by the PAT, with table_id 0x02.
5//! Descriptor parsing is out of scope for this commit — raw bytes only.
6
7use crate::descriptors::DescriptorLoop;
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use broadcast_common::{Parse, Serialize};
11
12/// PMT table_id (ISO/IEC 13818-1 Table 2-30).
13pub const TABLE_ID: u8 = 0x02;
14/// PMT PIDs are programme-specific and signalled via PAT; 0x0000 is a
15/// placeholder meaning "no well-known PID".
16pub const PID: u16 = 0x0000;
17
18const MIN_HEADER_LEN: usize = 3;
19const EXTENSION_HEADER_LEN: usize = 5;
20const PCR_PID_LEN: usize = 2;
21const PROG_INFO_LEN_BYTES: usize = 2;
22const CRC_LEN: usize = 4;
23const MIN_SECTION_LEN: usize =
24    MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
25const STREAM_HEADER_LEN: usize = 5;
26
27/// Stream type coding — Rec. ITU-T H.222.0 (06/2021) Table 2-34.
28///
29/// Identifies the elementary-stream type carried in the associated PID.
30/// Values `0x80`–`0xFF` are user private; only well-established entries
31/// cited to their own specs are named — the rest fall through to
32/// [`UserPrivate`](Self::UserPrivate).
33///
34/// # Examples
35/// ```
36/// use dvb_si::tables::pmt::StreamType;
37///
38/// assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
39/// assert_eq!(StreamType::from_u8(0x1B).to_u8(), 0x1B); // H.264, lossless round-trip
40/// ```
41/// A parsed `PmtStream.stream_type` is already a `StreamType` — match on it directly.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[non_exhaustive]
45pub enum StreamType {
46    /// 0x00 — ITU-T | ISO/IEC Reserved.
47    Reserved,
48    /// 0x01 — ISO/IEC 11172-2 Video (MPEG-1 video).
49    Mpeg1Video,
50    /// 0x02 — Rec. ITU-T H.262 | ISO/IEC 13818-2 Video (MPEG-2 video).
51    Mpeg2Video,
52    /// 0x03 — ISO/IEC 11172-3 Audio (MPEG-1 audio).
53    Mpeg1Audio,
54    /// 0x04 — ISO/IEC 13818-3 Audio (MPEG-2 audio).
55    Mpeg2Audio,
56    /// 0x05 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 private_sections.
57    PrivateSections,
58    /// 0x06 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 PES packets containing private data.
59    PesPrivateData,
60    /// 0x07 — ISO/IEC 13522 MHEG.
61    Mheg,
62    /// 0x08 — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC.
63    DsmCc,
64    /// 0x09 — Rec. ITU-T H.222.1.
65    H222_1,
66    /// 0x0A — ISO/IEC 13818-6 type A.
67    Iso13818_6TypeA,
68    /// 0x0B — ISO/IEC 13818-6 type B.
69    Iso13818_6TypeB,
70    /// 0x0C — ISO/IEC 13818-6 type C.
71    Iso13818_6TypeC,
72    /// 0x0D — ISO/IEC 13818-6 type D.
73    Iso13818_6TypeD,
74    /// 0x0E — Rec. ITU-T H.222.0 | ISO/IEC 13818-1 auxiliary.
75    Auxiliary,
76    /// 0x0F — ISO/IEC 13818-7 Audio with ADTS transport syntax (AAC).
77    AacAdts,
78    /// 0x10 — ISO/IEC 14496-2 Visual (MPEG-4 video).
79    Mpeg4Video,
80    /// 0x11 — ISO/IEC 14496-3 Audio with LATM transport syntax (AAC LATM).
81    AacLatm,
82    /// 0x12 — ISO/IEC 14496-1 SL-packetized / FlexMux stream in PES.
83    SlFlexMuxPes,
84    /// 0x13 — ISO/IEC 14496-1 SL-packetized / FlexMux stream in sections.
85    SlFlexMuxSections,
86    /// 0x14 — ISO/IEC 13818-6 Synchronized Download Protocol.
87    SyncDownload,
88    /// 0x15 — Metadata carried in PES packets.
89    MetadataPes,
90    /// 0x16 — Metadata carried in metadata_sections.
91    MetadataSections,
92    /// 0x17 — Metadata carried in ISO/IEC 13818-6 Data Carousel.
93    MetadataDataCarousel,
94    /// 0x18 — Metadata carried in ISO/IEC 13818-6 Object Carousel.
95    MetadataObjectCarousel,
96    /// 0x19 — Metadata carried in ISO/IEC 13818-6 Synchronized Download Protocol.
97    MetadataSyncDownload,
98    /// 0x1A — IPMP stream (ISO/IEC 13818-11, MPEG-2 IPMP).
99    Ipmp,
100    /// 0x1B — AVC video stream (Rec. ITU-T H.264 | ISO/IEC 14496-10).
101    H264,
102    /// 0x1C — ISO/IEC 14496-3 Audio without additional transport syntax (DST, ALS, SLS).
103    Iso14496_3Audio,
104    /// 0x1D — ISO/IEC 14496-17 Text.
105    Iso14496_17Text,
106    /// 0x1E — Auxiliary video stream (ISO/IEC 23002-3).
107    AuxiliaryVideo,
108    /// 0x1F — SVC video sub-bitstream of an AVC video stream (H.264 Annex G).
109    Svc,
110    /// 0x20 — MVC video sub-bitstream of an AVC video stream (H.264 Annex H).
111    Mvc,
112    /// 0x21 — JPEG 2000 video (Rec. ITU-T T.800 | ISO/IEC 15444-1).
113    Jpeg2000,
114    /// 0x22 — Additional view H.262 | ISO/IEC 13818-2 video for service-compatible stereoscopic 3D.
115    AdditionalViewH262,
116    /// 0x23 — Additional view H.264 | ISO/IEC 14496-10 video for service-compatible stereoscopic 3D.
117    AdditionalViewH264,
118    /// 0x24 — Rec. ITU-T H.265 | ISO/IEC 23008-2 video (HEVC) or HEVC temporal sub-bitstream.
119    Hevc,
120    /// 0x25 — HEVC temporal video subset (H.265 Annex A profiles).
121    HevcTemporalSubset,
122    /// 0x26 — MVCD video sub-bitstream of an AVC video stream (H.264 Annex I).
123    Mvcd,
124    /// 0x27 — Timeline and External Media Information (TEMI, H.222.0 Annex U).
125    Temi,
126    /// 0x28 — HEVC enhancement sub-partition incl. TemporalId 0 (H.265 Annex G).
127    HevcAnnexG,
128    /// 0x29 — HEVC temporal enhancement sub-partition (H.265 Annex G).
129    HevcAnnexGTemporal,
130    /// 0x2A — HEVC enhancement sub-partition incl. TemporalId 0 (H.265 Annex H).
131    HevcAnnexH,
132    /// 0x2B — HEVC temporal enhancement sub-partition (H.265 Annex H).
133    HevcAnnexHTemporal,
134    /// 0x2C — Green access units carried in MPEG-2 sections.
135    GreenAccessUnits,
136    /// 0x2D — ISO/IEC 23008-3 Audio with MHAS transport syntax — main stream.
137    MhasAudioMain,
138    /// 0x2E — ISO/IEC 23008-3 Audio with MHAS transport syntax — auxiliary stream.
139    MhasAudioAux,
140    /// 0x2F — Quality access units carried in sections.
141    QualityAccessUnits,
142    /// 0x30 — Media Orchestration Access Units carried in sections.
143    MediaOrchestration,
144    /// 0x31 — MCTS substream of an H.265 | ISO/IEC 23008-2 video stream.
145    MctsHevc,
146    /// 0x32 — JPEG XS video stream (ISO/IEC 21122-2 profiles).
147    JpegXs,
148    /// 0x33 — VVC video stream (Rec. ITU-T H.266 | ISO/IEC 23090-3) or VVC temporal sub-bitstream.
149    Vvc,
150    /// 0x34 — VVC temporal video subset (H.266 Annex A profiles).
151    VvcTemporalSubset,
152    /// 0x35 — EVC video stream or EVC temporal sub-bitstream (ISO/IEC 23094-1).
153    Evc,
154    /// 0x81 — ATSC AC-3 audio (A/52).
155    Ac3,
156    /// 0x86 — SCTE-35 splice_info_section (ANSI/SCTE 35).
157    Scte35,
158    /// 0x87 — ATSC E-AC-3 / Dolby Digital Plus audio (A/52B).
159    EAc3,
160    /// 0x7F — IPMP stream (H.222.0 Table 2-34).
161    IpmpHigh,
162    /// Rec. ITU-T H.222.0 reserved range `0x36`..=`0x7E`.
163    ReservedRange(u8),
164    /// User private range `0x80`..=`0xFF` (except named entries).
165    UserPrivate(u8),
166}
167
168impl StreamType {
169    /// Decode from the wire byte. Every byte maps to a variant (lossless).
170    #[must_use]
171    pub fn from_u8(v: u8) -> Self {
172        match v {
173            0x00 => Self::Reserved,
174            0x01 => Self::Mpeg1Video,
175            0x02 => Self::Mpeg2Video,
176            0x03 => Self::Mpeg1Audio,
177            0x04 => Self::Mpeg2Audio,
178            0x05 => Self::PrivateSections,
179            0x06 => Self::PesPrivateData,
180            0x07 => Self::Mheg,
181            0x08 => Self::DsmCc,
182            0x09 => Self::H222_1,
183            0x0A => Self::Iso13818_6TypeA,
184            0x0B => Self::Iso13818_6TypeB,
185            0x0C => Self::Iso13818_6TypeC,
186            0x0D => Self::Iso13818_6TypeD,
187            0x0E => Self::Auxiliary,
188            0x0F => Self::AacAdts,
189            0x10 => Self::Mpeg4Video,
190            0x11 => Self::AacLatm,
191            0x12 => Self::SlFlexMuxPes,
192            0x13 => Self::SlFlexMuxSections,
193            0x14 => Self::SyncDownload,
194            0x15 => Self::MetadataPes,
195            0x16 => Self::MetadataSections,
196            0x17 => Self::MetadataDataCarousel,
197            0x18 => Self::MetadataObjectCarousel,
198            0x19 => Self::MetadataSyncDownload,
199            0x1A => Self::Ipmp,
200            0x1B => Self::H264,
201            0x1C => Self::Iso14496_3Audio,
202            0x1D => Self::Iso14496_17Text,
203            0x1E => Self::AuxiliaryVideo,
204            0x1F => Self::Svc,
205            0x20 => Self::Mvc,
206            0x21 => Self::Jpeg2000,
207            0x22 => Self::AdditionalViewH262,
208            0x23 => Self::AdditionalViewH264,
209            0x24 => Self::Hevc,
210            0x25 => Self::HevcTemporalSubset,
211            0x26 => Self::Mvcd,
212            0x27 => Self::Temi,
213            0x28 => Self::HevcAnnexG,
214            0x29 => Self::HevcAnnexGTemporal,
215            0x2A => Self::HevcAnnexH,
216            0x2B => Self::HevcAnnexHTemporal,
217            0x2C => Self::GreenAccessUnits,
218            0x2D => Self::MhasAudioMain,
219            0x2E => Self::MhasAudioAux,
220            0x2F => Self::QualityAccessUnits,
221            0x30 => Self::MediaOrchestration,
222            0x31 => Self::MctsHevc,
223            0x32 => Self::JpegXs,
224            0x33 => Self::Vvc,
225            0x34 => Self::VvcTemporalSubset,
226            0x35 => Self::Evc,
227            0x36..=0x7E => Self::ReservedRange(v),
228            0x7F => Self::IpmpHigh,
229            0x81 => Self::Ac3,
230            0x86 => Self::Scte35,
231            0x87 => Self::EAc3,
232            _ => Self::UserPrivate(v),
233        }
234    }
235
236    /// Encode to the wire byte. Inverse of `from_u8`.
237    #[must_use]
238    pub const fn to_u8(self) -> u8 {
239        match self {
240            Self::Reserved => 0x00,
241            Self::Mpeg1Video => 0x01,
242            Self::Mpeg2Video => 0x02,
243            Self::Mpeg1Audio => 0x03,
244            Self::Mpeg2Audio => 0x04,
245            Self::PrivateSections => 0x05,
246            Self::PesPrivateData => 0x06,
247            Self::Mheg => 0x07,
248            Self::DsmCc => 0x08,
249            Self::H222_1 => 0x09,
250            Self::Iso13818_6TypeA => 0x0A,
251            Self::Iso13818_6TypeB => 0x0B,
252            Self::Iso13818_6TypeC => 0x0C,
253            Self::Iso13818_6TypeD => 0x0D,
254            Self::Auxiliary => 0x0E,
255            Self::AacAdts => 0x0F,
256            Self::Mpeg4Video => 0x10,
257            Self::AacLatm => 0x11,
258            Self::SlFlexMuxPes => 0x12,
259            Self::SlFlexMuxSections => 0x13,
260            Self::SyncDownload => 0x14,
261            Self::MetadataPes => 0x15,
262            Self::MetadataSections => 0x16,
263            Self::MetadataDataCarousel => 0x17,
264            Self::MetadataObjectCarousel => 0x18,
265            Self::MetadataSyncDownload => 0x19,
266            Self::Ipmp => 0x1A,
267            Self::H264 => 0x1B,
268            Self::Iso14496_3Audio => 0x1C,
269            Self::Iso14496_17Text => 0x1D,
270            Self::AuxiliaryVideo => 0x1E,
271            Self::Svc => 0x1F,
272            Self::Mvc => 0x20,
273            Self::Jpeg2000 => 0x21,
274            Self::AdditionalViewH262 => 0x22,
275            Self::AdditionalViewH264 => 0x23,
276            Self::Hevc => 0x24,
277            Self::HevcTemporalSubset => 0x25,
278            Self::Mvcd => 0x26,
279            Self::Temi => 0x27,
280            Self::HevcAnnexG => 0x28,
281            Self::HevcAnnexGTemporal => 0x29,
282            Self::HevcAnnexH => 0x2A,
283            Self::HevcAnnexHTemporal => 0x2B,
284            Self::GreenAccessUnits => 0x2C,
285            Self::MhasAudioMain => 0x2D,
286            Self::MhasAudioAux => 0x2E,
287            Self::QualityAccessUnits => 0x2F,
288            Self::MediaOrchestration => 0x30,
289            Self::MctsHevc => 0x31,
290            Self::JpegXs => 0x32,
291            Self::Vvc => 0x33,
292            Self::VvcTemporalSubset => 0x34,
293            Self::Evc => 0x35,
294            Self::IpmpHigh => 0x7F,
295            Self::Ac3 => 0x81,
296            Self::Scte35 => 0x86,
297            Self::EAc3 => 0x87,
298            Self::ReservedRange(v) | Self::UserPrivate(v) => v,
299        }
300    }
301
302    /// Returns `true` if this stream_type carries a video elementary stream.
303    ///
304    /// Covers the video-coded stream types defined in Rec. ITU-T H.222.0 |
305    /// ISO/IEC 13818-1 Table 2-34 (2021 edition):
306    ///
307    /// | Variant | Value | Spec |
308    /// |---------|-------|------|
309    /// | `Mpeg1Video` | 0x01 | ISO/IEC 11172-2 |
310    /// | `Mpeg2Video` | 0x02 | ITU-T H.262 / ISO/IEC 13818-2 |
311    /// | `Mpeg4Video` | 0x10 | ISO/IEC 14496-2 |
312    /// | `H264` | 0x1B | ITU-T H.264 / ISO/IEC 14496-10 |
313    /// | `AuxiliaryVideo` | 0x1E | ISO/IEC 23002-3 |
314    /// | `Svc` | 0x1F | H.264 Annex G |
315    /// | `Mvc` | 0x20 | H.264 Annex H |
316    /// | `Jpeg2000` | 0x21 | ITU-T T.800 / ISO/IEC 15444-1 |
317    /// | `AdditionalViewH262` | 0x22 | H.262 stereoscopic 3D |
318    /// | `AdditionalViewH264` | 0x23 | H.264 stereoscopic 3D |
319    /// | `Hevc` | 0x24 | ITU-T H.265 / ISO/IEC 23008-2 |
320    /// | `HevcTemporalSubset` | 0x25 | H.265 Annex A |
321    /// | `Mvcd` | 0x26 | H.264 Annex I |
322    /// | `HevcAnnexG` | 0x28 | H.265 Annex G |
323    /// | `HevcAnnexGTemporal` | 0x29 | H.265 Annex G |
324    /// | `HevcAnnexH` | 0x2A | H.265 Annex H |
325    /// | `HevcAnnexHTemporal` | 0x2B | H.265 Annex H |
326    /// | `MctsHevc` | 0x31 | H.265 / ISO/IEC 23008-2 |
327    /// | `JpegXs` | 0x32 | ISO/IEC 21122-2 |
328    /// | `Vvc` | 0x33 | ITU-T H.266 / ISO/IEC 23090-3 |
329    /// | `VvcTemporalSubset` | 0x34 | H.266 Annex A |
330    /// | `Evc` | 0x35 | ISO/IEC 23094-1 |
331    ///
332    /// **Note on `PesPrivateData` (0x06):** this stream_type alone does *not*
333    /// identify the payload — AC-3, H.264, DVB subtitle, teletext, and others
334    /// all share 0x06 and are disambiguated by component/registration descriptors
335    /// per ETSI TS 101 154. `is_video` returns `false` for `PesPrivateData`;
336    /// callers must inspect the ES_info descriptor loop to resolve the actual
337    /// content type.
338    #[must_use]
339    pub fn is_video(self) -> bool {
340        matches!(
341            self,
342            Self::Mpeg1Video
343                | Self::Mpeg2Video
344                | Self::Mpeg4Video
345                | Self::H264
346                | Self::AuxiliaryVideo
347                | Self::Svc
348                | Self::Mvc
349                | Self::Jpeg2000
350                | Self::AdditionalViewH262
351                | Self::AdditionalViewH264
352                | Self::Hevc
353                | Self::HevcTemporalSubset
354                | Self::Mvcd
355                | Self::HevcAnnexG
356                | Self::HevcAnnexGTemporal
357                | Self::HevcAnnexH
358                | Self::HevcAnnexHTemporal
359                | Self::MctsHevc
360                | Self::JpegXs
361                | Self::Vvc
362                | Self::VvcTemporalSubset
363                | Self::Evc
364        )
365    }
366
367    /// Returns `true` if this stream_type carries an audio elementary stream.
368    ///
369    /// Covers the audio-coded stream types defined in Rec. ITU-T H.222.0 |
370    /// ISO/IEC 13818-1 Table 2-34 (2021 edition):
371    ///
372    /// | Variant | Value | Spec |
373    /// |---------|-------|------|
374    /// | `Mpeg1Audio` | 0x03 | ISO/IEC 11172-3 |
375    /// | `Mpeg2Audio` | 0x04 | ISO/IEC 13818-3 |
376    /// | `AacAdts` | 0x0F | ISO/IEC 13818-7 (AAC in ADTS) |
377    /// | `AacLatm` | 0x11 | ISO/IEC 14496-3 (AAC in LATM) |
378    /// | `Iso14496_3Audio` | 0x1C | ISO/IEC 14496-3 (raw, no transport syntax) |
379    /// | `MhasAudioMain` | 0x2D | ISO/IEC 23008-3 MHAS main |
380    /// | `MhasAudioAux` | 0x2E | ISO/IEC 23008-3 MHAS auxiliary |
381    /// | `Ac3` | 0x81 | ATSC A/52 (AC-3) |
382    /// | `EAc3` | 0x87 | ATSC A/52B (E-AC-3 / Dolby Digital Plus) |
383    ///
384    /// **Note on `PesPrivateData` (0x06):** this stream_type alone does *not*
385    /// identify the payload — AC-3 carried over DVB uses 0x06 with an
386    /// AC-3/enhanced AC-3 descriptor per ETSI TS 101 154. `is_audio` returns
387    /// `false` for `PesPrivateData`; callers must inspect the ES_info descriptor
388    /// loop to resolve the actual content type.
389    #[must_use]
390    pub fn is_audio(self) -> bool {
391        matches!(
392            self,
393            Self::Mpeg1Audio
394                | Self::Mpeg2Audio
395                | Self::AacAdts
396                | Self::AacLatm
397                | Self::Iso14496_3Audio
398                | Self::MhasAudioMain
399                | Self::MhasAudioAux
400                | Self::Ac3
401                | Self::EAc3
402        )
403    }
404
405    /// Returns `true` if this stream_type unambiguously carries subtitle/text
406    /// content.
407    ///
408    /// Only `Iso14496_17Text` (0x1D, ISO/IEC 14496-17 timed text) is
409    /// unambiguously identified as subtitle/text by the stream_type byte alone.
410    ///
411    /// **Note on `PesPrivateData` (0x06):** DVB bitmap subtitles (ETSI EN 300 743)
412    /// and DVB teletext (ETSI EN 300 472) both use stream_type 0x06 and are
413    /// identified by subtitling / teletext descriptors in the ES_info loop per
414    /// ETSI TS 101 154. `is_subtitle` returns `false` for `PesPrivateData`;
415    /// callers must inspect the ES_info descriptor loop to resolve the actual
416    /// content type.
417    #[must_use]
418    pub fn is_subtitle(self) -> bool {
419        matches!(self, Self::Iso14496_17Text)
420    }
421
422    /// Returns `true` if this stream_type carries data, private sections,
423    /// signalling, or metadata — i.e. neither video, audio, nor subtitle.
424    ///
425    /// This includes:
426    ///
427    /// | Variant | Value | Notes |
428    /// |---------|-------|-------|
429    /// | `Reserved` | 0x00 | ITU-T / ISO reserved |
430    /// | `PrivateSections` | 0x05 | private_sections (H.222.0 §2.4.4.10) |
431    /// | `PesPrivateData` | 0x06 | descriptor-dependent; see note below |
432    /// | `Mheg` | 0x07 | MHEG interactive |
433    /// | `DsmCc` | 0x08 | DSM-CC (H.222.0 Annex A) |
434    /// | `H222_1` | 0x09 | H.222.1 |
435    /// | `Iso13818_6TypeA`–`TypeD` | 0x0A–0x0D | ISO/IEC 13818-6 carousel/download |
436    /// | `Auxiliary` | 0x0E | H.222.0 auxiliary |
437    /// | `SlFlexMuxPes` | 0x12 | SL/FlexMux in PES |
438    /// | `SlFlexMuxSections` | 0x13 | SL/FlexMux in sections |
439    /// | `SyncDownload` | 0x14 | ISO/IEC 13818-6 Synchronized Download Protocol |
440    /// | `MetadataPes`–`MetadataSyncDownload` | 0x15–0x19 | metadata streams |
441    /// | `Ipmp` | 0x1A | IPMP (ISO/IEC 13818-11) |
442    /// | `Temi` | 0x27 | Timeline and External Media Information |
443    /// | `GreenAccessUnits` | 0x2C | green access units |
444    /// | `QualityAccessUnits` | 0x2F | quality access units |
445    /// | `MediaOrchestration` | 0x30 | Media Orchestration |
446    /// | `IpmpHigh` | 0x7F | IPMP (H.222.0 Table 2-34) |
447    /// | `Scte35` | 0x86 | SCTE-35 splice_info_section |
448    /// | `ReservedRange(_)` | 0x36–0x7E | reserved, treated conservatively as data |
449    /// | `UserPrivate(_)` | 0x80–0xFF | user private, treated conservatively as data |
450    ///
451    /// **Note on `PesPrivateData` (0x06):** this stream_type is shared by many
452    /// DVB content types — AC-3 audio, E-AC-3, DVB subtitles, teletext, and
453    /// others — all identified via descriptors per ETSI TS 101 154. This
454    /// predicate conservatively classifies 0x06 as data/unknown. Callers that
455    /// need to classify 0x06 streams must inspect the ES_info descriptor loop.
456    #[must_use]
457    pub fn is_data(self) -> bool {
458        !self.is_video() && !self.is_audio() && !self.is_subtitle()
459    }
460
461    /// Human-readable spec display name.
462    #[must_use]
463    pub fn name(self) -> &'static str {
464        match self {
465            Self::Reserved => "Reserved",
466            Self::Mpeg1Video => "MPEG-1 Video",
467            Self::Mpeg2Video => "MPEG-2 Video",
468            Self::Mpeg1Audio => "MPEG-1 Audio",
469            Self::Mpeg2Audio => "MPEG-2 Audio",
470            Self::PrivateSections => "Private Sections",
471            Self::PesPrivateData => "PES Private Data",
472            Self::Mheg => "MHEG",
473            Self::DsmCc => "DSM-CC",
474            Self::H222_1 => "H.222.1",
475            Self::Iso13818_6TypeA => "ISO/IEC 13818-6 Type A",
476            Self::Iso13818_6TypeB => "ISO/IEC 13818-6 Type B",
477            Self::Iso13818_6TypeC => "ISO/IEC 13818-6 Type C",
478            Self::Iso13818_6TypeD => "ISO/IEC 13818-6 Type D",
479            Self::Auxiliary => "Auxiliary",
480            Self::AacAdts => "AAC ADTS",
481            Self::Mpeg4Video => "MPEG-4 Video",
482            Self::AacLatm => "AAC LATM",
483            Self::SlFlexMuxPes => "SL/FlexMux in PES",
484            Self::SlFlexMuxSections => "SL/FlexMux in Sections",
485            Self::SyncDownload => "Sync Download Protocol",
486            Self::MetadataPes => "Metadata in PES",
487            Self::MetadataSections => "Metadata in Sections",
488            Self::MetadataDataCarousel => "Metadata Data Carousel",
489            Self::MetadataObjectCarousel => "Metadata Object Carousel",
490            Self::MetadataSyncDownload => "Metadata Sync Download",
491            Self::Ipmp => "IPMP",
492            Self::H264 => "H.264/AVC",
493            Self::Iso14496_3Audio => "ISO/IEC 14496-3 Audio",
494            Self::Iso14496_17Text => "ISO/IEC 14496-17 Text",
495            Self::AuxiliaryVideo => "Auxiliary Video",
496            Self::Svc => "SVC",
497            Self::Mvc => "MVC",
498            Self::Jpeg2000 => "JPEG 2000",
499            Self::AdditionalViewH262 => "Additional View H.262 (3D)",
500            Self::AdditionalViewH264 => "Additional View H.264 (3D)",
501            Self::Hevc => "HEVC/H.265",
502            Self::HevcTemporalSubset => "HEVC Temporal Subset",
503            Self::Mvcd => "MVCD (H.264 Annex I)",
504            Self::Temi => "TEMI",
505            Self::HevcAnnexG => "HEVC Annex G",
506            Self::HevcAnnexGTemporal => "HEVC Annex G Temporal",
507            Self::HevcAnnexH => "HEVC Annex H",
508            Self::HevcAnnexHTemporal => "HEVC Annex H Temporal",
509            Self::GreenAccessUnits => "Green Access Units",
510            Self::MhasAudioMain => "MHAS Audio Main",
511            Self::MhasAudioAux => "MHAS Audio Aux",
512            Self::QualityAccessUnits => "Quality Access Units",
513            Self::MediaOrchestration => "Media Orchestration",
514            Self::MctsHevc => "MCTS HEVC",
515            Self::JpegXs => "JPEG XS",
516            Self::Vvc => "VVC/H.266",
517            Self::VvcTemporalSubset => "VVC Temporal Subset",
518            Self::Evc => "EVC",
519            Self::IpmpHigh => "IPMP (0x7F)",
520            Self::Ac3 => "AC-3",
521            Self::Scte35 => "SCTE-35",
522            Self::EAc3 => "E-AC-3",
523            Self::ReservedRange(_) => "Reserved",
524            Self::UserPrivate(_) => "User Private",
525        }
526    }
527}
528broadcast_common::impl_spec_display!(StreamType, ReservedRange, UserPrivate);
529
530/// One elementary stream entry in the PMT's ES loop.
531#[derive(Debug, Clone, PartialEq, Eq)]
532#[cfg_attr(feature = "serde", derive(serde::Serialize))]
533#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
534pub struct PmtStream<'a> {
535    /// MPEG-2 stream_type byte (ISO/IEC 13818-1 Table 2-34).
536    pub stream_type: StreamType,
537    /// 13-bit elementary stream PID.
538    pub elementary_pid: u16,
539    /// Raw ES_info descriptor bytes; parsing lives in crate::descriptors.
540    /// Elementary-stream descriptor loop. Serializes as the typed descriptor
541    /// sequence; `.raw()` yields the wire bytes.
542    pub es_info: DescriptorLoop<'a>,
543}
544
545/// Program Map Table.
546#[non_exhaustive]
547#[derive(Debug, Clone, PartialEq, Eq)]
548#[cfg_attr(feature = "serde", derive(serde::Serialize))]
549#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
550pub struct PmtSection<'a> {
551    /// Programme number from the table_id_extension field.
552    pub program_number: u16,
553    /// 5-bit version_number.
554    pub version_number: u8,
555    /// current_next_indicator bit.
556    pub current_next_indicator: bool,
557    /// section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
558    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
559    pub section_number: u8,
560    /// last_section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
561    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
562    pub last_section_number: u8,
563    /// 13-bit PCR PID.
564    pub pcr_pid: u16,
565    /// Raw program_info descriptor bytes.
566    /// Program-info descriptor loop. Serializes as the typed descriptor
567    /// sequence; `.raw()` yields the wire bytes.
568    pub program_info: DescriptorLoop<'a>,
569    /// Elementary streams in wire order.
570    pub streams: Vec<PmtStream<'a>>,
571}
572
573impl<'a> PmtSection<'a> {
574    /// Construct a `PmtSection` from its fields.
575    ///
576    /// This is the canonical constructor for external code. `PmtSection` is
577    /// `#[non_exhaustive]` so struct literal syntax is not available outside
578    /// the crate; use this function instead.
579    #[must_use]
580    #[allow(clippy::too_many_arguments)]
581    pub fn new(
582        program_number: u16,
583        version_number: u8,
584        current_next_indicator: bool,
585        section_number: u8,
586        last_section_number: u8,
587        pcr_pid: u16,
588        program_info: DescriptorLoop<'a>,
589        streams: Vec<PmtStream<'a>>,
590    ) -> Self {
591        Self {
592            program_number,
593            version_number,
594            current_next_indicator,
595            section_number,
596            last_section_number,
597            pcr_pid,
598            program_info,
599            streams,
600        }
601    }
602}
603
604impl<'a> Parse<'a> for PmtSection<'a> {
605    type Error = crate::error::Error;
606    fn parse(bytes: &'a [u8]) -> Result<Self> {
607        let min_len =
608            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
609        if bytes.len() < min_len {
610            return Err(Error::BufferTooShort {
611                need: min_len,
612                have: bytes.len(),
613                what: "PmtSection",
614            });
615        }
616        if bytes[0] != TABLE_ID {
617            return Err(Error::UnexpectedTableId {
618                table_id: bytes[0],
619                what: "PmtSection",
620                expected: &[TABLE_ID],
621            });
622        }
623
624        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
625        let total = super::check_section_length(
626            bytes.len(),
627            MIN_HEADER_LEN,
628            section_length as usize,
629            MIN_SECTION_LEN,
630        )?;
631
632        let program_number = u16::from_be_bytes(*bytes[3..].first_chunk::<2>().unwrap());
633        let version_number = (bytes[5] >> 1) & 0x1F;
634        let current_next_indicator = (bytes[5] & 0x01) != 0;
635        let section_number = bytes[6];
636        let last_section_number = bytes[7];
637
638        let pcr_pid = (((bytes[8] & 0x1F) as u16) << 8) | bytes[9] as u16;
639        let program_info_length = (((bytes[10] & 0x0F) as usize) << 8) | bytes[11] as usize;
640
641        let prog_info_start =
642            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
643        let prog_info_end = prog_info_start + program_info_length;
644        let stream_loop_end = total - CRC_LEN;
645        if prog_info_end > stream_loop_end {
646            return Err(Error::SectionLengthOverflow {
647                declared: program_info_length,
648                available: stream_loop_end.saturating_sub(prog_info_start),
649            });
650        }
651        let program_info = DescriptorLoop::new(&bytes[prog_info_start..prog_info_end]);
652
653        let mut streams = Vec::new();
654        let mut pos = prog_info_end;
655        while pos + STREAM_HEADER_LEN <= stream_loop_end {
656            let stream_type = StreamType::from_u8(bytes[pos]);
657            let elementary_pid = (((bytes[pos + 1] & 0x1F) as u16) << 8) | bytes[pos + 2] as u16;
658            let es_info_length =
659                (((bytes[pos + 3] & 0x0F) as usize) << 8) | bytes[pos + 4] as usize;
660            let es_start = pos + STREAM_HEADER_LEN;
661            let es_end = es_start + es_info_length;
662            if es_end > stream_loop_end {
663                return Err(Error::SectionLengthOverflow {
664                    declared: es_info_length,
665                    available: stream_loop_end.saturating_sub(es_start),
666                });
667            }
668            streams.push(PmtStream {
669                stream_type,
670                elementary_pid,
671                es_info: DescriptorLoop::new(&bytes[es_start..es_end]),
672            });
673            pos = es_end;
674        }
675
676        if pos != stream_loop_end {
677            return Err(Error::BufferTooShort {
678                need: stream_loop_end - pos,
679                have: 0,
680                what: "PmtSection trailing stream bytes",
681            });
682        }
683
684        Ok(PmtSection {
685            program_number,
686            version_number,
687            current_next_indicator,
688            section_number,
689            last_section_number,
690            pcr_pid,
691            program_info,
692            streams,
693        })
694    }
695}
696
697impl Serialize for PmtSection<'_> {
698    type Error = crate::error::Error;
699    fn serialized_len(&self) -> usize {
700        let streams_bytes: usize = self
701            .streams
702            .iter()
703            .map(|s| STREAM_HEADER_LEN + s.es_info.len())
704            .sum();
705        MIN_HEADER_LEN
706            + EXTENSION_HEADER_LEN
707            + PCR_PID_LEN
708            + PROG_INFO_LEN_BYTES
709            + self.program_info.len()
710            + streams_bytes
711            + CRC_LEN
712    }
713
714    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
715        let len = self.serialized_len();
716        if buf.len() < len {
717            return Err(Error::OutputBufferTooSmall {
718                need: len,
719                have: buf.len(),
720            });
721        }
722
723        let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
724        buf[0] = TABLE_ID;
725        buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
726        buf[2] = (section_length & 0xFF) as u8;
727        buf[3..5].copy_from_slice(&self.program_number.to_be_bytes());
728        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
729        buf[6] = self.section_number;
730        buf[7] = self.last_section_number;
731        buf[8] = 0xE0 | ((self.pcr_pid >> 8) as u8 & 0x1F);
732        buf[9] = (self.pcr_pid & 0xFF) as u8;
733        let pil = self.program_info.len() as u16;
734        buf[10] = 0xF0 | ((pil >> 8) as u8 & 0x0F);
735        buf[11] = (pil & 0xFF) as u8;
736
737        let prog_info_start =
738            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
739        buf[prog_info_start..prog_info_start + self.program_info.len()]
740            .copy_from_slice(self.program_info.raw());
741
742        let mut pos = prog_info_start + self.program_info.len();
743        for stream in &self.streams {
744            buf[pos] = stream.stream_type.to_u8();
745            buf[pos + 1] = 0xE0 | ((stream.elementary_pid >> 8) as u8 & 0x1F);
746            buf[pos + 2] = (stream.elementary_pid & 0xFF) as u8;
747            let esl = stream.es_info.len() as u16;
748            buf[pos + 3] = 0xF0 | ((esl >> 8) as u8 & 0x0F);
749            buf[pos + 4] = (esl & 0xFF) as u8;
750            let es_start = pos + STREAM_HEADER_LEN;
751            buf[es_start..es_start + stream.es_info.len()].copy_from_slice(stream.es_info.raw());
752            pos = es_start + stream.es_info.len();
753        }
754
755        let crc_pos = len - CRC_LEN;
756        let crc = broadcast_common::crc32_mpeg2::compute(&buf[..crc_pos]);
757        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
758        Ok(len)
759    }
760}
761impl<'a> crate::traits::TableDef<'a> for PmtSection<'a> {
762    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
763    const NAME: &'static str = "PROGRAM_MAP";
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769
770    /// Build a PMT section with given fields. Placeholder CRC.
771    fn build_pmt(
772        program_number: u16,
773        version: u8,
774        pcr_pid: u16,
775        program_info: &[u8],
776        streams: &[(u8, u16, Vec<u8>)],
777    ) -> Vec<u8> {
778        let streams_bytes: usize = streams
779            .iter()
780            .map(|(_, _, es)| STREAM_HEADER_LEN + es.len())
781            .sum();
782        let section_length: u16 = (EXTENSION_HEADER_LEN
783            + PCR_PID_LEN
784            + PROG_INFO_LEN_BYTES
785            + program_info.len()
786            + streams_bytes
787            + CRC_LEN) as u16;
788        let mut v = Vec::new();
789        v.push(TABLE_ID);
790        v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
791        v.push((section_length & 0xFF) as u8);
792        v.extend_from_slice(&program_number.to_be_bytes());
793        v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
794        v.push(0);
795        v.push(0);
796        v.push(0xE0 | ((pcr_pid >> 8) as u8 & 0x1F));
797        v.push((pcr_pid & 0xFF) as u8);
798        v.push(0xF0 | ((program_info.len() >> 8) as u8 & 0x0F));
799        v.push((program_info.len() & 0xFF) as u8);
800        v.extend_from_slice(program_info);
801        for (stype, pid, es) in streams {
802            v.push(*stype);
803            v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
804            v.push((pid & 0xFF) as u8);
805            v.push(0xF0 | ((es.len() >> 8) as u8 & 0x0F));
806            v.push((es.len() & 0xFF) as u8);
807            v.extend_from_slice(es);
808        }
809        v.extend_from_slice(&[0, 0, 0, 0]);
810        v
811    }
812
813    #[test]
814    fn parse_extracts_pcr_pid_and_program_info() {
815        let bytes = build_pmt(42, 5, 0x0100, &[0xAA, 0xBB], &[]);
816        let pmt = PmtSection::parse(&bytes).unwrap();
817        assert_eq!(pmt.program_number, 42);
818        assert_eq!(pmt.version_number, 5);
819        assert!(pmt.current_next_indicator);
820        assert_eq!(pmt.pcr_pid, 0x0100);
821        assert_eq!(pmt.program_info.raw(), &[0xAA, 0xBB]);
822        assert_eq!(pmt.streams.len(), 0);
823    }
824
825    #[test]
826    fn parse_elementary_streams_and_es_info_slices() {
827        let bytes = build_pmt(
828            1,
829            0,
830            0x101,
831            &[],
832            &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![0x33])],
833        );
834        let pmt = PmtSection::parse(&bytes).unwrap();
835        assert_eq!(pmt.streams.len(), 2);
836        assert_eq!(pmt.streams[0].stream_type, StreamType::Mpeg2Video);
837        assert_eq!(pmt.streams[0].elementary_pid, 0x102);
838        assert_eq!(pmt.streams[0].es_info.raw(), &[0x11, 0x22]);
839        assert_eq!(pmt.streams[1].stream_type, StreamType::H264);
840        assert_eq!(pmt.streams[1].elementary_pid, 0x103);
841        assert_eq!(pmt.streams[1].es_info.raw(), &[0x33]);
842    }
843
844    #[test]
845    fn parse_rejects_wrong_table_id() {
846        let mut bytes = build_pmt(1, 0, 0x100, &[], &[]);
847        bytes[0] = 0x00;
848        let err = PmtSection::parse(&bytes).unwrap_err();
849        assert!(matches!(
850            err,
851            Error::UnexpectedTableId { table_id: 0x00, .. }
852        ));
853    }
854
855    #[test]
856    fn parse_rejects_short_buffer() {
857        let err = PmtSection::parse(&[0x02, 0x00]).unwrap_err();
858        assert!(matches!(err, Error::BufferTooShort { .. }));
859    }
860
861    #[test]
862    fn serialize_round_trip_empty_program() {
863        let pmt = PmtSection {
864            program_number: 1,
865            version_number: 0,
866            current_next_indicator: true,
867            section_number: 0,
868            last_section_number: 0,
869            pcr_pid: 0x100,
870            program_info: DescriptorLoop::new(&[]),
871            streams: vec![],
872        };
873        let mut buf = vec![0u8; pmt.serialized_len()];
874        pmt.serialize_into(&mut buf).unwrap();
875        let re = PmtSection::parse(&buf).unwrap();
876        assert_eq!(pmt, re);
877    }
878
879    #[test]
880    fn serialize_round_trip_with_streams_and_descriptors() {
881        let prog_info: [u8; 3] = [0x09, 0x01, 0xFF];
882        let es1: [u8; 4] = [0x52, 0x02, 0xAA, 0xBB];
883        let es2: [u8; 2] = [0x0A, 0x00];
884        let pmt = PmtSection {
885            program_number: 0xABCD,
886            version_number: 7,
887            current_next_indicator: true,
888            section_number: 0,
889            last_section_number: 0,
890            pcr_pid: 0x1F0,
891            program_info: DescriptorLoop::new(&prog_info),
892            streams: vec![
893                PmtStream {
894                    stream_type: StreamType::Mpeg2Video,
895                    elementary_pid: 0x100,
896                    es_info: DescriptorLoop::new(&es1),
897                },
898                PmtStream {
899                    stream_type: StreamType::Mpeg1Audio,
900                    elementary_pid: 0x101,
901                    es_info: DescriptorLoop::new(&es2),
902                },
903                PmtStream {
904                    stream_type: StreamType::H264,
905                    elementary_pid: 0x102,
906                    es_info: DescriptorLoop::new(&[]),
907                },
908            ],
909        };
910        let mut buf = vec![0u8; pmt.serialized_len()];
911        pmt.serialize_into(&mut buf).unwrap();
912        let re = PmtSection::parse(&buf).unwrap();
913        assert_eq!(pmt, re);
914    }
915
916    #[test]
917    fn zero_elementary_streams_is_valid() {
918        let bytes = build_pmt(99, 0, 0x0100, &[], &[]);
919        let pmt = PmtSection::parse(&bytes).unwrap();
920        assert_eq!(pmt.streams.len(), 0);
921    }
922
923    #[test]
924    fn parse_preserves_raw_program_info_bytes() {
925        let pi = vec![0x09, 0x04, 0x01, 0x02, 0x03, 0x04];
926        let bytes = build_pmt(1, 0, 0x100, &pi, &[]);
927        let pmt = PmtSection::parse(&bytes).unwrap();
928        assert_eq!(pmt.program_info.raw(), &pi[..]);
929    }
930
931    #[test]
932    fn parse_rejects_zero_section_length() {
933        let mut buf = vec![0u8; 64];
934        buf[0] = TABLE_ID;
935        buf[1] = 0xF0;
936        buf[2] = 0x00;
937        for b in &mut buf[3..] {
938            *b = 0xFF;
939        }
940        assert!(matches!(
941            PmtSection::parse(&buf).unwrap_err(),
942            Error::SectionLengthOverflow { .. }
943        ));
944    }
945
946    #[test]
947    fn parse_rejects_trailing_slack_bytes() {
948        // ETSI EN 300 468 / ISO 13818-1 §2.4.4.8: the stream loop runs to
949        // stream_loop_end exactly. A truncated final entry (fewer than
950        // STREAM_HEADER_LEN bytes of slack) must be rejected, not silently
951        // dropped (mirrors EitSection's post-loop check).
952        let mut bytes = build_pmt(1, 0, 0x100, &[], &[(0x02, 0x101, vec![])]);
953        let sl = (bytes.len() - MIN_HEADER_LEN) as u16 + 2;
954        bytes[1] = (bytes[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
955        bytes[2] = (sl & 0xFF) as u8;
956        let crc_pos = bytes.len() - CRC_LEN;
957        bytes.splice(crc_pos..crc_pos, [0xFF, 0xFF]);
958        let err = PmtSection::parse(&bytes).unwrap_err();
959        assert!(matches!(
960            err,
961            Error::BufferTooShort {
962                what: "PmtSection trailing stream bytes",
963                ..
964            }
965        ));
966    }
967
968    #[test]
969    fn parse_accepts_well_formed_stream_loop_with_no_slack() {
970        let bytes = build_pmt(
971            1,
972            0,
973            0x101,
974            &[],
975            &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![])],
976        );
977        let pmt = PmtSection::parse(&bytes).unwrap();
978        assert_eq!(pmt.streams.len(), 2);
979    }
980
981    #[test]
982    fn stream_type_full_range_round_trip() {
983        for byte in 0u8..=0xFF {
984            let st = StreamType::from_u8(byte);
985            assert_eq!(
986                st.to_u8(),
987                byte,
988                "StreamType round-trip failed for {byte:#04x}"
989            );
990        }
991    }
992
993    #[test]
994    fn stream_type_named_values() {
995        assert_eq!(StreamType::Mpeg2Video.to_u8(), 0x02);
996        assert_eq!(StreamType::H264.to_u8(), 0x1B);
997        assert_eq!(StreamType::Hevc.to_u8(), 0x24);
998        assert_eq!(StreamType::Vvc.to_u8(), 0x33);
999        assert_eq!(StreamType::MediaOrchestration.to_u8(), 0x30);
1000        assert_eq!(StreamType::Mvcd.to_u8(), 0x26);
1001        assert_eq!(StreamType::Temi.to_u8(), 0x27);
1002        assert_eq!(StreamType::Ac3.to_u8(), 0x81);
1003        assert_eq!(StreamType::Scte35.to_u8(), 0x86);
1004        assert_eq!(StreamType::EAc3.to_u8(), 0x87);
1005        assert_eq!(StreamType::AacAdts.to_u8(), 0x0F);
1006        assert_eq!(StreamType::IpmpHigh.to_u8(), 0x7F);
1007    }
1008
1009    #[test]
1010    fn stream_type_names() {
1011        assert_eq!(StreamType::Mpeg2Video.name(), "MPEG-2 Video");
1012        assert_eq!(StreamType::H264.name(), "H.264/AVC");
1013        assert_eq!(StreamType::Hevc.name(), "HEVC/H.265");
1014        assert_eq!(StreamType::Vvc.name(), "VVC/H.266");
1015        assert_eq!(StreamType::MediaOrchestration.name(), "Media Orchestration");
1016        assert_eq!(StreamType::Mvcd.name(), "MVCD (H.264 Annex I)");
1017        assert_eq!(StreamType::Temi.name(), "TEMI");
1018        assert_eq!(StreamType::DsmCc.name(), "DSM-CC");
1019        assert_eq!(StreamType::Ac3.name(), "AC-3");
1020        assert_eq!(StreamType::Scte35.name(), "SCTE-35");
1021    }
1022
1023    #[test]
1024    fn stream_type_wire_to_name() {
1025        assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
1026        assert_eq!(StreamType::from_u8(0x1B).name(), "H.264/AVC");
1027        assert_eq!(StreamType::from_u8(0x24).name(), "HEVC/H.265");
1028        assert_eq!(StreamType::from_u8(0x00).name(), "Reserved");
1029        assert_eq!(StreamType::from_u8(0x81).name(), "AC-3");
1030    }
1031
1032    // ── StreamType classifier predicates (#522) ────────────────────────────
1033
1034    #[test]
1035    fn stream_type_is_video_known_types() {
1036        // Each video stream_type must be recognised.
1037        for (byte, label) in [
1038            (0x01_u8, "Mpeg1Video"),
1039            (0x02, "Mpeg2Video"),
1040            (0x10, "Mpeg4Video"),
1041            (0x1B, "H264"),
1042            (0x1E, "AuxiliaryVideo"),
1043            (0x1F, "Svc"),
1044            (0x20, "Mvc"),
1045            (0x21, "Jpeg2000"),
1046            (0x22, "AdditionalViewH262"),
1047            (0x23, "AdditionalViewH264"),
1048            (0x24, "Hevc"),
1049            (0x25, "HevcTemporalSubset"),
1050            (0x26, "Mvcd"),
1051            (0x28, "HevcAnnexG"),
1052            (0x29, "HevcAnnexGTemporal"),
1053            (0x2A, "HevcAnnexH"),
1054            (0x2B, "HevcAnnexHTemporal"),
1055            (0x31, "MctsHevc"),
1056            (0x32, "JpegXs"),
1057            (0x33, "Vvc"),
1058            (0x34, "VvcTemporalSubset"),
1059            (0x35, "Evc"),
1060        ] {
1061            let st = StreamType::from_u8(byte);
1062            assert!(st.is_video(), "{label} (0x{byte:02X}) should be is_video");
1063            assert!(
1064                !st.is_audio(),
1065                "{label} (0x{byte:02X}) must not be is_audio"
1066            );
1067            assert!(
1068                !st.is_subtitle(),
1069                "{label} (0x{byte:02X}) must not be is_subtitle"
1070            );
1071            assert!(!st.is_data(), "{label} (0x{byte:02X}) must not be is_data");
1072        }
1073    }
1074
1075    #[test]
1076    fn stream_type_is_audio_known_types() {
1077        for (byte, label) in [
1078            (0x03_u8, "Mpeg1Audio"),
1079            (0x04, "Mpeg2Audio"),
1080            (0x0F, "AacAdts"),
1081            (0x11, "AacLatm"),
1082            (0x1C, "Iso14496_3Audio"),
1083            (0x2D, "MhasAudioMain"),
1084            (0x2E, "MhasAudioAux"),
1085            (0x81, "Ac3"),
1086            (0x87, "EAc3"),
1087        ] {
1088            let st = StreamType::from_u8(byte);
1089            assert!(st.is_audio(), "{label} (0x{byte:02X}) should be is_audio");
1090            assert!(
1091                !st.is_video(),
1092                "{label} (0x{byte:02X}) must not be is_video"
1093            );
1094            assert!(
1095                !st.is_subtitle(),
1096                "{label} (0x{byte:02X}) must not be is_subtitle"
1097            );
1098            assert!(!st.is_data(), "{label} (0x{byte:02X}) must not be is_data");
1099        }
1100    }
1101
1102    #[test]
1103    fn stream_type_is_subtitle_known_types() {
1104        let st = StreamType::from_u8(0x1D); // Iso14496_17Text
1105        assert!(st.is_subtitle(), "Iso14496_17Text should be is_subtitle");
1106        assert!(!st.is_video(), "Iso14496_17Text must not be is_video");
1107        assert!(!st.is_audio(), "Iso14496_17Text must not be is_audio");
1108        assert!(!st.is_data(), "Iso14496_17Text must not be is_data");
1109    }
1110
1111    #[test]
1112    fn stream_type_pes_private_data_is_data() {
1113        // 0x06 is descriptor-dependent; the conservative classification is is_data.
1114        let st = StreamType::from_u8(0x06);
1115        assert!(
1116            st.is_data(),
1117            "PesPrivateData (0x06) should classify as is_data"
1118        );
1119        assert!(!st.is_video(), "PesPrivateData must not be is_video");
1120        assert!(!st.is_audio(), "PesPrivateData must not be is_audio");
1121        assert!(!st.is_subtitle(), "PesPrivateData must not be is_subtitle");
1122    }
1123
1124    #[test]
1125    fn stream_type_data_known_types() {
1126        for (byte, label) in [
1127            (0x00_u8, "Reserved"),
1128            (0x05, "PrivateSections"),
1129            (0x06, "PesPrivateData"),
1130            (0x07, "Mheg"),
1131            (0x08, "DsmCc"),
1132            (0x09, "H222_1"),
1133            (0x0A, "Iso13818_6TypeA"),
1134            (0x0B, "Iso13818_6TypeB"),
1135            (0x0C, "Iso13818_6TypeC"),
1136            (0x0D, "Iso13818_6TypeD"),
1137            (0x0E, "Auxiliary"),
1138            (0x12, "SlFlexMuxPes"),
1139            (0x13, "SlFlexMuxSections"),
1140            (0x14, "SyncDownload"),
1141            (0x15, "MetadataPes"),
1142            (0x16, "MetadataSections"),
1143            (0x17, "MetadataDataCarousel"),
1144            (0x18, "MetadataObjectCarousel"),
1145            (0x19, "MetadataSyncDownload"),
1146            (0x1A, "Ipmp"),
1147            (0x27, "Temi"),
1148            (0x2C, "GreenAccessUnits"),
1149            (0x2F, "QualityAccessUnits"),
1150            (0x30, "MediaOrchestration"),
1151            (0x7F, "IpmpHigh"),
1152            (0x86, "Scte35"),
1153        ] {
1154            let st = StreamType::from_u8(byte);
1155            assert!(st.is_data(), "{label} (0x{byte:02X}) should be is_data");
1156            assert!(
1157                !st.is_video(),
1158                "{label} (0x{byte:02X}) must not be is_video"
1159            );
1160            assert!(
1161                !st.is_audio(),
1162                "{label} (0x{byte:02X}) must not be is_audio"
1163            );
1164            assert!(
1165                !st.is_subtitle(),
1166                "{label} (0x{byte:02X}) must not be is_subtitle"
1167            );
1168        }
1169    }
1170
1171    #[test]
1172    fn stream_type_classifier_partition_all_bytes() {
1173        // Every byte value is covered by exactly one predicate.
1174        for byte in 0u8..=0xFF {
1175            let st = StreamType::from_u8(byte);
1176            let n = u8::from(st.is_video())
1177                + u8::from(st.is_audio())
1178                + u8::from(st.is_subtitle())
1179                + u8::from(st.is_data());
1180            assert_eq!(
1181                n, 1,
1182                "stream_type 0x{byte:02X} must be in exactly one category, got {n}"
1183            );
1184        }
1185    }
1186
1187    /// Regression test for #181: non-zero section_number/last_section_number must
1188    /// survive a serialize → parse round-trip. Before the fix, serialize_into
1189    /// hardcoded buf[6]=0 and buf[7]=0, so this test would fail.
1190    #[test]
1191    fn section_number_round_trip_nonzero() {
1192        let pmt = PmtSection {
1193            program_number: 42,
1194            version_number: 1,
1195            current_next_indicator: true,
1196            section_number: 3,
1197            last_section_number: 7,
1198            pcr_pid: 0x0200,
1199            program_info: DescriptorLoop::new(&[]),
1200            streams: vec![],
1201        };
1202        let mut buf = vec![0u8; pmt.serialized_len()];
1203        pmt.serialize_into(&mut buf).unwrap();
1204        // Wire bytes[6] and [7] must carry the encoded values.
1205        assert_eq!(buf[6], 3, "wire byte[6] must be section_number=3");
1206        assert_eq!(buf[7], 7, "wire byte[7] must be last_section_number=7");
1207        // Re-parse must recover the same values.
1208        let re = PmtSection::parse(&buf).unwrap();
1209        assert_eq!(re.section_number, 3);
1210        assert_eq!(re.last_section_number, 7);
1211        assert_eq!(pmt, re);
1212    }
1213}