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