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#[derive(Debug, Clone, PartialEq, Eq)]
386#[cfg_attr(feature = "serde", derive(serde::Serialize))]
387#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
388pub struct PmtSection<'a> {
389 pub program_number: u16,
391 pub version_number: u8,
393 pub current_next_indicator: bool,
395 pub pcr_pid: u16,
397 pub program_info: DescriptorLoop<'a>,
401 pub streams: Vec<PmtStream<'a>>,
403}
404
405impl<'a> Parse<'a> for PmtSection<'a> {
406 type Error = crate::error::Error;
407 fn parse(bytes: &'a [u8]) -> Result<Self> {
408 let min_len =
409 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES + CRC_LEN;
410 if bytes.len() < min_len {
411 return Err(Error::BufferTooShort {
412 need: min_len,
413 have: bytes.len(),
414 what: "PmtSection",
415 });
416 }
417 if bytes[0] != TABLE_ID {
418 return Err(Error::UnexpectedTableId {
419 table_id: bytes[0],
420 what: "PmtSection",
421 expected: &[TABLE_ID],
422 });
423 }
424
425 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
426 let total = super::check_section_length(
427 bytes.len(),
428 MIN_HEADER_LEN,
429 section_length as usize,
430 MIN_SECTION_LEN,
431 )?;
432
433 let program_number = u16::from_be_bytes([bytes[3], bytes[4]]);
434 let version_number = (bytes[5] >> 1) & 0x1F;
435 let current_next_indicator = (bytes[5] & 0x01) != 0;
436
437 let pcr_pid = (((bytes[8] & 0x1F) as u16) << 8) | bytes[9] as u16;
438 let program_info_length = (((bytes[10] & 0x0F) as usize) << 8) | bytes[11] as usize;
439
440 let prog_info_start =
441 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
442 let prog_info_end = prog_info_start + program_info_length;
443 let stream_loop_end = total - CRC_LEN;
444 if prog_info_end > stream_loop_end {
445 return Err(Error::SectionLengthOverflow {
446 declared: program_info_length,
447 available: stream_loop_end.saturating_sub(prog_info_start),
448 });
449 }
450 let program_info = DescriptorLoop::new(&bytes[prog_info_start..prog_info_end]);
451
452 let mut streams = Vec::new();
453 let mut pos = prog_info_end;
454 while pos + STREAM_HEADER_LEN <= stream_loop_end {
455 let stream_type = StreamType::from_u8(bytes[pos]);
456 let elementary_pid = (((bytes[pos + 1] & 0x1F) as u16) << 8) | bytes[pos + 2] as u16;
457 let es_info_length =
458 (((bytes[pos + 3] & 0x0F) as usize) << 8) | bytes[pos + 4] as usize;
459 let es_start = pos + STREAM_HEADER_LEN;
460 let es_end = es_start + es_info_length;
461 if es_end > stream_loop_end {
462 return Err(Error::SectionLengthOverflow {
463 declared: es_info_length,
464 available: stream_loop_end.saturating_sub(es_start),
465 });
466 }
467 streams.push(PmtStream {
468 stream_type,
469 elementary_pid,
470 es_info: DescriptorLoop::new(&bytes[es_start..es_end]),
471 });
472 pos = es_end;
473 }
474
475 Ok(PmtSection {
476 program_number,
477 version_number,
478 current_next_indicator,
479 pcr_pid,
480 program_info,
481 streams,
482 })
483 }
484}
485
486impl Serialize for PmtSection<'_> {
487 type Error = crate::error::Error;
488 fn serialized_len(&self) -> usize {
489 let streams_bytes: usize = self
490 .streams
491 .iter()
492 .map(|s| STREAM_HEADER_LEN + s.es_info.len())
493 .sum();
494 MIN_HEADER_LEN
495 + EXTENSION_HEADER_LEN
496 + PCR_PID_LEN
497 + PROG_INFO_LEN_BYTES
498 + self.program_info.len()
499 + streams_bytes
500 + CRC_LEN
501 }
502
503 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
504 let len = self.serialized_len();
505 if buf.len() < len {
506 return Err(Error::OutputBufferTooSmall {
507 need: len,
508 have: buf.len(),
509 });
510 }
511
512 let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
513 buf[0] = TABLE_ID;
514 buf[1] = super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F);
515 buf[2] = (section_length & 0xFF) as u8;
516 buf[3..5].copy_from_slice(&self.program_number.to_be_bytes());
517 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
518 buf[6] = 0;
519 buf[7] = 0;
520 buf[8] = 0xE0 | ((self.pcr_pid >> 8) as u8 & 0x1F);
521 buf[9] = (self.pcr_pid & 0xFF) as u8;
522 let pil = self.program_info.len() as u16;
523 buf[10] = 0xF0 | ((pil >> 8) as u8 & 0x0F);
524 buf[11] = (pil & 0xFF) as u8;
525
526 let prog_info_start =
527 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + PCR_PID_LEN + PROG_INFO_LEN_BYTES;
528 buf[prog_info_start..prog_info_start + self.program_info.len()]
529 .copy_from_slice(self.program_info.raw());
530
531 let mut pos = prog_info_start + self.program_info.len();
532 for stream in &self.streams {
533 buf[pos] = stream.stream_type.to_u8();
534 buf[pos + 1] = 0xE0 | ((stream.elementary_pid >> 8) as u8 & 0x1F);
535 buf[pos + 2] = (stream.elementary_pid & 0xFF) as u8;
536 let esl = stream.es_info.len() as u16;
537 buf[pos + 3] = 0xF0 | ((esl >> 8) as u8 & 0x0F);
538 buf[pos + 4] = (esl & 0xFF) as u8;
539 let es_start = pos + STREAM_HEADER_LEN;
540 buf[es_start..es_start + stream.es_info.len()].copy_from_slice(stream.es_info.raw());
541 pos = es_start + stream.es_info.len();
542 }
543
544 let crc_pos = len - CRC_LEN;
545 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
546 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
547 Ok(len)
548 }
549}
550impl<'a> crate::traits::TableDef<'a> for PmtSection<'a> {
551 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
552 const NAME: &'static str = "PROGRAM_MAP";
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 fn build_pmt(
561 program_number: u16,
562 version: u8,
563 pcr_pid: u16,
564 program_info: &[u8],
565 streams: &[(u8, u16, Vec<u8>)],
566 ) -> Vec<u8> {
567 let streams_bytes: usize = streams
568 .iter()
569 .map(|(_, _, es)| STREAM_HEADER_LEN + es.len())
570 .sum();
571 let section_length: u16 = (EXTENSION_HEADER_LEN
572 + PCR_PID_LEN
573 + PROG_INFO_LEN_BYTES
574 + program_info.len()
575 + streams_bytes
576 + CRC_LEN) as u16;
577 let mut v = Vec::new();
578 v.push(TABLE_ID);
579 v.push(super::super::SECTION_B1_FLAGS_PSI | ((section_length >> 8) as u8 & 0x0F));
580 v.push((section_length & 0xFF) as u8);
581 v.extend_from_slice(&program_number.to_be_bytes());
582 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
583 v.push(0);
584 v.push(0);
585 v.push(0xE0 | ((pcr_pid >> 8) as u8 & 0x1F));
586 v.push((pcr_pid & 0xFF) as u8);
587 v.push(0xF0 | ((program_info.len() >> 8) as u8 & 0x0F));
588 v.push((program_info.len() & 0xFF) as u8);
589 v.extend_from_slice(program_info);
590 for (stype, pid, es) in streams {
591 v.push(*stype);
592 v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
593 v.push((pid & 0xFF) as u8);
594 v.push(0xF0 | ((es.len() >> 8) as u8 & 0x0F));
595 v.push((es.len() & 0xFF) as u8);
596 v.extend_from_slice(es);
597 }
598 v.extend_from_slice(&[0, 0, 0, 0]);
599 v
600 }
601
602 #[test]
603 fn parse_extracts_pcr_pid_and_program_info() {
604 let bytes = build_pmt(42, 5, 0x0100, &[0xAA, 0xBB], &[]);
605 let pmt = PmtSection::parse(&bytes).unwrap();
606 assert_eq!(pmt.program_number, 42);
607 assert_eq!(pmt.version_number, 5);
608 assert!(pmt.current_next_indicator);
609 assert_eq!(pmt.pcr_pid, 0x0100);
610 assert_eq!(pmt.program_info.raw(), &[0xAA, 0xBB]);
611 assert_eq!(pmt.streams.len(), 0);
612 }
613
614 #[test]
615 fn parse_elementary_streams_and_es_info_slices() {
616 let bytes = build_pmt(
617 1,
618 0,
619 0x101,
620 &[],
621 &[(0x02, 0x102, vec![0x11, 0x22]), (0x1B, 0x103, vec![0x33])],
622 );
623 let pmt = PmtSection::parse(&bytes).unwrap();
624 assert_eq!(pmt.streams.len(), 2);
625 assert_eq!(pmt.streams[0].stream_type, StreamType::Mpeg2Video);
626 assert_eq!(pmt.streams[0].elementary_pid, 0x102);
627 assert_eq!(pmt.streams[0].es_info.raw(), &[0x11, 0x22]);
628 assert_eq!(pmt.streams[1].stream_type, StreamType::H264);
629 assert_eq!(pmt.streams[1].elementary_pid, 0x103);
630 assert_eq!(pmt.streams[1].es_info.raw(), &[0x33]);
631 }
632
633 #[test]
634 fn parse_rejects_wrong_table_id() {
635 let mut bytes = build_pmt(1, 0, 0x100, &[], &[]);
636 bytes[0] = 0x00;
637 let err = PmtSection::parse(&bytes).unwrap_err();
638 assert!(matches!(
639 err,
640 Error::UnexpectedTableId { table_id: 0x00, .. }
641 ));
642 }
643
644 #[test]
645 fn parse_rejects_short_buffer() {
646 let err = PmtSection::parse(&[0x02, 0x00]).unwrap_err();
647 assert!(matches!(err, Error::BufferTooShort { .. }));
648 }
649
650 #[test]
651 fn serialize_round_trip_empty_program() {
652 let pmt = PmtSection {
653 program_number: 1,
654 version_number: 0,
655 current_next_indicator: true,
656 pcr_pid: 0x100,
657 program_info: DescriptorLoop::new(&[]),
658 streams: vec![],
659 };
660 let mut buf = vec![0u8; pmt.serialized_len()];
661 pmt.serialize_into(&mut buf).unwrap();
662 let re = PmtSection::parse(&buf).unwrap();
663 assert_eq!(pmt, re);
664 }
665
666 #[test]
667 fn serialize_round_trip_with_streams_and_descriptors() {
668 let prog_info: [u8; 3] = [0x09, 0x01, 0xFF];
669 let es1: [u8; 4] = [0x52, 0x02, 0xAA, 0xBB];
670 let es2: [u8; 2] = [0x0A, 0x00];
671 let pmt = PmtSection {
672 program_number: 0xABCD,
673 version_number: 7,
674 current_next_indicator: true,
675 pcr_pid: 0x1F0,
676 program_info: DescriptorLoop::new(&prog_info),
677 streams: vec![
678 PmtStream {
679 stream_type: StreamType::Mpeg2Video,
680 elementary_pid: 0x100,
681 es_info: DescriptorLoop::new(&es1),
682 },
683 PmtStream {
684 stream_type: StreamType::Mpeg1Audio,
685 elementary_pid: 0x101,
686 es_info: DescriptorLoop::new(&es2),
687 },
688 PmtStream {
689 stream_type: StreamType::H264,
690 elementary_pid: 0x102,
691 es_info: DescriptorLoop::new(&[]),
692 },
693 ],
694 };
695 let mut buf = vec![0u8; pmt.serialized_len()];
696 pmt.serialize_into(&mut buf).unwrap();
697 let re = PmtSection::parse(&buf).unwrap();
698 assert_eq!(pmt, re);
699 }
700
701 #[test]
702 fn zero_elementary_streams_is_valid() {
703 let bytes = build_pmt(99, 0, 0x0100, &[], &[]);
704 let pmt = PmtSection::parse(&bytes).unwrap();
705 assert_eq!(pmt.streams.len(), 0);
706 }
707
708 #[test]
709 fn parse_preserves_raw_program_info_bytes() {
710 let pi = vec![0x09, 0x04, 0x01, 0x02, 0x03, 0x04];
711 let bytes = build_pmt(1, 0, 0x100, &pi, &[]);
712 let pmt = PmtSection::parse(&bytes).unwrap();
713 assert_eq!(pmt.program_info.raw(), &pi[..]);
714 }
715
716 #[test]
717 fn parse_rejects_zero_section_length() {
718 let mut buf = vec![0u8; 64];
719 buf[0] = TABLE_ID;
720 buf[1] = 0xF0;
721 buf[2] = 0x00;
722 for b in &mut buf[3..] {
723 *b = 0xFF;
724 }
725 assert!(matches!(
726 PmtSection::parse(&buf).unwrap_err(),
727 Error::SectionLengthOverflow { .. }
728 ));
729 }
730
731 #[test]
732 fn stream_type_full_range_round_trip() {
733 for byte in 0u8..=0xFF {
734 let st = StreamType::from_u8(byte);
735 assert_eq!(
736 st.to_u8(),
737 byte,
738 "StreamType round-trip failed for {byte:#04x}"
739 );
740 }
741 }
742
743 #[test]
744 fn stream_type_named_values() {
745 assert_eq!(StreamType::Mpeg2Video.to_u8(), 0x02);
746 assert_eq!(StreamType::H264.to_u8(), 0x1B);
747 assert_eq!(StreamType::Hevc.to_u8(), 0x24);
748 assert_eq!(StreamType::Vvc.to_u8(), 0x33);
749 assert_eq!(StreamType::MediaOrchestration.to_u8(), 0x30);
750 assert_eq!(StreamType::Mvcd.to_u8(), 0x26);
751 assert_eq!(StreamType::Temi.to_u8(), 0x27);
752 assert_eq!(StreamType::Ac3.to_u8(), 0x81);
753 assert_eq!(StreamType::Scte35.to_u8(), 0x86);
754 assert_eq!(StreamType::EAc3.to_u8(), 0x87);
755 assert_eq!(StreamType::AacAdts.to_u8(), 0x0F);
756 assert_eq!(StreamType::IpmpHigh.to_u8(), 0x7F);
757 }
758
759 #[test]
760 fn stream_type_names() {
761 assert_eq!(StreamType::Mpeg2Video.name(), "MPEG-2 Video");
762 assert_eq!(StreamType::H264.name(), "H.264/AVC");
763 assert_eq!(StreamType::Hevc.name(), "HEVC/H.265");
764 assert_eq!(StreamType::Vvc.name(), "VVC/H.266");
765 assert_eq!(StreamType::MediaOrchestration.name(), "Media Orchestration");
766 assert_eq!(StreamType::Mvcd.name(), "MVCD (H.264 Annex I)");
767 assert_eq!(StreamType::Temi.name(), "TEMI");
768 assert_eq!(StreamType::DsmCc.name(), "DSM-CC");
769 assert_eq!(StreamType::Ac3.name(), "AC-3");
770 assert_eq!(StreamType::Scte35.name(), "SCTE-35");
771 }
772}