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 dvb_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 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    /// Human-readable spec display name.
303    #[must_use]
304    pub fn name(self) -> &'static str {
305        match self {
306            Self::Reserved => "Reserved",
307            Self::Mpeg1Video => "MPEG-1 Video",
308            Self::Mpeg2Video => "MPEG-2 Video",
309            Self::Mpeg1Audio => "MPEG-1 Audio",
310            Self::Mpeg2Audio => "MPEG-2 Audio",
311            Self::PrivateSections => "Private Sections",
312            Self::PesPrivateData => "PES Private Data",
313            Self::Mheg => "MHEG",
314            Self::DsmCc => "DSM-CC",
315            Self::H222_1 => "H.222.1",
316            Self::Iso13818_6TypeA => "ISO/IEC 13818-6 Type A",
317            Self::Iso13818_6TypeB => "ISO/IEC 13818-6 Type B",
318            Self::Iso13818_6TypeC => "ISO/IEC 13818-6 Type C",
319            Self::Iso13818_6TypeD => "ISO/IEC 13818-6 Type D",
320            Self::Auxiliary => "Auxiliary",
321            Self::AacAdts => "AAC ADTS",
322            Self::Mpeg4Video => "MPEG-4 Video",
323            Self::AacLatm => "AAC LATM",
324            Self::SlFlexMuxPes => "SL/FlexMux in PES",
325            Self::SlFlexMuxSections => "SL/FlexMux in Sections",
326            Self::SyncDownload => "Sync Download Protocol",
327            Self::MetadataPes => "Metadata in PES",
328            Self::MetadataSections => "Metadata in Sections",
329            Self::MetadataDataCarousel => "Metadata Data Carousel",
330            Self::MetadataObjectCarousel => "Metadata Object Carousel",
331            Self::MetadataSyncDownload => "Metadata Sync Download",
332            Self::Ipmp => "IPMP",
333            Self::H264 => "H.264/AVC",
334            Self::Iso14496_3Audio => "ISO/IEC 14496-3 Audio",
335            Self::Iso14496_17Text => "ISO/IEC 14496-17 Text",
336            Self::AuxiliaryVideo => "Auxiliary Video",
337            Self::Svc => "SVC",
338            Self::Mvc => "MVC",
339            Self::Jpeg2000 => "JPEG 2000",
340            Self::AdditionalViewH262 => "Additional View H.262 (3D)",
341            Self::AdditionalViewH264 => "Additional View H.264 (3D)",
342            Self::Hevc => "HEVC/H.265",
343            Self::HevcTemporalSubset => "HEVC Temporal Subset",
344            Self::Mvcd => "MVCD (H.264 Annex I)",
345            Self::Temi => "TEMI",
346            Self::HevcAnnexG => "HEVC Annex G",
347            Self::HevcAnnexGTemporal => "HEVC Annex G Temporal",
348            Self::HevcAnnexH => "HEVC Annex H",
349            Self::HevcAnnexHTemporal => "HEVC Annex H Temporal",
350            Self::GreenAccessUnits => "Green Access Units",
351            Self::MhasAudioMain => "MHAS Audio Main",
352            Self::MhasAudioAux => "MHAS Audio Aux",
353            Self::QualityAccessUnits => "Quality Access Units",
354            Self::MediaOrchestration => "Media Orchestration",
355            Self::MctsHevc => "MCTS HEVC",
356            Self::JpegXs => "JPEG XS",
357            Self::Vvc => "VVC/H.266",
358            Self::VvcTemporalSubset => "VVC Temporal Subset",
359            Self::Evc => "EVC",
360            Self::IpmpHigh => "IPMP (0x7F)",
361            Self::Ac3 => "AC-3",
362            Self::Scte35 => "SCTE-35",
363            Self::EAc3 => "E-AC-3",
364            Self::ReservedRange(_) => "Reserved",
365            Self::UserPrivate(_) => "User Private",
366        }
367    }
368}
369dvb_common::impl_spec_display!(StreamType, ReservedRange, UserPrivate);
370
371/// One elementary stream entry in the PMT's ES loop.
372#[derive(Debug, Clone, PartialEq, Eq)]
373#[cfg_attr(feature = "serde", derive(serde::Serialize))]
374#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
375pub struct PmtStream<'a> {
376    /// MPEG-2 stream_type byte (ISO/IEC 13818-1 Table 2-34).
377    pub stream_type: StreamType,
378    /// 13-bit elementary stream PID.
379    pub elementary_pid: u16,
380    /// Raw ES_info descriptor bytes; parsing lives in crate::descriptors.
381    /// Elementary-stream descriptor loop. Serializes as the typed descriptor
382    /// sequence; `.raw()` yields the wire bytes.
383    pub es_info: DescriptorLoop<'a>,
384}
385
386/// Program Map Table.
387#[non_exhaustive]
388#[derive(Debug, Clone, PartialEq, Eq)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize))]
390#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
391pub struct PmtSection<'a> {
392    /// Programme number from the table_id_extension field.
393    pub program_number: u16,
394    /// 5-bit version_number.
395    pub version_number: u8,
396    /// current_next_indicator bit.
397    pub current_next_indicator: bool,
398    /// section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
399    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
400    pub section_number: u8,
401    /// last_section_number in the sub-table sequence (ISO/IEC 13818-1 §2.4.4.8;
402    /// shall be 0x00 for conformant PMTs but preserved for round-trip fidelity).
403    pub last_section_number: u8,
404    /// 13-bit PCR PID.
405    pub pcr_pid: u16,
406    /// Raw program_info descriptor bytes.
407    /// Program-info descriptor loop. Serializes as the typed descriptor
408    /// sequence; `.raw()` yields the wire bytes.
409    pub program_info: DescriptorLoop<'a>,
410    /// Elementary streams in wire order.
411    pub streams: Vec<PmtStream<'a>>,
412}
413
414impl<'a> PmtSection<'a> {
415    /// Construct a `PmtSection` from its fields.
416    ///
417    /// This is the canonical constructor for external code. `PmtSection` is
418    /// `#[non_exhaustive]` so struct literal syntax is not available outside
419    /// the crate; use this function instead.
420    #[must_use]
421    #[allow(clippy::too_many_arguments)]
422    pub fn new(
423        program_number: u16,
424        version_number: u8,
425        current_next_indicator: bool,
426        section_number: u8,
427        last_section_number: u8,
428        pcr_pid: u16,
429        program_info: DescriptorLoop<'a>,
430        streams: Vec<PmtStream<'a>>,
431    ) -> Self {
432        Self {
433            program_number,
434            version_number,
435            current_next_indicator,
436            section_number,
437            last_section_number,
438            pcr_pid,
439            program_info,
440            streams,
441        }
442    }
443}
444
445impl<'a> Parse<'a> for PmtSection<'a> {
446    type Error = crate::error::Error;
447    fn parse(bytes: &'a [u8]) -> Result<Self> {
448        let min_len =
449            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
450        if bytes.len() < min_len {
451            return Err(Error::BufferTooShort {
452                need: min_len,
453                have: bytes.len(),
454                what: "PmtSection",
455            });
456        }
457        if bytes[0] != TABLE_ID {
458            return Err(Error::UnexpectedTableId {
459                table_id: bytes[0],
460                what: "PmtSection",
461                expected: &[TABLE_ID],
462            });
463        }
464
465        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
466        let total = super::check_section_length(
467            bytes.len(),
468            MIN_HEADER_LEN,
469            section_length as usize,
470            MIN_SECTION_LEN,
471        )?;
472
473        let program_number = u16::from_be_bytes([bytes[3], bytes[4]]);
474        let version_number = (bytes[5] >> 1) & 0x1F;
475        let current_next_indicator = (bytes[5] & 0x01) != 0;
476        let section_number = bytes[6];
477        let last_section_number = bytes[7];
478
479        let pcr_pid = (((bytes[8] & 0x1F) as u16) << 8) | bytes[9] as u16;
480        let program_info_length = (((bytes[10] & 0x0F) as usize) << 8) | bytes[11] as usize;
481
482        let prog_info_start =
483            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
484        let prog_info_end = prog_info_start + program_info_length;
485        let stream_loop_end = total - CRC_LEN;
486        if prog_info_end > stream_loop_end {
487            return Err(Error::SectionLengthOverflow {
488                declared: program_info_length,
489                available: stream_loop_end.saturating_sub(prog_info_start),
490            });
491        }
492        let program_info = DescriptorLoop::new(&bytes[prog_info_start..prog_info_end]);
493
494        let mut streams = Vec::new();
495        let mut pos = prog_info_end;
496        while pos + STREAM_HEADER_LEN <= stream_loop_end {
497            let stream_type = StreamType::from_u8(bytes[pos]);
498            let elementary_pid = (((bytes[pos + 1] & 0x1F) as u16) << 8) | bytes[pos + 2] as u16;
499            let es_info_length =
500                (((bytes[pos + 3] & 0x0F) as usize) << 8) | bytes[pos + 4] as usize;
501            let es_start = pos + STREAM_HEADER_LEN;
502            let es_end = es_start + es_info_length;
503            if es_end > stream_loop_end {
504                return Err(Error::SectionLengthOverflow {
505                    declared: es_info_length,
506                    available: stream_loop_end.saturating_sub(es_start),
507                });
508            }
509            streams.push(PmtStream {
510                stream_type,
511                elementary_pid,
512                es_info: DescriptorLoop::new(&bytes[es_start..es_end]),
513            });
514            pos = es_end;
515        }
516
517        Ok(PmtSection {
518            program_number,
519            version_number,
520            current_next_indicator,
521            section_number,
522            last_section_number,
523            pcr_pid,
524            program_info,
525            streams,
526        })
527    }
528}
529
530impl Serialize for PmtSection<'_> {
531    type Error = crate::error::Error;
532    fn serialized_len(&self) -> usize {
533        let streams_bytes: usize = self
534            .streams
535            .iter()
536            .map(|s| STREAM_HEADER_LEN + s.es_info.len())
537            .sum();
538        MIN_HEADER_LEN
539            + EXTENSION_HEADER_LEN
540            + PCR_PID_LEN
541            + PROG_INFO_LEN_BYTES
542            + self.program_info.len()
543            + streams_bytes
544            + CRC_LEN
545    }
546
547    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
548        let len = self.serialized_len();
549        if buf.len() < len {
550            return Err(Error::OutputBufferTooSmall {
551                need: len,
552                have: buf.len(),
553            });
554        }
555
556        let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
557        buf[0] = TABLE_ID;
558        buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
559        buf[2] = (section_length & 0xFF) as u8;
560        buf[3..5].copy_from_slice(&self.program_number.to_be_bytes());
561        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
562        buf[6] = self.section_number;
563        buf[7] = self.last_section_number;
564        buf[8] = 0xE0 | ((self.pcr_pid >> 8) as u8 & 0x1F);
565        buf[9] = (self.pcr_pid & 0xFF) as u8;
566        let pil = self.program_info.len() as u16;
567        buf[10] = 0xF0 | ((pil >> 8) as u8 & 0x0F);
568        buf[11] = (pil & 0xFF) as u8;
569
570        let prog_info_start =
571            MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
572        buf[prog_info_start..prog_info_start + self.program_info.len()]
573            .copy_from_slice(self.program_info.raw());
574
575        let mut pos = prog_info_start + self.program_info.len();
576        for stream in &self.streams {
577            buf[pos] = stream.stream_type.to_u8();
578            buf[pos + 1] = 0xE0 | ((stream.elementary_pid >> 8) as u8 & 0x1F);
579            buf[pos + 2] = (stream.elementary_pid & 0xFF) as u8;
580            let esl = stream.es_info.len() as u16;
581            buf[pos + 3] = 0xF0 | ((esl >> 8) as u8 & 0x0F);
582            buf[pos + 4] = (esl & 0xFF) as u8;
583            let es_start = pos + STREAM_HEADER_LEN;
584            buf[es_start..es_start + stream.es_info.len()].copy_from_slice(stream.es_info.raw());
585            pos = es_start + stream.es_info.len();
586        }
587
588        let crc_pos = len - CRC_LEN;
589        let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
590        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
591        Ok(len)
592    }
593}
594impl<'a> crate::traits::TableDef<'a> for PmtSection<'a> {
595    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
596    const NAME: &'static str = "PROGRAM_MAP";
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    /// Build a PMT section with given fields. Placeholder CRC.
604    fn build_pmt(
605        program_number: u16,
606        version: u8,
607        pcr_pid: u16,
608        program_info: &[u8],
609        streams: &[(u8, u16, Vec<u8>)],
610    ) -> Vec<u8> {
611        let streams_bytes: usize = streams
612            .iter()
613            .map(|(_, _, es)| STREAM_HEADER_LEN + es.len())
614            .sum();
615        let section_length: u16 = (EXTENSION_HEADER_LEN
616            + PCR_PID_LEN
617            + PROG_INFO_LEN_BYTES
618            + program_info.len()
619            + streams_bytes
620            + CRC_LEN) as u16;
621        let mut v = Vec::new();
622        v.push(TABLE_ID);
623        v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
624        v.push((section_length & 0xFF) as u8);
625        v.extend_from_slice(&program_number.to_be_bytes());
626        v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
627        v.push(0);
628        v.push(0);
629        v.push(0xE0 | ((pcr_pid >> 8) as u8 & 0x1F));
630        v.push((pcr_pid & 0xFF) as u8);
631        v.push(0xF0 | ((program_info.len() >> 8) as u8 & 0x0F));
632        v.push((program_info.len() & 0xFF) as u8);
633        v.extend_from_slice(program_info);
634        for (stype, pid, es) in streams {
635            v.push(*stype);
636            v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
637            v.push((pid & 0xFF) as u8);
638            v.push(0xF0 | ((es.len() >> 8) as u8 & 0x0F));
639            v.push((es.len() & 0xFF) as u8);
640            v.extend_from_slice(es);
641        }
642        v.extend_from_slice(&[0, 0, 0, 0]);
643        v
644    }
645
646    #[test]
647    fn parse_extracts_pcr_pid_and_program_info() {
648        let bytes = build_pmt(42, 5, 0x0100, &[0xAA, 0xBB], &[]);
649        let pmt = PmtSection::parse(&bytes).unwrap();
650        assert_eq!(pmt.program_number, 42);
651        assert_eq!(pmt.version_number, 5);
652        assert!(pmt.current_next_indicator);
653        assert_eq!(pmt.pcr_pid, 0x0100);
654        assert_eq!(pmt.program_info.raw(), &[0xAA, 0xBB]);
655        assert_eq!(pmt.streams.len(), 0);
656    }
657
658    #[test]
659    fn parse_elementary_streams_and_es_info_slices() {
660        let bytes = build_pmt(
661            1,
662            0,
663            0x101,
664            &[],
665            &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![0x33])],
666        );
667        let pmt = PmtSection::parse(&bytes).unwrap();
668        assert_eq!(pmt.streams.len(), 2);
669        assert_eq!(pmt.streams[0].stream_type, StreamType::Mpeg2Video);
670        assert_eq!(pmt.streams[0].elementary_pid, 0x102);
671        assert_eq!(pmt.streams[0].es_info.raw(), &[0x11, 0x22]);
672        assert_eq!(pmt.streams[1].stream_type, StreamType::H264);
673        assert_eq!(pmt.streams[1].elementary_pid, 0x103);
674        assert_eq!(pmt.streams[1].es_info.raw(), &[0x33]);
675    }
676
677    #[test]
678    fn parse_rejects_wrong_table_id() {
679        let mut bytes = build_pmt(1, 0, 0x100, &[], &[]);
680        bytes[0] = 0x00;
681        let err = PmtSection::parse(&bytes).unwrap_err();
682        assert!(matches!(
683            err,
684            Error::UnexpectedTableId { table_id: 0x00, .. }
685        ));
686    }
687
688    #[test]
689    fn parse_rejects_short_buffer() {
690        let err = PmtSection::parse(&[0x02, 0x00]).unwrap_err();
691        assert!(matches!(err, Error::BufferTooShort { .. }));
692    }
693
694    #[test]
695    fn serialize_round_trip_empty_program() {
696        let pmt = PmtSection {
697            program_number: 1,
698            version_number: 0,
699            current_next_indicator: true,
700            section_number: 0,
701            last_section_number: 0,
702            pcr_pid: 0x100,
703            program_info: DescriptorLoop::new(&[]),
704            streams: vec![],
705        };
706        let mut buf = vec![0u8; pmt.serialized_len()];
707        pmt.serialize_into(&mut buf).unwrap();
708        let re = PmtSection::parse(&buf).unwrap();
709        assert_eq!(pmt, re);
710    }
711
712    #[test]
713    fn serialize_round_trip_with_streams_and_descriptors() {
714        let prog_info: [u8; 3] = [0x09, 0x01, 0xFF];
715        let es1: [u8; 4] = [0x52, 0x02, 0xAA, 0xBB];
716        let es2: [u8; 2] = [0x0A, 0x00];
717        let pmt = PmtSection {
718            program_number: 0xABCD,
719            version_number: 7,
720            current_next_indicator: true,
721            section_number: 0,
722            last_section_number: 0,
723            pcr_pid: 0x1F0,
724            program_info: DescriptorLoop::new(&prog_info),
725            streams: vec![
726                PmtStream {
727                    stream_type: StreamType::Mpeg2Video,
728                    elementary_pid: 0x100,
729                    es_info: DescriptorLoop::new(&es1),
730                },
731                PmtStream {
732                    stream_type: StreamType::Mpeg1Audio,
733                    elementary_pid: 0x101,
734                    es_info: DescriptorLoop::new(&es2),
735                },
736                PmtStream {
737                    stream_type: StreamType::H264,
738                    elementary_pid: 0x102,
739                    es_info: DescriptorLoop::new(&[]),
740                },
741            ],
742        };
743        let mut buf = vec![0u8; pmt.serialized_len()];
744        pmt.serialize_into(&mut buf).unwrap();
745        let re = PmtSection::parse(&buf).unwrap();
746        assert_eq!(pmt, re);
747    }
748
749    #[test]
750    fn zero_elementary_streams_is_valid() {
751        let bytes = build_pmt(99, 0, 0x0100, &[], &[]);
752        let pmt = PmtSection::parse(&bytes).unwrap();
753        assert_eq!(pmt.streams.len(), 0);
754    }
755
756    #[test]
757    fn parse_preserves_raw_program_info_bytes() {
758        let pi = vec![0x09, 0x04, 0x01, 0x02, 0x03, 0x04];
759        let bytes = build_pmt(1, 0, 0x100, &pi, &[]);
760        let pmt = PmtSection::parse(&bytes).unwrap();
761        assert_eq!(pmt.program_info.raw(), &pi[..]);
762    }
763
764    #[test]
765    fn parse_rejects_zero_section_length() {
766        let mut buf = vec![0u8; 64];
767        buf[0] = TABLE_ID;
768        buf[1] = 0xF0;
769        buf[2] = 0x00;
770        for b in &mut buf[3..] {
771            *b = 0xFF;
772        }
773        assert!(matches!(
774            PmtSection::parse(&buf).unwrap_err(),
775            Error::SectionLengthOverflow { .. }
776        ));
777    }
778
779    #[test]
780    fn stream_type_full_range_round_trip() {
781        for byte in 0u8..=0xFF {
782            let st = StreamType::from_u8(byte);
783            assert_eq!(
784                st.to_u8(),
785                byte,
786                "StreamType round-trip failed for {byte:#04x}"
787            );
788        }
789    }
790
791    #[test]
792    fn stream_type_named_values() {
793        assert_eq!(StreamType::Mpeg2Video.to_u8(), 0x02);
794        assert_eq!(StreamType::H264.to_u8(), 0x1B);
795        assert_eq!(StreamType::Hevc.to_u8(), 0x24);
796        assert_eq!(StreamType::Vvc.to_u8(), 0x33);
797        assert_eq!(StreamType::MediaOrchestration.to_u8(), 0x30);
798        assert_eq!(StreamType::Mvcd.to_u8(), 0x26);
799        assert_eq!(StreamType::Temi.to_u8(), 0x27);
800        assert_eq!(StreamType::Ac3.to_u8(), 0x81);
801        assert_eq!(StreamType::Scte35.to_u8(), 0x86);
802        assert_eq!(StreamType::EAc3.to_u8(), 0x87);
803        assert_eq!(StreamType::AacAdts.to_u8(), 0x0F);
804        assert_eq!(StreamType::IpmpHigh.to_u8(), 0x7F);
805    }
806
807    #[test]
808    fn stream_type_names() {
809        assert_eq!(StreamType::Mpeg2Video.name(), "MPEG-2 Video");
810        assert_eq!(StreamType::H264.name(), "H.264/AVC");
811        assert_eq!(StreamType::Hevc.name(), "HEVC/H.265");
812        assert_eq!(StreamType::Vvc.name(), "VVC/H.266");
813        assert_eq!(StreamType::MediaOrchestration.name(), "Media Orchestration");
814        assert_eq!(StreamType::Mvcd.name(), "MVCD (H.264 Annex I)");
815        assert_eq!(StreamType::Temi.name(), "TEMI");
816        assert_eq!(StreamType::DsmCc.name(), "DSM-CC");
817        assert_eq!(StreamType::Ac3.name(), "AC-3");
818        assert_eq!(StreamType::Scte35.name(), "SCTE-35");
819    }
820
821    #[test]
822    fn stream_type_wire_to_name() {
823        assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
824        assert_eq!(StreamType::from_u8(0x1B).name(), "H.264/AVC");
825        assert_eq!(StreamType::from_u8(0x24).name(), "HEVC/H.265");
826        assert_eq!(StreamType::from_u8(0x00).name(), "Reserved");
827        assert_eq!(StreamType::from_u8(0x81).name(), "AC-3");
828    }
829
830    /// Regression test for #181: non-zero section_number/last_section_number must
831    /// survive a serialize → parse round-trip. Before the fix, serialize_into
832    /// hardcoded buf[6]=0 and buf[7]=0, so this test would fail.
833    #[test]
834    fn section_number_round_trip_nonzero() {
835        let pmt = PmtSection {
836            program_number: 42,
837            version_number: 1,
838            current_next_indicator: true,
839            section_number: 3,
840            last_section_number: 7,
841            pcr_pid: 0x0200,
842            program_info: DescriptorLoop::new(&[]),
843            streams: vec![],
844        };
845        let mut buf = vec![0u8; pmt.serialized_len()];
846        pmt.serialize_into(&mut buf).unwrap();
847        // Wire bytes[6] and [7] must carry the encoded values.
848        assert_eq!(buf[6], 3, "wire byte[6] must be section_number=3");
849        assert_eq!(buf[7], 7, "wire byte[7] must be last_section_number=7");
850        // Re-parse must recover the same values.
851        let re = PmtSection::parse(&buf).unwrap();
852        assert_eq!(re.section_number, 3);
853        assert_eq!(re.last_section_number, 7);
854        assert_eq!(pmt, re);
855    }
856}