1use crate::descriptors::DescriptorLoop;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11pub const TABLE_ID: u8 = 0x02;
13pub const PID: u16 = 0x0000;
16
17const MIN_HEADER_LEN: usize = 3;
18const EXTENSION_HEADER_LEN: usize = 5;
19const PCR_PID_LEN: usize = 2;
20const PROG_INFO_LEN_BYTES: usize = 2;
21const CRC_LEN: usize = 4;
22const MIN_SECTION_LEN: usize =
23 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
24const STREAM_HEADER_LEN: usize = 5;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize))]
43#[non_exhaustive]
44pub enum StreamType {
45 Reserved,
47 Mpeg1Video,
49 Mpeg2Video,
51 Mpeg1Audio,
53 Mpeg2Audio,
55 PrivateSections,
57 PesPrivateData,
59 Mheg,
61 DsmCc,
63 H222_1,
65 Iso13818_6TypeA,
67 Iso13818_6TypeB,
69 Iso13818_6TypeC,
71 Iso13818_6TypeD,
73 Auxiliary,
75 AacAdts,
77 Mpeg4Video,
79 AacLatm,
81 SlFlexMuxPes,
83 SlFlexMuxSections,
85 SyncDownload,
87 MetadataPes,
89 MetadataSections,
91 MetadataDataCarousel,
93 MetadataObjectCarousel,
95 MetadataSyncDownload,
97 Ipmp,
99 H264,
101 Iso14496_3Audio,
103 Iso14496_17Text,
105 AuxiliaryVideo,
107 Svc,
109 Mvc,
111 Jpeg2000,
113 AdditionalViewH262,
115 AdditionalViewH264,
117 Hevc,
119 HevcTemporalSubset,
121 Mvcd,
123 Temi,
125 HevcAnnexG,
127 HevcAnnexGTemporal,
129 HevcAnnexH,
131 HevcAnnexHTemporal,
133 GreenAccessUnits,
135 MhasAudioMain,
137 MhasAudioAux,
139 QualityAccessUnits,
141 MediaOrchestration,
143 MctsHevc,
145 JpegXs,
147 Vvc,
149 VvcTemporalSubset,
151 Evc,
153 Ac3,
155 Scte35,
157 EAc3,
159 IpmpHigh,
161 ReservedRange(u8),
163 UserPrivate(u8),
165}
166
167impl StreamType {
168 #[must_use]
170 pub fn from_u8(v: u8) -> Self {
171 match v {
172 0x00 => Self::Reserved,
173 0x01 => Self::Mpeg1Video,
174 0x02 => Self::Mpeg2Video,
175 0x03 => Self::Mpeg1Audio,
176 0x04 => Self::Mpeg2Audio,
177 0x05 => Self::PrivateSections,
178 0x06 => Self::PesPrivateData,
179 0x07 => Self::Mheg,
180 0x08 => Self::DsmCc,
181 0x09 => Self::H222_1,
182 0x0A => Self::Iso13818_6TypeA,
183 0x0B => Self::Iso13818_6TypeB,
184 0x0C => Self::Iso13818_6TypeC,
185 0x0D => Self::Iso13818_6TypeD,
186 0x0E => Self::Auxiliary,
187 0x0F => Self::AacAdts,
188 0x10 => Self::Mpeg4Video,
189 0x11 => Self::AacLatm,
190 0x12 => Self::SlFlexMuxPes,
191 0x13 => Self::SlFlexMuxSections,
192 0x14 => Self::SyncDownload,
193 0x15 => Self::MetadataPes,
194 0x16 => Self::MetadataSections,
195 0x17 => Self::MetadataDataCarousel,
196 0x18 => Self::MetadataObjectCarousel,
197 0x19 => Self::MetadataSyncDownload,
198 0x1A => Self::Ipmp,
199 0x1B => Self::H264,
200 0x1C => Self::Iso14496_3Audio,
201 0x1D => Self::Iso14496_17Text,
202 0x1E => Self::AuxiliaryVideo,
203 0x1F => Self::Svc,
204 0x20 => Self::Mvc,
205 0x21 => Self::Jpeg2000,
206 0x22 => Self::AdditionalViewH262,
207 0x23 => Self::AdditionalViewH264,
208 0x24 => Self::Hevc,
209 0x25 => Self::HevcTemporalSubset,
210 0x26 => Self::Mvcd,
211 0x27 => Self::Temi,
212 0x28 => Self::HevcAnnexG,
213 0x29 => Self::HevcAnnexGTemporal,
214 0x2A => Self::HevcAnnexH,
215 0x2B => Self::HevcAnnexHTemporal,
216 0x2C => Self::GreenAccessUnits,
217 0x2D => Self::MhasAudioMain,
218 0x2E => Self::MhasAudioAux,
219 0x2F => Self::QualityAccessUnits,
220 0x30 => Self::MediaOrchestration,
221 0x31 => Self::MctsHevc,
222 0x32 => Self::JpegXs,
223 0x33 => Self::Vvc,
224 0x34 => Self::VvcTemporalSubset,
225 0x35 => Self::Evc,
226 0x36..=0x7E => Self::ReservedRange(v),
227 0x7F => Self::IpmpHigh,
228 0x81 => Self::Ac3,
229 0x86 => Self::Scte35,
230 0x87 => Self::EAc3,
231 _ => Self::UserPrivate(v),
232 }
233 }
234
235 #[must_use]
237 pub fn to_u8(self) -> u8 {
238 match self {
239 Self::Reserved => 0x00,
240 Self::Mpeg1Video => 0x01,
241 Self::Mpeg2Video => 0x02,
242 Self::Mpeg1Audio => 0x03,
243 Self::Mpeg2Audio => 0x04,
244 Self::PrivateSections => 0x05,
245 Self::PesPrivateData => 0x06,
246 Self::Mheg => 0x07,
247 Self::DsmCc => 0x08,
248 Self::H222_1 => 0x09,
249 Self::Iso13818_6TypeA => 0x0A,
250 Self::Iso13818_6TypeB => 0x0B,
251 Self::Iso13818_6TypeC => 0x0C,
252 Self::Iso13818_6TypeD => 0x0D,
253 Self::Auxiliary => 0x0E,
254 Self::AacAdts => 0x0F,
255 Self::Mpeg4Video => 0x10,
256 Self::AacLatm => 0x11,
257 Self::SlFlexMuxPes => 0x12,
258 Self::SlFlexMuxSections => 0x13,
259 Self::SyncDownload => 0x14,
260 Self::MetadataPes => 0x15,
261 Self::MetadataSections => 0x16,
262 Self::MetadataDataCarousel => 0x17,
263 Self::MetadataObjectCarousel => 0x18,
264 Self::MetadataSyncDownload => 0x19,
265 Self::Ipmp => 0x1A,
266 Self::H264 => 0x1B,
267 Self::Iso14496_3Audio => 0x1C,
268 Self::Iso14496_17Text => 0x1D,
269 Self::AuxiliaryVideo => 0x1E,
270 Self::Svc => 0x1F,
271 Self::Mvc => 0x20,
272 Self::Jpeg2000 => 0x21,
273 Self::AdditionalViewH262 => 0x22,
274 Self::AdditionalViewH264 => 0x23,
275 Self::Hevc => 0x24,
276 Self::HevcTemporalSubset => 0x25,
277 Self::Mvcd => 0x26,
278 Self::Temi => 0x27,
279 Self::HevcAnnexG => 0x28,
280 Self::HevcAnnexGTemporal => 0x29,
281 Self::HevcAnnexH => 0x2A,
282 Self::HevcAnnexHTemporal => 0x2B,
283 Self::GreenAccessUnits => 0x2C,
284 Self::MhasAudioMain => 0x2D,
285 Self::MhasAudioAux => 0x2E,
286 Self::QualityAccessUnits => 0x2F,
287 Self::MediaOrchestration => 0x30,
288 Self::MctsHevc => 0x31,
289 Self::JpegXs => 0x32,
290 Self::Vvc => 0x33,
291 Self::VvcTemporalSubset => 0x34,
292 Self::Evc => 0x35,
293 Self::IpmpHigh => 0x7F,
294 Self::Ac3 => 0x81,
295 Self::Scte35 => 0x86,
296 Self::EAc3 => 0x87,
297 Self::ReservedRange(v) | Self::UserPrivate(v) => v,
298 }
299 }
300
301 #[must_use]
303 pub fn name(self) -> &'static str {
304 match self {
305 Self::Reserved => "Reserved",
306 Self::Mpeg1Video => "MPEG-1 Video",
307 Self::Mpeg2Video => "MPEG-2 Video",
308 Self::Mpeg1Audio => "MPEG-1 Audio",
309 Self::Mpeg2Audio => "MPEG-2 Audio",
310 Self::PrivateSections => "Private Sections",
311 Self::PesPrivateData => "PES Private Data",
312 Self::Mheg => "MHEG",
313 Self::DsmCc => "DSM-CC",
314 Self::H222_1 => "H.222.1",
315 Self::Iso13818_6TypeA => "ISO/IEC 13818-6 Type A",
316 Self::Iso13818_6TypeB => "ISO/IEC 13818-6 Type B",
317 Self::Iso13818_6TypeC => "ISO/IEC 13818-6 Type C",
318 Self::Iso13818_6TypeD => "ISO/IEC 13818-6 Type D",
319 Self::Auxiliary => "Auxiliary",
320 Self::AacAdts => "AAC ADTS",
321 Self::Mpeg4Video => "MPEG-4 Video",
322 Self::AacLatm => "AAC LATM",
323 Self::SlFlexMuxPes => "SL/FlexMux in PES",
324 Self::SlFlexMuxSections => "SL/FlexMux in Sections",
325 Self::SyncDownload => "Sync Download Protocol",
326 Self::MetadataPes => "Metadata in PES",
327 Self::MetadataSections => "Metadata in Sections",
328 Self::MetadataDataCarousel => "Metadata Data Carousel",
329 Self::MetadataObjectCarousel => "Metadata Object Carousel",
330 Self::MetadataSyncDownload => "Metadata Sync Download",
331 Self::Ipmp => "IPMP",
332 Self::H264 => "H.264/AVC",
333 Self::Iso14496_3Audio => "ISO/IEC 14496-3 Audio",
334 Self::Iso14496_17Text => "ISO/IEC 14496-17 Text",
335 Self::AuxiliaryVideo => "Auxiliary Video",
336 Self::Svc => "SVC",
337 Self::Mvc => "MVC",
338 Self::Jpeg2000 => "JPEG 2000",
339 Self::AdditionalViewH262 => "Additional View H.262 (3D)",
340 Self::AdditionalViewH264 => "Additional View H.264 (3D)",
341 Self::Hevc => "HEVC/H.265",
342 Self::HevcTemporalSubset => "HEVC Temporal Subset",
343 Self::Mvcd => "MVCD (H.264 Annex I)",
344 Self::Temi => "TEMI",
345 Self::HevcAnnexG => "HEVC Annex G",
346 Self::HevcAnnexGTemporal => "HEVC Annex G Temporal",
347 Self::HevcAnnexH => "HEVC Annex H",
348 Self::HevcAnnexHTemporal => "HEVC Annex H Temporal",
349 Self::GreenAccessUnits => "Green Access Units",
350 Self::MhasAudioMain => "MHAS Audio Main",
351 Self::MhasAudioAux => "MHAS Audio Aux",
352 Self::QualityAccessUnits => "Quality Access Units",
353 Self::MediaOrchestration => "Media Orchestration",
354 Self::MctsHevc => "MCTS HEVC",
355 Self::JpegXs => "JPEG XS",
356 Self::Vvc => "VVC/H.266",
357 Self::VvcTemporalSubset => "VVC Temporal Subset",
358 Self::Evc => "EVC",
359 Self::IpmpHigh => "IPMP (0x7F)",
360 Self::Ac3 => "AC-3",
361 Self::Scte35 => "SCTE-35",
362 Self::EAc3 => "E-AC-3",
363 Self::ReservedRange(_) => "Reserved",
364 Self::UserPrivate(_) => "User Private",
365 }
366 }
367}
368
369#[derive(Debug, Clone, PartialEq, Eq)]
371#[cfg_attr(feature = "serde", derive(serde::Serialize))]
372#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
373pub struct PmtStream<'a> {
374 pub stream_type: StreamType,
376 pub elementary_pid: u16,
378 pub es_info: DescriptorLoop<'a>,
382}
383
384#[non_exhaustive]
386#[derive(Debug, Clone, PartialEq, Eq)]
387#[cfg_attr(feature = "serde", derive(serde::Serialize))]
388#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
389pub struct PmtSection<'a> {
390 pub program_number: u16,
392 pub version_number: u8,
394 pub current_next_indicator: bool,
396 pub section_number: u8,
399 pub last_section_number: u8,
402 pub pcr_pid: u16,
404 pub program_info: DescriptorLoop<'a>,
408 pub streams: Vec<PmtStream<'a>>,
410}
411
412impl<'a> PmtSection<'a> {
413 #[must_use]
419 #[allow(clippy::too_many_arguments)]
420 pub fn new(
421 program_number: u16,
422 version_number: u8,
423 current_next_indicator: bool,
424 section_number: u8,
425 last_section_number: u8,
426 pcr_pid: u16,
427 program_info: DescriptorLoop<'a>,
428 streams: Vec<PmtStream<'a>>,
429 ) -> Self {
430 Self {
431 program_number,
432 version_number,
433 current_next_indicator,
434 section_number,
435 last_section_number,
436 pcr_pid,
437 program_info,
438 streams,
439 }
440 }
441}
442
443impl<'a> Parse<'a> for PmtSection<'a> {
444 type Error = crate::error::Error;
445 fn parse(bytes: &'a [u8]) -> Result<Self> {
446 let min_len =
447 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
448 if bytes.len() < min_len {
449 return Err(Error::BufferTooShort {
450 need: min_len,
451 have: bytes.len(),
452 what: "PmtSection",
453 });
454 }
455 if bytes[0] != TABLE_ID {
456 return Err(Error::UnexpectedTableId {
457 table_id: bytes[0],
458 what: "PmtSection",
459 expected: &[TABLE_ID],
460 });
461 }
462
463 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
464 let total = super::check_section_length(
465 bytes.len(),
466 MIN_HEADER_LEN,
467 section_length as usize,
468 MIN_SECTION_LEN,
469 )?;
470
471 let program_number = u16::from_be_bytes([bytes[3], bytes[4]]);
472 let version_number = (bytes[5] >> 1) & 0x1F;
473 let current_next_indicator = (bytes[5] & 0x01) != 0;
474 let section_number = bytes[6];
475 let last_section_number = bytes[7];
476
477 let pcr_pid = (((bytes[8] & 0x1F) as u16) << 8) | bytes[9] as u16;
478 let program_info_length = (((bytes[10] & 0x0F) as usize) << 8) | bytes[11] as usize;
479
480 let prog_info_start =
481 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
482 let prog_info_end = prog_info_start + program_info_length;
483 let stream_loop_end = total - CRC_LEN;
484 if prog_info_end > stream_loop_end {
485 return Err(Error::SectionLengthOverflow {
486 declared: program_info_length,
487 available: stream_loop_end.saturating_sub(prog_info_start),
488 });
489 }
490 let program_info = DescriptorLoop::new(&bytes[prog_info_start..prog_info_end]);
491
492 let mut streams = Vec::new();
493 let mut pos = prog_info_end;
494 while pos + STREAM_HEADER_LEN <= stream_loop_end {
495 let stream_type = StreamType::from_u8(bytes[pos]);
496 let elementary_pid = (((bytes[pos + 1] & 0x1F) as u16) << 8) | bytes[pos + 2] as u16;
497 let es_info_length =
498 (((bytes[pos + 3] & 0x0F) as usize) << 8) | bytes[pos + 4] as usize;
499 let es_start = pos + STREAM_HEADER_LEN;
500 let es_end = es_start + es_info_length;
501 if es_end > stream_loop_end {
502 return Err(Error::SectionLengthOverflow {
503 declared: es_info_length,
504 available: stream_loop_end.saturating_sub(es_start),
505 });
506 }
507 streams.push(PmtStream {
508 stream_type,
509 elementary_pid,
510 es_info: DescriptorLoop::new(&bytes[es_start..es_end]),
511 });
512 pos = es_end;
513 }
514
515 Ok(PmtSection {
516 program_number,
517 version_number,
518 current_next_indicator,
519 section_number,
520 last_section_number,
521 pcr_pid,
522 program_info,
523 streams,
524 })
525 }
526}
527
528impl Serialize for PmtSection<'_> {
529 type Error = crate::error::Error;
530 fn serialized_len(&self) -> usize {
531 let streams_bytes: usize = self
532 .streams
533 .iter()
534 .map(|s| STREAM_HEADER_LEN + s.es_info.len())
535 .sum();
536 MIN_HEADER_LEN
537 + EXTENSION_HEADER_LEN
538 + PCR_PID_LEN
539 + PROG_INFO_LEN_BYTES
540 + self.program_info.len()
541 + streams_bytes
542 + CRC_LEN
543 }
544
545 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
546 let len = self.serialized_len();
547 if buf.len() < len {
548 return Err(Error::OutputBufferTooSmall {
549 need: len,
550 have: buf.len(),
551 });
552 }
553
554 let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
555 buf[0] = TABLE_ID;
556 buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
557 buf[2] = (section_length & 0xFF) as u8;
558 buf[3..5].copy_from_slice(&self.program_number.to_be_bytes());
559 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
560 buf[6] = self.section_number;
561 buf[7] = self.last_section_number;
562 buf[8] = 0xE0 | ((self.pcr_pid >> 8) as u8 & 0x1F);
563 buf[9] = (self.pcr_pid & 0xFF) as u8;
564 let pil = self.program_info.len() as u16;
565 buf[10] = 0xF0 | ((pil >> 8) as u8 & 0x0F);
566 buf[11] = (pil & 0xFF) as u8;
567
568 let prog_info_start =
569 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
570 buf[prog_info_start..prog_info_start + self.program_info.len()]
571 .copy_from_slice(self.program_info.raw());
572
573 let mut pos = prog_info_start + self.program_info.len();
574 for stream in &self.streams {
575 buf[pos] = stream.stream_type.to_u8();
576 buf[pos + 1] = 0xE0 | ((stream.elementary_pid >> 8) as u8 & 0x1F);
577 buf[pos + 2] = (stream.elementary_pid & 0xFF) as u8;
578 let esl = stream.es_info.len() as u16;
579 buf[pos + 3] = 0xF0 | ((esl >> 8) as u8 & 0x0F);
580 buf[pos + 4] = (esl & 0xFF) as u8;
581 let es_start = pos + STREAM_HEADER_LEN;
582 buf[es_start..es_start + stream.es_info.len()].copy_from_slice(stream.es_info.raw());
583 pos = es_start + stream.es_info.len();
584 }
585
586 let crc_pos = len - CRC_LEN;
587 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
588 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
589 Ok(len)
590 }
591}
592impl<'a> crate::traits::TableDef<'a> for PmtSection<'a> {
593 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
594 const NAME: &'static str = "PROGRAM_MAP";
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 fn build_pmt(
603 program_number: u16,
604 version: u8,
605 pcr_pid: u16,
606 program_info: &[u8],
607 streams: &[(u8, u16, Vec<u8>)],
608 ) -> Vec<u8> {
609 let streams_bytes: usize = streams
610 .iter()
611 .map(|(_, _, es)| STREAM_HEADER_LEN + es.len())
612 .sum();
613 let section_length: u16 = (EXTENSION_HEADER_LEN
614 + PCR_PID_LEN
615 + PROG_INFO_LEN_BYTES
616 + program_info.len()
617 + streams_bytes
618 + CRC_LEN) as u16;
619 let mut v = Vec::new();
620 v.push(TABLE_ID);
621 v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
622 v.push((section_length & 0xFF) as u8);
623 v.extend_from_slice(&program_number.to_be_bytes());
624 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
625 v.push(0);
626 v.push(0);
627 v.push(0xE0 | ((pcr_pid >> 8) as u8 & 0x1F));
628 v.push((pcr_pid & 0xFF) as u8);
629 v.push(0xF0 | ((program_info.len() >> 8) as u8 & 0x0F));
630 v.push((program_info.len() & 0xFF) as u8);
631 v.extend_from_slice(program_info);
632 for (stype, pid, es) in streams {
633 v.push(*stype);
634 v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
635 v.push((pid & 0xFF) as u8);
636 v.push(0xF0 | ((es.len() >> 8) as u8 & 0x0F));
637 v.push((es.len() & 0xFF) as u8);
638 v.extend_from_slice(es);
639 }
640 v.extend_from_slice(&[0, 0, 0, 0]);
641 v
642 }
643
644 #[test]
645 fn parse_extracts_pcr_pid_and_program_info() {
646 let bytes = build_pmt(42, 5, 0x0100, &[0xAA, 0xBB], &[]);
647 let pmt = PmtSection::parse(&bytes).unwrap();
648 assert_eq!(pmt.program_number, 42);
649 assert_eq!(pmt.version_number, 5);
650 assert!(pmt.current_next_indicator);
651 assert_eq!(pmt.pcr_pid, 0x0100);
652 assert_eq!(pmt.program_info.raw(), &[0xAA, 0xBB]);
653 assert_eq!(pmt.streams.len(), 0);
654 }
655
656 #[test]
657 fn parse_elementary_streams_and_es_info_slices() {
658 let bytes = build_pmt(
659 1,
660 0,
661 0x101,
662 &[],
663 &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![0x33])],
664 );
665 let pmt = PmtSection::parse(&bytes).unwrap();
666 assert_eq!(pmt.streams.len(), 2);
667 assert_eq!(pmt.streams[0].stream_type, StreamType::Mpeg2Video);
668 assert_eq!(pmt.streams[0].elementary_pid, 0x102);
669 assert_eq!(pmt.streams[0].es_info.raw(), &[0x11, 0x22]);
670 assert_eq!(pmt.streams[1].stream_type, StreamType::H264);
671 assert_eq!(pmt.streams[1].elementary_pid, 0x103);
672 assert_eq!(pmt.streams[1].es_info.raw(), &[0x33]);
673 }
674
675 #[test]
676 fn parse_rejects_wrong_table_id() {
677 let mut bytes = build_pmt(1, 0, 0x100, &[], &[]);
678 bytes[0] = 0x00;
679 let err = PmtSection::parse(&bytes).unwrap_err();
680 assert!(matches!(
681 err,
682 Error::UnexpectedTableId { table_id: 0x00, .. }
683 ));
684 }
685
686 #[test]
687 fn parse_rejects_short_buffer() {
688 let err = PmtSection::parse(&[0x02, 0x00]).unwrap_err();
689 assert!(matches!(err, Error::BufferTooShort { .. }));
690 }
691
692 #[test]
693 fn serialize_round_trip_empty_program() {
694 let pmt = PmtSection {
695 program_number: 1,
696 version_number: 0,
697 current_next_indicator: true,
698 section_number: 0,
699 last_section_number: 0,
700 pcr_pid: 0x100,
701 program_info: DescriptorLoop::new(&[]),
702 streams: vec![],
703 };
704 let mut buf = vec![0u8; pmt.serialized_len()];
705 pmt.serialize_into(&mut buf).unwrap();
706 let re = PmtSection::parse(&buf).unwrap();
707 assert_eq!(pmt, re);
708 }
709
710 #[test]
711 fn serialize_round_trip_with_streams_and_descriptors() {
712 let prog_info: [u8; 3] = [0x09, 0x01, 0xFF];
713 let es1: [u8; 4] = [0x52, 0x02, 0xAA, 0xBB];
714 let es2: [u8; 2] = [0x0A, 0x00];
715 let pmt = PmtSection {
716 program_number: 0xABCD,
717 version_number: 7,
718 current_next_indicator: true,
719 section_number: 0,
720 last_section_number: 0,
721 pcr_pid: 0x1F0,
722 program_info: DescriptorLoop::new(&prog_info),
723 streams: vec![
724 PmtStream {
725 stream_type: StreamType::Mpeg2Video,
726 elementary_pid: 0x100,
727 es_info: DescriptorLoop::new(&es1),
728 },
729 PmtStream {
730 stream_type: StreamType::Mpeg1Audio,
731 elementary_pid: 0x101,
732 es_info: DescriptorLoop::new(&es2),
733 },
734 PmtStream {
735 stream_type: StreamType::H264,
736 elementary_pid: 0x102,
737 es_info: DescriptorLoop::new(&[]),
738 },
739 ],
740 };
741 let mut buf = vec![0u8; pmt.serialized_len()];
742 pmt.serialize_into(&mut buf).unwrap();
743 let re = PmtSection::parse(&buf).unwrap();
744 assert_eq!(pmt, re);
745 }
746
747 #[test]
748 fn zero_elementary_streams_is_valid() {
749 let bytes = build_pmt(99, 0, 0x0100, &[], &[]);
750 let pmt = PmtSection::parse(&bytes).unwrap();
751 assert_eq!(pmt.streams.len(), 0);
752 }
753
754 #[test]
755 fn parse_preserves_raw_program_info_bytes() {
756 let pi = vec![0x09, 0x04, 0x01, 0x02, 0x03, 0x04];
757 let bytes = build_pmt(1, 0, 0x100, &pi, &[]);
758 let pmt = PmtSection::parse(&bytes).unwrap();
759 assert_eq!(pmt.program_info.raw(), &pi[..]);
760 }
761
762 #[test]
763 fn parse_rejects_zero_section_length() {
764 let mut buf = vec![0u8; 64];
765 buf[0] = TABLE_ID;
766 buf[1] = 0xF0;
767 buf[2] = 0x00;
768 for b in &mut buf[3..] {
769 *b = 0xFF;
770 }
771 assert!(matches!(
772 PmtSection::parse(&buf).unwrap_err(),
773 Error::SectionLengthOverflow { .. }
774 ));
775 }
776
777 #[test]
778 fn stream_type_full_range_round_trip() {
779 for byte in 0u8..=0xFF {
780 let st = StreamType::from_u8(byte);
781 assert_eq!(
782 st.to_u8(),
783 byte,
784 "StreamType round-trip failed for {byte:#04x}"
785 );
786 }
787 }
788
789 #[test]
790 fn stream_type_named_values() {
791 assert_eq!(StreamType::Mpeg2Video.to_u8(), 0x02);
792 assert_eq!(StreamType::H264.to_u8(), 0x1B);
793 assert_eq!(StreamType::Hevc.to_u8(), 0x24);
794 assert_eq!(StreamType::Vvc.to_u8(), 0x33);
795 assert_eq!(StreamType::MediaOrchestration.to_u8(), 0x30);
796 assert_eq!(StreamType::Mvcd.to_u8(), 0x26);
797 assert_eq!(StreamType::Temi.to_u8(), 0x27);
798 assert_eq!(StreamType::Ac3.to_u8(), 0x81);
799 assert_eq!(StreamType::Scte35.to_u8(), 0x86);
800 assert_eq!(StreamType::EAc3.to_u8(), 0x87);
801 assert_eq!(StreamType::AacAdts.to_u8(), 0x0F);
802 assert_eq!(StreamType::IpmpHigh.to_u8(), 0x7F);
803 }
804
805 #[test]
806 fn stream_type_names() {
807 assert_eq!(StreamType::Mpeg2Video.name(), "MPEG-2 Video");
808 assert_eq!(StreamType::H264.name(), "H.264/AVC");
809 assert_eq!(StreamType::Hevc.name(), "HEVC/H.265");
810 assert_eq!(StreamType::Vvc.name(), "VVC/H.266");
811 assert_eq!(StreamType::MediaOrchestration.name(), "Media Orchestration");
812 assert_eq!(StreamType::Mvcd.name(), "MVCD (H.264 Annex I)");
813 assert_eq!(StreamType::Temi.name(), "TEMI");
814 assert_eq!(StreamType::DsmCc.name(), "DSM-CC");
815 assert_eq!(StreamType::Ac3.name(), "AC-3");
816 assert_eq!(StreamType::Scte35.name(), "SCTE-35");
817 }
818
819 #[test]
820 fn stream_type_wire_to_name() {
821 assert_eq!(StreamType::from_u8(0x02).name(), "MPEG-2 Video");
822 assert_eq!(StreamType::from_u8(0x1B).name(), "H.264/AVC");
823 assert_eq!(StreamType::from_u8(0x24).name(), "HEVC/H.265");
824 assert_eq!(StreamType::from_u8(0x00).name(), "Reserved");
825 assert_eq!(StreamType::from_u8(0x81).name(), "AC-3");
826 }
827
828 #[test]
832 fn section_number_round_trip_nonzero() {
833 let pmt = PmtSection {
834 program_number: 42,
835 version_number: 1,
836 current_next_indicator: true,
837 section_number: 3,
838 last_section_number: 7,
839 pcr_pid: 0x0200,
840 program_info: DescriptorLoop::new(&[]),
841 streams: vec![],
842 };
843 let mut buf = vec![0u8; pmt.serialized_len()];
844 pmt.serialize_into(&mut buf).unwrap();
845 assert_eq!(buf[6], 3, "wire byte[6] must be section_number=3");
847 assert_eq!(buf[7], 7, "wire byte[7] must be last_section_number=7");
848 let re = PmtSection::parse(&buf).unwrap();
850 assert_eq!(re.section_number, 3);
851 assert_eq!(re.last_section_number, 7);
852 assert_eq!(pmt, re);
853 }
854}