1use crate::descriptors::DescriptorLoop;
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use dvb_common::{Parse, Serialize};
11
12pub const TABLE_ID: u8 = 0x02;
14pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[non_exhaustive]
45pub enum StreamType {
46 Reserved,
48 Mpeg1Video,
50 Mpeg2Video,
52 Mpeg1Audio,
54 Mpeg2Audio,
56 PrivateSections,
58 PesPrivateData,
60 Mheg,
62 DsmCc,
64 H222_1,
66 Iso13818_6TypeA,
68 Iso13818_6TypeB,
70 Iso13818_6TypeC,
72 Iso13818_6TypeD,
74 Auxiliary,
76 AacAdts,
78 Mpeg4Video,
80 AacLatm,
82 SlFlexMuxPes,
84 SlFlexMuxSections,
86 SyncDownload,
88 MetadataPes,
90 MetadataSections,
92 MetadataDataCarousel,
94 MetadataObjectCarousel,
96 MetadataSyncDownload,
98 Ipmp,
100 H264,
102 Iso14496_3Audio,
104 Iso14496_17Text,
106 AuxiliaryVideo,
108 Svc,
110 Mvc,
112 Jpeg2000,
114 AdditionalViewH262,
116 AdditionalViewH264,
118 Hevc,
120 HevcTemporalSubset,
122 Mvcd,
124 Temi,
126 HevcAnnexG,
128 HevcAnnexGTemporal,
130 HevcAnnexH,
132 HevcAnnexHTemporal,
134 GreenAccessUnits,
136 MhasAudioMain,
138 MhasAudioAux,
140 QualityAccessUnits,
142 MediaOrchestration,
144 MctsHevc,
146 JpegXs,
148 Vvc,
150 VvcTemporalSubset,
152 Evc,
154 Ac3,
156 Scte35,
158 EAc3,
160 IpmpHigh,
162 ReservedRange(u8),
164 UserPrivate(u8),
166}
167
168impl StreamType {
169 #[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 #[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 #[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#[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 pub stream_type: StreamType,
378 pub elementary_pid: u16,
380 pub es_info: DescriptorLoop<'a>,
384}
385
386#[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 pub program_number: u16,
394 pub version_number: u8,
396 pub current_next_indicator: bool,
398 pub section_number: u8,
401 pub last_section_number: u8,
404 pub pcr_pid: u16,
406 pub program_info: DescriptorLoop<'a>,
410 pub streams: Vec<PmtStream<'a>>,
412}
413
414impl<'a> PmtSection<'a> {
415 #[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 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 #[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 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 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}