1use crate::descriptor::{iter_descriptors, DescriptorIter};
29use crate::TsError;
30
31pub const PAT_TABLE_ID: u8 = 0x00;
33pub const CAT_TABLE_ID: u8 = 0x01;
35pub const PMT_TABLE_ID: u8 = 0x02;
37pub const TSDT_TABLE_ID: u8 = 0x03;
40pub const SDT_ACTUAL_TABLE_ID: u8 = 0x42;
44pub const SDT_OTHER_TABLE_ID: u8 = 0x46;
47pub const EIT_ACTUAL_PF_TABLE_ID: u8 = 0x4E;
50pub const EIT_OTHER_PF_TABLE_ID: u8 = 0x4F;
53pub const EIT_ACTUAL_SCHEDULE_FIRST: u8 = 0x50;
56pub const EIT_ACTUAL_SCHEDULE_LAST: u8 = 0x5F;
58pub const EIT_OTHER_SCHEDULE_FIRST: u8 = 0x60;
61pub const EIT_OTHER_SCHEDULE_LAST: u8 = 0x6F;
63
64pub const PAT_PID: u16 = 0x0000;
66pub const CAT_PID: u16 = 0x0001;
68pub const TSDT_PID: u16 = 0x0002;
71pub const SDT_PID: u16 = 0x0011;
74pub const EIT_PID: u16 = 0x0012;
77
78const SECTION_HEADER_LEN: usize = 8;
81const SECTION_CRC_LEN: usize = 4;
83pub const MAX_PSI_SECTION_LEN: usize = 3 + 0x3FD;
88
89#[derive(Debug, Default, Clone)]
91pub struct ProgramAssociationTable {
92 pub transport_stream_id: u16,
94 pub version_number: u8,
96 pub current_next_indicator: bool,
98 pub section_number: u8,
100 pub last_section_number: u8,
102 pub programs: Vec<(u16, u16)>,
107}
108
109impl ProgramAssociationTable {
110 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
115 let (hdr, body) = parse_section_header(section, PAT_TABLE_ID)?;
116 let mut programs = Vec::new();
117 let mut i = 0;
118 while i + 4 <= body.len() {
119 let program_number = u16::from_be_bytes([body[i], body[i + 1]]);
120 let pid = ((((body[i + 2] & 0b0001_1111) as u16) << 8) | (body[i + 3] as u16)) & 0x1FFF;
121 programs.push((program_number, pid));
122 i += 4;
123 }
124 Ok(Self {
125 transport_stream_id: hdr.table_id_extension,
126 version_number: hdr.version_number,
127 current_next_indicator: hdr.current_next_indicator,
128 section_number: hdr.section_number,
129 last_section_number: hdr.last_section_number,
130 programs,
131 })
132 }
133}
134
135#[derive(Debug, Clone)]
137pub struct PmtStream {
138 pub stream_type: u8,
140 pub elementary_pid: u16,
142 pub descriptors: Vec<u8>,
145}
146
147impl PmtStream {
148 pub fn iter_descriptors(&self) -> DescriptorIter<'_> {
151 iter_descriptors(&self.descriptors)
152 }
153}
154
155#[derive(Debug, Default, Clone)]
157pub struct ProgramMapTable {
158 pub program_number: u16,
160 pub version_number: u8,
162 pub current_next_indicator: bool,
164 pub pcr_pid: u16,
166 pub program_info: Vec<u8>,
169 pub streams: Vec<PmtStream>,
171}
172
173impl ProgramMapTable {
174 pub fn iter_program_descriptors(&self) -> DescriptorIter<'_> {
178 iter_descriptors(&self.program_info)
179 }
180
181 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
184 let (hdr, body) = parse_section_header(section, PMT_TABLE_ID)?;
185 if body.len() < 4 {
186 return Err(TsError::Truncated {
187 what: "PMT body",
188 have: body.len(),
189 need: 4,
190 });
191 }
192 let pcr_pid = u16::from_be_bytes([body[0] & 0b0001_1111, body[1]]);
193 let program_info_length = (u16::from_be_bytes([body[2] & 0b0000_1111, body[3]])) as usize;
194 let after_pcr: usize = 4;
195 let pi_end =
196 after_pcr
197 .checked_add(program_info_length)
198 .ok_or(TsError::SectionLengthOverrun {
199 claimed: program_info_length,
200 have: body.len() - after_pcr,
201 })?;
202 if pi_end > body.len() {
203 return Err(TsError::SectionLengthOverrun {
204 claimed: program_info_length,
205 have: body.len() - after_pcr,
206 });
207 }
208 let program_info = body[after_pcr..pi_end].to_vec();
209
210 let mut streams = Vec::new();
211 let mut i = pi_end;
212 while i + 5 <= body.len() {
213 let stream_type = body[i];
214 let elementary_pid = u16::from_be_bytes([body[i + 1] & 0b0001_1111, body[i + 2]]);
215 let es_info_length =
216 (u16::from_be_bytes([body[i + 3] & 0b0000_1111, body[i + 4]])) as usize;
217 let descr_start = i + 5;
218 let descr_end =
219 descr_start
220 .checked_add(es_info_length)
221 .ok_or(TsError::SectionLengthOverrun {
222 claimed: es_info_length,
223 have: body.len() - descr_start,
224 })?;
225 if descr_end > body.len() {
226 return Err(TsError::SectionLengthOverrun {
227 claimed: es_info_length,
228 have: body.len() - descr_start,
229 });
230 }
231 let descriptors = body[descr_start..descr_end].to_vec();
232 streams.push(PmtStream {
233 stream_type,
234 elementary_pid,
235 descriptors,
236 });
237 i = descr_end;
238 }
239 Ok(Self {
240 program_number: hdr.table_id_extension,
241 version_number: hdr.version_number,
242 current_next_indicator: hdr.current_next_indicator,
243 pcr_pid,
244 program_info,
245 streams,
246 })
247 }
248}
249
250#[derive(Debug, Default, Clone)]
259pub struct ConditionalAccessTable {
260 pub version_number: u8,
262 pub current_next_indicator: bool,
264 pub section_number: u8,
266 pub last_section_number: u8,
268 pub descriptors: Vec<u8>,
271}
272
273impl ConditionalAccessTable {
274 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
278 let (hdr, body) = parse_section_header(section, CAT_TABLE_ID)?;
279 Ok(Self {
280 version_number: hdr.version_number,
281 current_next_indicator: hdr.current_next_indicator,
282 section_number: hdr.section_number,
283 last_section_number: hdr.last_section_number,
284 descriptors: body.to_vec(),
285 })
286 }
287
288 pub fn iter_descriptors(&self) -> DescriptorIter<'_> {
290 iter_descriptors(&self.descriptors)
291 }
292}
293
294#[derive(Debug, Default, Clone)]
309pub struct TransportStreamDescriptionTable {
310 pub version_number: u8,
312 pub current_next_indicator: bool,
314 pub section_number: u8,
316 pub last_section_number: u8,
318 pub descriptors: Vec<u8>,
321}
322
323impl TransportStreamDescriptionTable {
324 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
328 let (hdr, body) = parse_section_header(section, TSDT_TABLE_ID)?;
329 Ok(Self {
330 version_number: hdr.version_number,
331 current_next_indicator: hdr.current_next_indicator,
332 section_number: hdr.section_number,
333 last_section_number: hdr.last_section_number,
334 descriptors: body.to_vec(),
335 })
336 }
337
338 pub fn iter_descriptors(&self) -> DescriptorIter<'_> {
340 iter_descriptors(&self.descriptors)
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum RunningStatus {
350 Undefined,
352 NotRunning,
354 StartsSoon,
356 Pausing,
358 Running,
360 OffAir,
362 Reserved(u8),
364}
365
366impl RunningStatus {
367 pub fn from_bits(value: u8) -> Self {
369 match value & 0b0000_0111 {
370 0 => RunningStatus::Undefined,
371 1 => RunningStatus::NotRunning,
372 2 => RunningStatus::StartsSoon,
373 3 => RunningStatus::Pausing,
374 4 => RunningStatus::Running,
375 5 => RunningStatus::OffAir,
376 other => RunningStatus::Reserved(other),
377 }
378 }
379}
380
381#[derive(Debug, Clone)]
384pub struct SdtService {
385 pub service_id: u16,
388 pub eit_schedule_flag: bool,
390 pub eit_present_following_flag: bool,
392 pub running_status: RunningStatus,
394 pub free_ca_mode: bool,
397 pub descriptors: Vec<u8>,
401}
402
403impl SdtService {
404 pub fn iter_descriptors(&self) -> DescriptorIter<'_> {
406 iter_descriptors(&self.descriptors)
407 }
408}
409
410#[derive(Debug, Default, Clone)]
429pub struct ServiceDescriptionTable {
430 pub transport_stream_id: u16,
432 pub other_transport_stream: bool,
435 pub version_number: u8,
437 pub current_next_indicator: bool,
439 pub section_number: u8,
441 pub last_section_number: u8,
443 pub original_network_id: u16,
445 pub services: Vec<SdtService>,
447}
448
449impl ServiceDescriptionTable {
450 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
455 let table_id = section.first().copied().unwrap_or(0);
456 let other_transport_stream = match table_id {
457 SDT_ACTUAL_TABLE_ID => false,
458 SDT_OTHER_TABLE_ID => true,
459 _ => {
460 return Err(TsError::Unsupported(
461 "PSI table_id does not match expected value",
462 ))
463 }
464 };
465 let (hdr, body) = parse_section_header(section, table_id)?;
466 if body.len() < 3 {
469 return Err(TsError::Truncated {
470 what: "SDT body",
471 have: body.len(),
472 need: 3,
473 });
474 }
475 let original_network_id = u16::from_be_bytes([body[0], body[1]]);
476 let mut services = Vec::new();
478 let mut i = 3;
479 while i + 5 <= body.len() {
483 let service_id = u16::from_be_bytes([body[i], body[i + 1]]);
484 let b = body[i + 2];
485 let eit_schedule_flag = (b & 0b0000_0010) != 0;
486 let eit_present_following_flag = (b & 0b0000_0001) != 0;
487 let b3 = body[i + 3];
488 let running_status = RunningStatus::from_bits(b3 >> 5);
489 let free_ca_mode = (b3 & 0b0001_0000) != 0;
490 let descriptors_length = (u16::from_be_bytes([b3 & 0b0000_1111, body[i + 4]])) as usize;
491 let descr_start = i + 5;
492 let descr_end = descr_start.checked_add(descriptors_length).ok_or(
493 TsError::SectionLengthOverrun {
494 claimed: descriptors_length,
495 have: body.len() - descr_start,
496 },
497 )?;
498 if descr_end > body.len() {
499 return Err(TsError::SectionLengthOverrun {
500 claimed: descriptors_length,
501 have: body.len() - descr_start,
502 });
503 }
504 services.push(SdtService {
505 service_id,
506 eit_schedule_flag,
507 eit_present_following_flag,
508 running_status,
509 free_ca_mode,
510 descriptors: body[descr_start..descr_end].to_vec(),
511 });
512 i = descr_end;
513 }
514 Ok(Self {
515 transport_stream_id: hdr.table_id_extension,
516 other_transport_stream,
517 version_number: hdr.version_number,
518 current_next_indicator: hdr.current_next_indicator,
519 section_number: hdr.section_number,
520 last_section_number: hdr.last_section_number,
521 original_network_id,
522 services,
523 })
524 }
525}
526
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
540pub struct EitDateTime {
541 pub year: u16,
544 pub month: u8,
546 pub day: u8,
548 pub hour: u8,
550 pub minute: u8,
552 pub second: u8,
554 pub mjd: u16,
557}
558
559#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
563pub struct EitDuration {
564 pub hours: u8,
566 pub minutes: u8,
568 pub seconds: u8,
570}
571
572impl EitDuration {
573 pub fn as_seconds(&self) -> u32 {
575 (self.hours as u32) * 3600 + (self.minutes as u32) * 60 + (self.seconds as u32)
576 }
577}
578
579fn bcd_byte(b: u8) -> u8 {
581 (b >> 4) * 10 + (b & 0x0F)
582}
583
584fn decode_eit_start_time(bytes: [u8; 5]) -> Option<EitDateTime> {
599 if bytes == [0xFF, 0xFF, 0xFF, 0xFF, 0xFF] {
600 return None;
601 }
602 let mjd = u16::from_be_bytes([bytes[0], bytes[1]]);
603 let mjd_i = mjd as i64;
607 let yp = ((mjd_i - 15078) * 100 - 20) / 36525; let yp_days = (yp * 36525) / 100; let mp = ((mjd_i - 14956 - yp_days) * 10000 - 1) / 306001;
611 let mp_days = (mp * 306001) / 10000; let d = mjd_i - 14956 - yp_days - mp_days;
613 let k: i64 = if mp == 14 || mp == 15 { 1 } else { 0 };
614 let y = yp + k;
615 let m = mp - 1 - k * 12;
616 let year = (1900 + y) as u16;
617 let month = m as u8;
618 let day = d as u8;
619 let hour = bcd_byte(bytes[2]);
620 let minute = bcd_byte(bytes[3]);
621 let second = bcd_byte(bytes[4]);
622 Some(EitDateTime {
623 year,
624 month,
625 day,
626 hour,
627 minute,
628 second,
629 mjd,
630 })
631}
632
633#[derive(Debug, Clone)]
636pub struct EitEvent {
637 pub event_id: u16,
639 pub start_time: Option<EitDateTime>,
642 pub duration: EitDuration,
644 pub running_status: RunningStatus,
646 pub free_ca_mode: bool,
649 pub descriptors: Vec<u8>,
653}
654
655impl EitEvent {
656 pub fn iter_descriptors(&self) -> DescriptorIter<'_> {
658 iter_descriptors(&self.descriptors)
659 }
660}
661
662#[derive(Debug, Default, Clone)]
686pub struct EventInformationTable {
687 pub service_id: u16,
689 pub other_transport_stream: bool,
693 pub schedule: bool,
696 pub table_id: u8,
698 pub version_number: u8,
700 pub current_next_indicator: bool,
702 pub section_number: u8,
704 pub last_section_number: u8,
706 pub transport_stream_id: u16,
708 pub original_network_id: u16,
710 pub segment_last_section_number: u8,
714 pub last_table_id: u8,
717 pub events: Vec<EitEvent>,
719}
720
721impl EventInformationTable {
722 pub fn is_eit_table_id(table_id: u8) -> bool {
725 matches!(table_id, EIT_ACTUAL_PF_TABLE_ID | EIT_OTHER_PF_TABLE_ID)
726 || (EIT_ACTUAL_SCHEDULE_FIRST..=EIT_ACTUAL_SCHEDULE_LAST).contains(&table_id)
727 || (EIT_OTHER_SCHEDULE_FIRST..=EIT_OTHER_SCHEDULE_LAST).contains(&table_id)
728 }
729
730 pub fn parse(section: &[u8]) -> Result<Self, TsError> {
734 let table_id = section.first().copied().unwrap_or(0);
735 if !Self::is_eit_table_id(table_id) {
736 return Err(TsError::Unsupported(
737 "PSI table_id does not match expected value",
738 ));
739 }
740 let other_transport_stream = table_id == EIT_OTHER_PF_TABLE_ID
741 || (EIT_OTHER_SCHEDULE_FIRST..=EIT_OTHER_SCHEDULE_LAST).contains(&table_id);
742 let schedule = (EIT_ACTUAL_SCHEDULE_FIRST..=EIT_OTHER_SCHEDULE_LAST).contains(&table_id);
743 let (hdr, body) = parse_section_header(section, table_id)?;
744 if body.len() < 6 {
748 return Err(TsError::Truncated {
749 what: "EIT body",
750 have: body.len(),
751 need: 6,
752 });
753 }
754 let transport_stream_id = u16::from_be_bytes([body[0], body[1]]);
755 let original_network_id = u16::from_be_bytes([body[2], body[3]]);
756 let segment_last_section_number = body[4];
757 let last_table_id = body[5];
758 let mut events = Vec::new();
759 let mut i = 6;
760 while i + 12 <= body.len() {
765 let event_id = u16::from_be_bytes([body[i], body[i + 1]]);
766 let start_time = decode_eit_start_time([
767 body[i + 2],
768 body[i + 3],
769 body[i + 4],
770 body[i + 5],
771 body[i + 6],
772 ]);
773 let duration = EitDuration {
774 hours: bcd_byte(body[i + 7]),
775 minutes: bcd_byte(body[i + 8]),
776 seconds: bcd_byte(body[i + 9]),
777 };
778 let b10 = body[i + 10];
779 let running_status = RunningStatus::from_bits(b10 >> 5);
780 let free_ca_mode = (b10 & 0b0001_0000) != 0;
781 let descriptors_length =
782 (u16::from_be_bytes([b10 & 0b0000_1111, body[i + 11]])) as usize;
783 let descr_start = i + 12;
784 let descr_end = descr_start.checked_add(descriptors_length).ok_or(
785 TsError::SectionLengthOverrun {
786 claimed: descriptors_length,
787 have: body.len() - descr_start,
788 },
789 )?;
790 if descr_end > body.len() {
791 return Err(TsError::SectionLengthOverrun {
792 claimed: descriptors_length,
793 have: body.len() - descr_start,
794 });
795 }
796 events.push(EitEvent {
797 event_id,
798 start_time,
799 duration,
800 running_status,
801 free_ca_mode,
802 descriptors: body[descr_start..descr_end].to_vec(),
803 });
804 i = descr_end;
805 }
806 Ok(Self {
807 service_id: hdr.table_id_extension,
808 other_transport_stream,
809 schedule,
810 table_id,
811 version_number: hdr.version_number,
812 current_next_indicator: hdr.current_next_indicator,
813 section_number: hdr.section_number,
814 last_section_number: hdr.last_section_number,
815 transport_stream_id,
816 original_network_id,
817 segment_last_section_number,
818 last_table_id,
819 events,
820 })
821 }
822}
823
824#[derive(Debug, Default)]
860pub struct PsiSectionAssembler {
861 in_flight: Vec<u8>,
864 target_len: Option<usize>,
867 last_cc: Option<u8>,
871}
872
873impl PsiSectionAssembler {
874 pub fn new() -> Self {
876 Self::default()
877 }
878
879 pub fn reset(&mut self) {
883 self.in_flight.clear();
884 self.target_len = None;
885 self.last_cc = None;
886 }
887
888 pub fn feed(
908 &mut self,
909 payload: &[u8],
910 pusi: bool,
911 continuity_counter: u8,
912 ) -> Result<Vec<Vec<u8>>, TsError> {
913 let cc = continuity_counter & 0x0F;
918 if let Some(prev) = self.last_cc {
919 let expected = (prev + 1) & 0x0F;
920 if cc != expected {
921 self.in_flight.clear();
923 self.target_len = None;
924 }
925 }
926 self.last_cc = Some(cc);
927
928 let mut out = Vec::new();
929 let mut rest: &[u8] = payload;
930
931 if pusi {
932 if rest.is_empty() {
934 return Ok(out);
935 }
936 let ptr = rest[0] as usize;
937 rest = &rest[1..];
938 if ptr > rest.len() {
939 self.in_flight.clear();
942 self.target_len = None;
943 return Ok(out);
944 }
945 let (tail_of_prev, after_ptr) = rest.split_at(ptr);
946 if !self.in_flight.is_empty() || self.target_len.is_some() {
951 if let Some(section) = self.extend_in_flight(tail_of_prev)? {
952 out.push(section);
953 }
954 self.in_flight.clear();
960 self.target_len = None;
961 }
962 rest = after_ptr;
963 } else if self.target_len.is_none() {
964 return Ok(out);
967 } else {
968 if let Some(section) = self.extend_in_flight(rest)? {
971 out.push(section);
972 }
973 return Ok(out);
974 }
975
976 while !rest.is_empty() {
980 if rest[0] == 0xFF {
981 break;
983 }
984 if rest.len() < 3 {
985 self.in_flight.extend_from_slice(rest);
988 self.target_len = None;
989 break;
990 }
991 let section_length =
992 ((((rest[1] & 0b0000_1111) as usize) << 8) | (rest[2] as usize)) & 0x0FFF;
993 let total = 3 + section_length;
994 if total > MAX_PSI_SECTION_LEN {
995 self.in_flight.clear();
996 self.target_len = None;
997 return Err(TsError::SectionLengthOverrun {
998 claimed: section_length,
999 have: rest.len() - 3,
1000 });
1001 }
1002 if rest.len() >= total {
1003 out.push(rest[..total].to_vec());
1006 rest = &rest[total..];
1007 } else {
1008 self.in_flight.clear();
1010 self.in_flight.extend_from_slice(rest);
1011 self.target_len = Some(total);
1012 break;
1013 }
1014 }
1015
1016 Ok(out)
1017 }
1018
1019 fn extend_in_flight(&mut self, bytes: &[u8]) -> Result<Option<Vec<u8>>, TsError> {
1023 if bytes.is_empty() {
1024 return Ok(None);
1025 }
1026 if self.target_len.is_none() {
1029 let want = 3usize.saturating_sub(self.in_flight.len());
1030 let take = want.min(bytes.len());
1031 self.in_flight.extend_from_slice(&bytes[..take]);
1032 if self.in_flight.len() < 3 {
1033 return Ok(None);
1034 }
1035 let section_length = ((((self.in_flight[1] & 0b0000_1111) as usize) << 8)
1036 | (self.in_flight[2] as usize))
1037 & 0x0FFF;
1038 let total = 3 + section_length;
1039 if total > MAX_PSI_SECTION_LEN {
1040 self.in_flight.clear();
1041 self.target_len = None;
1042 return Err(TsError::SectionLengthOverrun {
1043 claimed: section_length,
1044 have: bytes.len() - take,
1045 });
1046 }
1047 self.target_len = Some(total);
1048 return self.extend_in_flight(&bytes[take..]);
1050 }
1051 let target = self.target_len.expect("checked above");
1052 let want = target.saturating_sub(self.in_flight.len());
1053 let take = want.min(bytes.len());
1054 self.in_flight.extend_from_slice(&bytes[..take]);
1055 if self.in_flight.len() < target {
1056 return Ok(None);
1057 }
1058 let done = std::mem::take(&mut self.in_flight);
1060 self.target_len = None;
1061 Ok(Some(done))
1062 }
1063}
1064
1065struct SectionHeader {
1067 table_id_extension: u16,
1068 version_number: u8,
1069 current_next_indicator: bool,
1070 section_number: u8,
1071 last_section_number: u8,
1072}
1073
1074fn parse_section_header(
1075 section: &[u8],
1076 expected_table_id: u8,
1077) -> Result<(SectionHeader, &[u8]), TsError> {
1078 if section.len() < SECTION_HEADER_LEN + SECTION_CRC_LEN {
1079 return Err(TsError::Truncated {
1080 what: "PSI section header",
1081 have: section.len(),
1082 need: SECTION_HEADER_LEN + SECTION_CRC_LEN,
1083 });
1084 }
1085 let table_id = section[0];
1086 if table_id != expected_table_id {
1087 return Err(TsError::Unsupported(
1088 "PSI table_id does not match expected value",
1089 ));
1090 }
1091 let b1 = section[1];
1092 let b2 = section[2];
1093 let section_length = ((((b1 & 0b0000_1111) as usize) << 8) | (b2 as usize)) & 0x0FFF;
1095
1096 let total = 3 + section_length;
1099 if total > section.len() {
1100 return Err(TsError::SectionLengthOverrun {
1101 claimed: section_length,
1102 have: section.len() - 3,
1103 });
1104 }
1105 let section = §ion[..total];
1106
1107 let crc_pos = total - SECTION_CRC_LEN;
1109 let computed = mpeg2_crc32(§ion[..crc_pos]);
1110 let header_crc = u32::from_be_bytes([
1111 section[crc_pos],
1112 section[crc_pos + 1],
1113 section[crc_pos + 2],
1114 section[crc_pos + 3],
1115 ]);
1116 if computed != header_crc {
1117 return Err(TsError::PsiCrcMismatch {
1118 header: header_crc,
1119 computed,
1120 });
1121 }
1122
1123 let table_id_extension = u16::from_be_bytes([section[3], section[4]]);
1124 let b5 = section[5];
1125 let version_number = (b5 >> 1) & 0b0001_1111;
1126 let current_next_indicator = (b5 & 0b0000_0001) != 0;
1127 let section_number = section[6];
1128 let last_section_number = section[7];
1129
1130 let body = §ion[SECTION_HEADER_LEN..crc_pos];
1131 Ok((
1132 SectionHeader {
1133 table_id_extension,
1134 version_number,
1135 current_next_indicator,
1136 section_number,
1137 last_section_number,
1138 },
1139 body,
1140 ))
1141}
1142
1143pub fn mpeg2_crc32(bytes: &[u8]) -> u32 {
1146 let mut crc: u32 = 0xFFFF_FFFF;
1147 for &b in bytes {
1148 crc ^= (b as u32) << 24;
1149 for _ in 0..8 {
1150 if (crc & 0x8000_0000) != 0 {
1151 crc = (crc << 1) ^ 0x04C1_1DB7;
1152 } else {
1153 crc <<= 1;
1154 }
1155 }
1156 }
1157 crc
1158}
1159
1160pub fn iter_sections(ts_payload: &[u8]) -> SectionIter<'_> {
1170 if ts_payload.is_empty() {
1171 return SectionIter { rest: &[][..] };
1172 }
1173 let ptr = ts_payload[0] as usize;
1174 let start = 1 + ptr;
1175 if start > ts_payload.len() {
1176 return SectionIter { rest: &[][..] };
1177 }
1178 SectionIter {
1179 rest: &ts_payload[start..],
1180 }
1181}
1182
1183#[derive(Debug)]
1185pub struct SectionIter<'a> {
1186 rest: &'a [u8],
1187}
1188
1189impl<'a> Iterator for SectionIter<'a> {
1190 type Item = &'a [u8];
1191
1192 fn next(&mut self) -> Option<Self::Item> {
1193 if self.rest.len() < 3 {
1195 return None;
1196 }
1197 if self.rest[0] == 0xFF {
1200 return None;
1201 }
1202 let section_length =
1203 ((((self.rest[1] & 0b0000_1111) as usize) << 8) | (self.rest[2] as usize)) & 0x0FFF;
1204 let total = 3 + section_length;
1205 if total > self.rest.len() {
1206 return None;
1207 }
1208 let (head, tail) = self.rest.split_at(total);
1209 self.rest = tail;
1210 Some(head)
1211 }
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216 use super::*;
1217 use crate::descriptor::DescriptorBody;
1218
1219 fn build_pat_section(tsid: u16, version: u8, programs: &[(u16, u16)]) -> Vec<u8> {
1222 let body_len = programs.len() * 4;
1225 let section_length = 5 + body_len + 4;
1226 let mut s = Vec::with_capacity(3 + section_length);
1227 s.push(PAT_TABLE_ID);
1228 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
1231 s.push(len_hi);
1232 s.push((section_length & 0xFF) as u8);
1233 s.extend_from_slice(&tsid.to_be_bytes());
1234 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
1236 s.push(0); s.push(0); for (prog, pid) in programs {
1239 s.extend_from_slice(&prog.to_be_bytes());
1240 s.push(0b1110_0000 | ((pid >> 8) & 0x1F) as u8);
1242 s.push((pid & 0xFF) as u8);
1243 }
1244 let crc = mpeg2_crc32(&s);
1245 s.extend_from_slice(&crc.to_be_bytes());
1246 s
1247 }
1248
1249 fn build_pmt_section(
1250 program_number: u16,
1251 version: u8,
1252 pcr_pid: u16,
1253 program_info: &[u8],
1254 streams: &[(u8, u16, &[u8])],
1255 ) -> Vec<u8> {
1256 let body_len: usize =
1258 4 + program_info.len() + streams.iter().map(|(_, _, d)| 5 + d.len()).sum::<usize>();
1259 let section_length = 5 + body_len + 4;
1260 let mut s = Vec::with_capacity(3 + section_length);
1261 s.push(PMT_TABLE_ID);
1262 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
1263 s.push(len_hi);
1264 s.push((section_length & 0xFF) as u8);
1265 s.extend_from_slice(&program_number.to_be_bytes());
1266 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
1267 s.push(0);
1268 s.push(0);
1269 s.push(0b1110_0000 | ((pcr_pid >> 8) & 0x1F) as u8);
1271 s.push((pcr_pid & 0xFF) as u8);
1272 let pil = program_info.len() as u16;
1274 s.push(0b1111_0000 | ((pil >> 8) & 0x0F) as u8);
1275 s.push((pil & 0xFF) as u8);
1276 s.extend_from_slice(program_info);
1277 for (stype, epid, descr) in streams {
1278 s.push(*stype);
1279 s.push(0b1110_0000 | ((*epid >> 8) & 0x1F) as u8);
1280 s.push((*epid & 0xFF) as u8);
1281 let el = descr.len() as u16;
1282 s.push(0b1111_0000 | ((el >> 8) & 0x0F) as u8);
1283 s.push((el & 0xFF) as u8);
1284 s.extend_from_slice(descr);
1285 }
1286 let crc = mpeg2_crc32(&s);
1287 s.extend_from_slice(&crc.to_be_bytes());
1288 s
1289 }
1290
1291 #[test]
1292 fn mpeg2_crc32_known_vector() {
1293 let crc = mpeg2_crc32(b"123456789");
1298 assert_eq!(crc, 0x0376_E6E7);
1301 }
1302
1303 #[test]
1304 fn pat_one_program_round_trip() {
1305 let section = build_pat_section(1, 3, &[(1, 0x100)]);
1306 let pat = ProgramAssociationTable::parse(§ion).unwrap();
1307 assert_eq!(pat.transport_stream_id, 1);
1308 assert_eq!(pat.version_number, 3);
1309 assert!(pat.current_next_indicator);
1310 assert_eq!(pat.programs, vec![(1, 0x100)]);
1311 }
1312
1313 #[test]
1314 fn pmt_avc_ac3_pgs_round_trip() {
1315 let avc_descr: &[u8] = &[0x52, 0x01, 0x00]; let ac3_descr: &[u8] = &[0x6A, 0x01, 0x80];
1320 let pgs_descr: &[u8] = &[];
1321 let section = build_pmt_section(
1322 1,
1323 5,
1324 0x100,
1325 &[],
1326 &[
1327 (0x1B, 0x1011, avc_descr),
1328 (0x81, 0x1100, ac3_descr),
1329 (0x90, 0x1200, pgs_descr),
1330 ],
1331 );
1332 let pmt = ProgramMapTable::parse(§ion).unwrap();
1333 assert_eq!(pmt.program_number, 1);
1334 assert_eq!(pmt.version_number, 5);
1335 assert!(pmt.current_next_indicator);
1336 assert_eq!(pmt.pcr_pid, 0x100);
1337 assert!(pmt.program_info.is_empty());
1338 assert_eq!(pmt.streams.len(), 3);
1339 assert_eq!(pmt.streams[0].stream_type, 0x1B);
1340 assert_eq!(pmt.streams[0].elementary_pid, 0x1011);
1341 assert_eq!(pmt.streams[0].descriptors, avc_descr);
1342 assert_eq!(pmt.streams[1].stream_type, 0x81);
1343 assert_eq!(pmt.streams[1].elementary_pid, 0x1100);
1344 assert_eq!(pmt.streams[1].descriptors, ac3_descr);
1345 assert_eq!(pmt.streams[2].stream_type, 0x90);
1346 assert_eq!(pmt.streams[2].elementary_pid, 0x1200);
1347 assert!(pmt.streams[2].descriptors.is_empty());
1348 }
1349
1350 #[test]
1351 fn psi_crc_corruption_is_rejected() {
1352 let mut section = build_pat_section(7, 0, &[(1, 0x100)]);
1353 section[3] ^= 0x01;
1355 let err = ProgramAssociationTable::parse(§ion).unwrap_err();
1356 match err {
1357 TsError::PsiCrcMismatch { .. } => {}
1358 other => panic!("expected PsiCrcMismatch, got {other:?}"),
1359 }
1360 }
1361
1362 #[test]
1363 fn iter_sections_skips_pointer_field_and_stuffing() {
1364 let section = build_pat_section(1, 0, &[(1, 0x100)]);
1365 let mut payload = Vec::new();
1368 payload.push(0u8); payload.extend_from_slice(§ion);
1370 payload.extend(std::iter::repeat(0xFF).take(10));
1371 let mut it = iter_sections(&payload);
1372 let s = it.next().expect("section");
1373 assert_eq!(s, section);
1374 assert!(it.next().is_none());
1375 }
1376
1377 #[test]
1378 fn iter_sections_with_nonzero_pointer_field() {
1379 let section = build_pat_section(1, 0, &[(1, 0x100)]);
1380 let mut payload = Vec::new();
1381 payload.push(3u8); payload.extend_from_slice(&[0xAA, 0xBB, 0xCC]); payload.extend_from_slice(§ion);
1384 let s = iter_sections(&payload).next().expect("section");
1385 assert_eq!(s, section);
1386 }
1387
1388 #[test]
1389 fn pmt_per_stream_descriptors_decode_iso639() {
1390 let es_descr: &[u8] = &[0x0A, 0x08, b'e', b'n', b'g', 0x00, b'j', b'p', b'n', 0x02];
1393 let section = build_pmt_section(1, 0, 0x100, &[], &[(0x81, 0x1100, es_descr)]);
1394 let pmt = ProgramMapTable::parse(§ion).unwrap();
1395 assert_eq!(pmt.streams.len(), 1);
1396 let descriptors: Vec<_> = pmt.streams[0]
1397 .iter_descriptors()
1398 .collect::<Result<_, _>>()
1399 .unwrap();
1400 assert_eq!(descriptors.len(), 1);
1401 assert_eq!(descriptors[0].tag, 0x0A);
1402 match &descriptors[0].body {
1403 crate::descriptor::DescriptorBody::Iso639Language(langs) => {
1404 assert_eq!(langs.len(), 2);
1405 assert_eq!(&langs[0].language, b"eng");
1406 assert_eq!(&langs[1].language, b"jpn");
1407 assert_eq!(langs[1].audio_type, 2);
1408 }
1409 other => panic!("expected Iso639Language, got {other:?}"),
1410 }
1411 }
1412
1413 #[test]
1414 fn pmt_program_info_descriptors_decode_registration() {
1415 let program_info: &[u8] = &[0x05, 0x04, b'H', b'D', b'M', b'V'];
1417 let section = build_pmt_section(1, 0, 0x100, program_info, &[(0x1B, 0x1011, &[])]);
1418 let pmt = ProgramMapTable::parse(§ion).unwrap();
1419 let descriptors: Vec<_> = pmt
1420 .iter_program_descriptors()
1421 .collect::<Result<_, _>>()
1422 .unwrap();
1423 assert_eq!(descriptors.len(), 1);
1424 match &descriptors[0].body {
1425 crate::descriptor::DescriptorBody::Registration {
1426 format_identifier, ..
1427 } => {
1428 assert_eq!(format_identifier, b"HDMV");
1429 }
1430 other => panic!("expected Registration, got {other:?}"),
1431 }
1432 }
1433
1434 #[test]
1435 fn pat_network_pid_program_zero() {
1436 let section = build_pat_section(2, 0, &[(0, 0x10), (1, 0x100)]);
1437 let pat = ProgramAssociationTable::parse(§ion).unwrap();
1438 assert_eq!(pat.programs[0], (0, 0x10));
1439 assert_eq!(pat.programs[1], (1, 0x100));
1440 }
1441
1442 fn build_cat_section(version: u8, descriptors: &[u8]) -> Vec<u8> {
1445 let section_length = 5 + descriptors.len() + 4;
1446 let mut s = Vec::with_capacity(3 + section_length);
1447 s.push(CAT_TABLE_ID);
1448 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
1449 s.push(len_hi);
1450 s.push((section_length & 0xFF) as u8);
1451 s.push(0xFF);
1454 s.push(0xFF);
1455 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
1457 s.push(0);
1458 s.push(0);
1459 s.extend_from_slice(descriptors);
1460 let crc = mpeg2_crc32(&s);
1461 s.extend_from_slice(&crc.to_be_bytes());
1462 s
1463 }
1464
1465 #[test]
1466 fn cat_round_trip_single_ca_descriptor() {
1467 let ca_descr: &[u8] = &[0x09, 0x06, 0x05, 0x00, 0xE1, 0x23, 0xCA, 0xFE];
1470 let section = build_cat_section(3, ca_descr);
1471 let cat = ConditionalAccessTable::parse(§ion).unwrap();
1472 assert_eq!(cat.version_number, 3);
1473 assert!(cat.current_next_indicator);
1474 let descrs: Vec<_> = cat.iter_descriptors().collect::<Result<_, _>>().unwrap();
1475 assert_eq!(descrs.len(), 1);
1476 match &descrs[0].body {
1477 crate::descriptor::DescriptorBody::Ca(ca) => {
1478 assert_eq!(ca.ca_system_id, 0x0500);
1479 assert_eq!(ca.ca_pid, 0x0123);
1480 assert_eq!(ca.private_data, &[0xCA, 0xFE]);
1481 }
1482 other => panic!("expected CA, got {other:?}"),
1483 }
1484 }
1485
1486 #[test]
1487 fn cat_rejects_wrong_table_id() {
1488 let section = build_pat_section(1, 0, &[(1, 0x100)]);
1491 let err = ConditionalAccessTable::parse(§ion).unwrap_err();
1492 match err {
1493 TsError::Unsupported(_) => {}
1494 other => panic!("expected Unsupported, got {other:?}"),
1495 }
1496 }
1497
1498 fn build_tsdt_section(version: u8, section_number: u8, descriptors: &[u8]) -> Vec<u8> {
1503 let section_length = 5 + descriptors.len() + 4;
1504 let mut s = Vec::with_capacity(3 + section_length);
1505 s.push(TSDT_TABLE_ID);
1506 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
1507 s.push(len_hi);
1508 s.push((section_length & 0xFF) as u8);
1509 s.push(0xFF);
1511 s.push(0xFF);
1512 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
1514 s.push(section_number);
1515 s.push(section_number); s.extend_from_slice(descriptors);
1517 let crc = mpeg2_crc32(&s);
1518 s.extend_from_slice(&crc.to_be_bytes());
1519 s
1520 }
1521
1522 #[test]
1523 fn tsdt_round_trip_registration_descriptor() {
1524 let reg_descr: &[u8] = &[0x05, 0x05, b'H', b'D', b'M', b'V', 0xAB];
1528 let section = build_tsdt_section(9, 0, reg_descr);
1529 let tsdt = TransportStreamDescriptionTable::parse(§ion).unwrap();
1530 assert_eq!(tsdt.version_number, 9);
1531 assert!(tsdt.current_next_indicator);
1532 assert_eq!(tsdt.section_number, 0);
1533 assert_eq!(tsdt.last_section_number, 0);
1534 let descrs: Vec<_> = tsdt.iter_descriptors().collect::<Result<_, _>>().unwrap();
1535 assert_eq!(descrs.len(), 1);
1536 match &descrs[0].body {
1537 crate::descriptor::DescriptorBody::Registration {
1538 format_identifier, ..
1539 } => assert_eq!(format_identifier, b"HDMV"),
1540 other => panic!("expected Registration, got {other:?}"),
1541 }
1542 }
1543
1544 #[test]
1545 fn tsdt_empty_descriptor_loop() {
1546 let section = build_tsdt_section(0, 0, &[]);
1548 let tsdt = TransportStreamDescriptionTable::parse(§ion).unwrap();
1549 assert_eq!(tsdt.version_number, 0);
1550 assert!(tsdt.descriptors.is_empty());
1551 assert_eq!(tsdt.iter_descriptors().count(), 0);
1552 }
1553
1554 #[test]
1555 fn tsdt_rejects_wrong_table_id() {
1556 let section = build_cat_section(1, &[0x09, 0x04, 0x05, 0x00, 0xE1, 0x23]);
1558 let err = TransportStreamDescriptionTable::parse(§ion).unwrap_err();
1559 match err {
1560 TsError::Unsupported(_) => {}
1561 other => panic!("expected Unsupported, got {other:?}"),
1562 }
1563 }
1564
1565 #[test]
1566 fn tsdt_reassembles_via_psi_assembler() {
1567 let reg_descr: &[u8] = &[0x05, 0x04, b'A', b'V', b'0', b'1'];
1571 let section = build_tsdt_section(2, 0, reg_descr);
1572 let mut payload = vec![0u8]; payload.extend_from_slice(§ion);
1574 payload.resize(184, 0xFF);
1575 let mut asm = PsiSectionAssembler::new();
1576 let out = asm.feed(&payload, true, 0).unwrap();
1577 assert_eq!(out.len(), 1);
1578 let tsdt = TransportStreamDescriptionTable::parse(&out[0]).unwrap();
1579 assert_eq!(tsdt.version_number, 2);
1580 assert_eq!(tsdt.iter_descriptors().count(), 1);
1581 }
1582
1583 fn fake_ts_payload(payload: &[u8], _pusi: bool, _cc: u8) -> Vec<u8> {
1588 payload.to_vec()
1592 }
1593
1594 #[test]
1595 fn assembler_single_packet_section() {
1596 let section = build_pat_section(7, 0, &[(1, 0x100)]);
1599 let mut payload = vec![0u8]; payload.extend_from_slice(§ion);
1601 payload.resize(184, 0xFF);
1604 let mut asm = PsiSectionAssembler::new();
1605 let out = asm
1606 .feed(&fake_ts_payload(&payload, true, 0), true, 0)
1607 .unwrap();
1608 assert_eq!(out.len(), 1);
1609 assert_eq!(out[0], section);
1610 let pat = ProgramAssociationTable::parse(&out[0]).unwrap();
1611 assert_eq!(pat.programs, vec![(1, 0x100)]);
1612 }
1613
1614 #[test]
1615 fn assembler_section_spans_two_ts_packets() {
1616 let stuff_descr = vec![0u8; 100]; let mut descr_block = vec![];
1620 descr_block.push(0xC0); descr_block.push(stuff_descr.len() as u8);
1622 descr_block.extend_from_slice(&stuff_descr);
1623 let section = build_pmt_section(
1624 1,
1625 0,
1626 0x100,
1627 &[],
1628 &[(0x1B, 0x1011, &descr_block), (0x81, 0x1100, &descr_block)],
1629 );
1630 assert!(
1631 section.len() > 184,
1632 "section ({} bytes) must exceed a single TS payload to exercise the assembler",
1633 section.len()
1634 );
1635
1636 let mut p0 = vec![0u8]; p0.extend_from_slice(§ion[..183]);
1642 let mut p1 = Vec::new();
1645 p1.extend_from_slice(§ion[183..]);
1646 p1.resize(184, 0xFF);
1647
1648 let mut asm = PsiSectionAssembler::new();
1649 let out0 = asm.feed(&p0, true, 5).unwrap();
1650 assert!(
1651 out0.is_empty(),
1652 "section straddles two TS packets — first feed must not yield"
1653 );
1654 let out1 = asm.feed(&p1, false, 6).unwrap();
1655 assert_eq!(out1.len(), 1, "second feed must complete the section");
1656 assert_eq!(out1[0], section);
1657 let pmt = ProgramMapTable::parse(&out1[0]).unwrap();
1658 assert_eq!(pmt.streams.len(), 2);
1659 }
1660
1661 #[test]
1662 fn assembler_section_spans_three_ts_packets() {
1663 let payload_chunk = vec![0xABu8; 250];
1668 let mut descr_block: Vec<u8> = Vec::new();
1669 for tag in [0xC0u8, 0xC1] {
1670 descr_block.push(tag);
1671 descr_block.push(payload_chunk.len() as u8);
1672 descr_block.extend_from_slice(&payload_chunk);
1673 }
1674 let section = build_pmt_section(1, 0, 0x100, &descr_block, &[(0x1B, 0x1011, &[])]);
1675 assert!(
1676 section.len() > 2 * 184,
1677 "need >2 TS payloads worth, got {} bytes",
1678 section.len()
1679 );
1680
1681 let mut p0 = vec![0u8];
1685 p0.extend_from_slice(§ion[..183]);
1686 let p1 = section[183..183 + 184].to_vec();
1687 let mut p2 = section[183 + 184..].to_vec();
1688 p2.resize(184, 0xFF);
1689
1690 let mut asm = PsiSectionAssembler::new();
1691 assert!(asm.feed(&p0, true, 0).unwrap().is_empty());
1692 assert!(asm.feed(&p1, false, 1).unwrap().is_empty());
1693 let out = asm.feed(&p2, false, 2).unwrap();
1694 assert_eq!(out.len(), 1);
1695 assert_eq!(out[0], section);
1696 }
1697
1698 #[test]
1699 fn assembler_cc_skip_discards_in_flight_section() {
1700 let section = build_pat_section(1, 0, &[(1, 0x100), (2, 0x200), (3, 0x300)]);
1704 let mut padded = Vec::new();
1707 for _ in 0..200 {
1708 padded.extend_from_slice(§ion);
1709 }
1710 let big_section = build_pmt_section(
1711 1,
1712 0,
1713 0x100,
1714 &[0u8; 200],
1715 &[(0x1B, 0x1011, &[]), (0x81, 0x1100, &[])],
1716 );
1717 let mut p0 = vec![0u8];
1718 p0.extend_from_slice(&big_section[..183]);
1719
1720 let mut asm = PsiSectionAssembler::new();
1721 assert!(asm.feed(&p0, true, 0).unwrap().is_empty());
1722 let p1 = vec![0xFFu8; 184];
1725 let out = asm.feed(&p1, false, 2).unwrap();
1726 assert!(out.is_empty());
1727 let mut p2 = big_section[183..].to_vec();
1730 p2.resize(184, 0xFF);
1731 let out2 = asm.feed(&p2, false, 3).unwrap();
1732 assert!(out2.is_empty(), "CC-skip must have dropped in-flight bytes");
1733
1734 let _ = padded;
1735 }
1736
1737 #[test]
1738 fn assembler_stuffing_terminates_payload() {
1739 let section = build_pat_section(9, 0, &[(1, 0x100)]);
1742 let mut payload = vec![0u8]; payload.extend_from_slice(§ion);
1744 payload.extend_from_slice(&[0xFFu8; 64]);
1745 let mut asm = PsiSectionAssembler::new();
1746 let out = asm.feed(&payload, true, 0).unwrap();
1747 assert_eq!(out.len(), 1);
1748 assert_eq!(out[0], section);
1749 }
1750
1751 #[test]
1752 fn assembler_two_sections_same_payload() {
1753 let s0 = build_pat_section(1, 0, &[(1, 0x100)]);
1757 let s1 = build_pat_section(2, 0, &[(2, 0x200)]);
1758 let mut payload = vec![0u8];
1759 payload.extend_from_slice(&s0);
1760 payload.extend_from_slice(&s1);
1761 payload.resize(184, 0xFF);
1762 let mut asm = PsiSectionAssembler::new();
1763 let out = asm.feed(&payload, true, 0).unwrap();
1764 assert_eq!(out.len(), 2);
1765 assert_eq!(out[0], s0);
1766 assert_eq!(out[1], s1);
1767 }
1768
1769 #[test]
1770 fn assembler_pointer_field_finishes_prev_section() {
1771 let big_descr = vec![0u8; 200];
1780 let s0 = build_pmt_section(1, 0, 0x100, &big_descr, &[(0x1B, 0x1011, &[])]);
1781 let s1 = build_pat_section(7, 0, &[(3, 0x300)]);
1782 assert!(s0.len() > 184, "s0 must straddle a TS packet boundary");
1783 let mut p0 = vec![0u8]; p0.extend_from_slice(&s0[..183]);
1785 let remaining = s0.len() - 183;
1787 let mut p1 = vec![remaining as u8];
1788 p1.extend_from_slice(&s0[183..]);
1789 p1.extend_from_slice(&s1);
1790 p1.resize(184, 0xFF);
1791
1792 let mut asm = PsiSectionAssembler::new();
1793 let out0 = asm.feed(&p0, true, 0).unwrap();
1794 assert!(out0.is_empty());
1795 let out1 = asm.feed(&p1, true, 1).unwrap();
1796 assert_eq!(out1.len(), 2);
1797 assert_eq!(out1[0], s0);
1798 assert_eq!(out1[1], s1);
1799 }
1800
1801 #[test]
1802 fn assembler_reset_drops_buffer() {
1803 let section = build_pmt_section(1, 0, 0x100, &[0u8; 200], &[(0x1B, 0x1011, &[])]);
1804 let mut p0 = vec![0u8];
1805 p0.extend_from_slice(§ion[..183]);
1806 let mut asm = PsiSectionAssembler::new();
1807 assert!(asm.feed(&p0, true, 0).unwrap().is_empty());
1808 asm.reset();
1809 let mut p1 = section[183..].to_vec();
1812 p1.resize(184, 0xFF);
1813 let out = asm.feed(&p1, false, 1).unwrap();
1814 assert!(out.is_empty());
1815 }
1816
1817 fn build_sdt_service(
1819 service_id: u16,
1820 eit_sched: bool,
1821 eit_pf: bool,
1822 running_status: u8,
1823 free_ca: bool,
1824 descriptors: &[u8],
1825 ) -> Vec<u8> {
1826 let mut s = Vec::new();
1827 s.extend_from_slice(&service_id.to_be_bytes());
1828 let mut b = 0b1111_1100u8;
1830 if eit_sched {
1831 b |= 0b10;
1832 }
1833 if eit_pf {
1834 b |= 0b01;
1835 }
1836 s.push(b);
1837 let dlen = descriptors.len() as u16;
1839 let b3 = ((running_status & 0b111) << 5)
1840 | (if free_ca { 0b0001_0000 } else { 0 })
1841 | ((dlen >> 8) & 0x0F) as u8;
1842 s.push(b3);
1843 s.push((dlen & 0xFF) as u8);
1844 s.extend_from_slice(descriptors);
1845 s
1846 }
1847
1848 fn build_sdt_section(
1850 table_id: u8,
1851 tsid: u16,
1852 version: u8,
1853 original_network_id: u16,
1854 services: &[Vec<u8>],
1855 ) -> Vec<u8> {
1856 let body_len: usize = 3 + services.iter().map(|s| s.len()).sum::<usize>();
1858 let section_length = 5 + body_len + 4;
1859 let mut s = Vec::with_capacity(3 + section_length);
1860 s.push(table_id);
1861 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
1862 s.push(len_hi);
1863 s.push((section_length & 0xFF) as u8);
1864 s.extend_from_slice(&tsid.to_be_bytes());
1865 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
1866 s.push(0); s.push(0); s.extend_from_slice(&original_network_id.to_be_bytes());
1869 s.push(0xFF); for svc in services {
1871 s.extend_from_slice(svc);
1872 }
1873 let crc = mpeg2_crc32(&s);
1874 s.extend_from_slice(&crc.to_be_bytes());
1875 s
1876 }
1877
1878 fn build_service_descriptor(service_type: u8, provider: &[u8], name: &[u8]) -> Vec<u8> {
1880 let mut body = vec![service_type, provider.len() as u8];
1881 body.extend_from_slice(provider);
1882 body.push(name.len() as u8);
1883 body.extend_from_slice(name);
1884 let mut v = vec![0x48u8, body.len() as u8];
1885 v.extend_from_slice(&body);
1886 v
1887 }
1888
1889 #[test]
1890 fn sdt_actual_single_service_round_trip() {
1891 let sd = build_service_descriptor(0x01, b"Provider", b"Channel One");
1892 let svc = build_sdt_service(0x0064, true, true, 4, false, &sd);
1893 let section = build_sdt_section(SDT_ACTUAL_TABLE_ID, 0x0001, 7, 0x2024, &[svc]);
1894 let sdt = ServiceDescriptionTable::parse(§ion).unwrap();
1895 assert!(!sdt.other_transport_stream);
1896 assert_eq!(sdt.transport_stream_id, 0x0001);
1897 assert_eq!(sdt.version_number, 7);
1898 assert!(sdt.current_next_indicator);
1899 assert_eq!(sdt.original_network_id, 0x2024);
1900 assert_eq!(sdt.services.len(), 1);
1901 let s = &sdt.services[0];
1902 assert_eq!(s.service_id, 0x0064);
1903 assert!(s.eit_schedule_flag);
1904 assert!(s.eit_present_following_flag);
1905 assert_eq!(s.running_status, RunningStatus::Running);
1906 assert!(!s.free_ca_mode);
1907 let d = s.iter_descriptors().next().unwrap().unwrap();
1908 match d.body {
1909 crate::descriptor::DescriptorBody::Service(svc) => {
1910 assert_eq!(svc.service_type, 0x01);
1911 assert_eq!(svc.service_provider_name, b"Provider");
1912 assert_eq!(svc.service_name, b"Channel One");
1913 }
1914 other => panic!("expected Service, got {other:?}"),
1915 }
1916 }
1917
1918 #[test]
1919 fn sdt_other_table_id_sets_flag() {
1920 let svc = build_sdt_service(0x0001, false, false, 1, true, &[]);
1921 let section = build_sdt_section(SDT_OTHER_TABLE_ID, 0x0009, 0, 0x0001, &[svc]);
1922 let sdt = ServiceDescriptionTable::parse(§ion).unwrap();
1923 assert!(sdt.other_transport_stream);
1924 assert_eq!(sdt.services.len(), 1);
1925 let s = &sdt.services[0];
1926 assert_eq!(s.running_status, RunningStatus::NotRunning);
1927 assert!(s.free_ca_mode);
1928 assert!(!s.eit_schedule_flag);
1929 assert!(!s.eit_present_following_flag);
1930 }
1931
1932 #[test]
1933 fn sdt_multiple_services() {
1934 let svc0 = build_sdt_service(0x0064, true, true, 4, false, &[]);
1935 let svc1 = build_sdt_service(0x0065, false, true, 5, true, &[]);
1936 let section = build_sdt_section(SDT_ACTUAL_TABLE_ID, 0x0002, 1, 0x1000, &[svc0, svc1]);
1937 let sdt = ServiceDescriptionTable::parse(§ion).unwrap();
1938 assert_eq!(sdt.services.len(), 2);
1939 assert_eq!(sdt.services[0].service_id, 0x0064);
1940 assert_eq!(sdt.services[1].service_id, 0x0065);
1941 assert_eq!(sdt.services[1].running_status, RunningStatus::OffAir);
1942 }
1943
1944 #[test]
1945 fn sdt_rejects_wrong_table_id() {
1946 let section = build_pmt_section(1, 0, 0x100, &[], &[(0x1B, 0x1011, &[])]);
1948 assert!(ServiceDescriptionTable::parse(§ion).is_err());
1949 }
1950
1951 #[test]
1952 fn sdt_crc_mismatch_rejected() {
1953 let svc = build_sdt_service(0x0064, true, true, 4, false, &[]);
1954 let mut section = build_sdt_section(SDT_ACTUAL_TABLE_ID, 0x0001, 7, 0x2024, &[svc]);
1955 let last = section.len() - 1;
1956 section[last] ^= 0xFF;
1957 assert!(matches!(
1958 ServiceDescriptionTable::parse(§ion),
1959 Err(TsError::PsiCrcMismatch { .. })
1960 ));
1961 }
1962
1963 #[test]
1964 fn running_status_reserved_values() {
1965 assert_eq!(RunningStatus::from_bits(6), RunningStatus::Reserved(6));
1966 assert_eq!(RunningStatus::from_bits(7), RunningStatus::Reserved(7));
1967 assert_eq!(RunningStatus::from_bits(0), RunningStatus::Undefined);
1968 assert_eq!(RunningStatus::from_bits(2), RunningStatus::StartsSoon);
1969 assert_eq!(RunningStatus::from_bits(3), RunningStatus::Pausing);
1970 }
1971
1972 fn build_short_event_descriptor(lang: &[u8; 3], name: &[u8], text: &[u8]) -> Vec<u8> {
1974 let mut body = Vec::new();
1975 body.extend_from_slice(lang);
1976 body.push(name.len() as u8);
1977 body.extend_from_slice(name);
1978 body.push(text.len() as u8);
1979 body.extend_from_slice(text);
1980 let mut v = vec![0x4Du8, body.len() as u8];
1981 v.extend_from_slice(&body);
1982 v
1983 }
1984
1985 #[allow(clippy::too_many_arguments)]
1987 fn build_eit_event(
1988 event_id: u16,
1989 start_time: [u8; 5],
1990 duration_bcd: [u8; 3],
1991 running_status: u8,
1992 free_ca: bool,
1993 descriptors: &[u8],
1994 ) -> Vec<u8> {
1995 let mut s = Vec::new();
1996 s.extend_from_slice(&event_id.to_be_bytes());
1997 s.extend_from_slice(&start_time);
1998 s.extend_from_slice(&duration_bcd);
1999 let dlen = descriptors.len() as u16;
2000 let b = ((running_status & 0b111) << 5)
2001 | (if free_ca { 0b0001_0000 } else { 0 })
2002 | ((dlen >> 8) & 0x0F) as u8;
2003 s.push(b);
2004 s.push((dlen & 0xFF) as u8);
2005 s.extend_from_slice(descriptors);
2006 s
2007 }
2008
2009 #[allow(clippy::too_many_arguments)]
2011 fn build_eit_section(
2012 table_id: u8,
2013 service_id: u16,
2014 version: u8,
2015 tsid: u16,
2016 onid: u16,
2017 segment_last: u8,
2018 last_table_id: u8,
2019 events: &[Vec<u8>],
2020 ) -> Vec<u8> {
2021 let body_len: usize = 6 + events.iter().map(|e| e.len()).sum::<usize>();
2024 let section_length = 5 + body_len + 4;
2025 let mut s = Vec::with_capacity(3 + section_length);
2026 s.push(table_id);
2027 let len_hi = 0b1011_0000 | ((section_length >> 8) & 0x0F) as u8;
2028 s.push(len_hi);
2029 s.push((section_length & 0xFF) as u8);
2030 s.extend_from_slice(&service_id.to_be_bytes());
2031 s.push(0b1100_0001 | ((version & 0b1_1111) << 1));
2032 s.push(0); s.push(0); s.extend_from_slice(&tsid.to_be_bytes());
2035 s.extend_from_slice(&onid.to_be_bytes());
2036 s.push(segment_last);
2037 s.push(last_table_id);
2038 for e in events {
2039 s.extend_from_slice(e);
2040 }
2041 let crc = mpeg2_crc32(&s);
2042 s.extend_from_slice(&crc.to_be_bytes());
2043 s
2044 }
2045
2046 #[test]
2047 fn eit_start_time_spec_example() {
2048 let dt = decode_eit_start_time([0xC0, 0x79, 0x12, 0x45, 0x00]).unwrap();
2051 assert_eq!(dt.mjd, 0xC079);
2052 assert_eq!(dt.year, 1993);
2053 assert_eq!(dt.month, 10);
2054 assert_eq!(dt.day, 13);
2055 assert_eq!(dt.hour, 12);
2056 assert_eq!(dt.minute, 45);
2057 assert_eq!(dt.second, 0);
2058 }
2059
2060 #[test]
2061 fn eit_start_time_undefined_sentinel() {
2062 assert!(decode_eit_start_time([0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).is_none());
2063 }
2064
2065 #[test]
2066 fn eit_duration_spec_example() {
2067 let d = EitDuration {
2069 hours: bcd_byte(0x01),
2070 minutes: bcd_byte(0x45),
2071 seconds: bcd_byte(0x30),
2072 };
2073 assert_eq!(d.hours, 1);
2074 assert_eq!(d.minutes, 45);
2075 assert_eq!(d.seconds, 30);
2076 assert_eq!(d.as_seconds(), 3600 + 45 * 60 + 30);
2077 }
2078
2079 #[test]
2080 fn eit_actual_pf_single_event_round_trip() {
2081 let sed = build_short_event_descriptor(b"eng", b"The Event", b"A short description");
2082 let ev = build_eit_event(
2083 0x1234,
2084 [0xC0, 0x79, 0x12, 0x45, 0x00],
2085 [0x01, 0x45, 0x30],
2086 4, false,
2088 &sed,
2089 );
2090 let section = build_eit_section(
2091 EIT_ACTUAL_PF_TABLE_ID,
2092 0x0064, 5,
2094 0x0001, 0x2024, 0,
2097 EIT_ACTUAL_PF_TABLE_ID,
2098 &[ev],
2099 );
2100 let eit = EventInformationTable::parse(§ion).unwrap();
2101 assert!(!eit.other_transport_stream);
2102 assert!(!eit.schedule);
2103 assert_eq!(eit.table_id, EIT_ACTUAL_PF_TABLE_ID);
2104 assert_eq!(eit.service_id, 0x0064);
2105 assert_eq!(eit.version_number, 5);
2106 assert!(eit.current_next_indicator);
2107 assert_eq!(eit.transport_stream_id, 0x0001);
2108 assert_eq!(eit.original_network_id, 0x2024);
2109 assert_eq!(eit.last_table_id, EIT_ACTUAL_PF_TABLE_ID);
2110 assert_eq!(eit.events.len(), 1);
2111 let e = &eit.events[0];
2112 assert_eq!(e.event_id, 0x1234);
2113 let st = e.start_time.unwrap();
2114 assert_eq!((st.year, st.month, st.day), (1993, 10, 13));
2115 assert_eq!((st.hour, st.minute, st.second), (12, 45, 0));
2116 assert_eq!(e.duration.as_seconds(), 6330);
2117 assert_eq!(e.running_status, RunningStatus::Running);
2118 assert!(!e.free_ca_mode);
2119 let d = e.iter_descriptors().next().unwrap().unwrap();
2121 match d.body {
2122 DescriptorBody::ShortEvent(se) => {
2123 assert_eq!(&se.language_code, b"eng");
2124 assert_eq!(se.event_name, b"The Event");
2125 assert_eq!(se.text, b"A short description");
2126 }
2127 other => panic!("expected ShortEvent, got {other:?}"),
2128 }
2129 }
2130
2131 #[test]
2132 fn eit_schedule_and_other_flags() {
2133 let ev = build_eit_event(1, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF], [0, 0, 0], 0, true, &[]);
2135 let section = build_eit_section(0x60, 0x0001, 0, 0x0001, 0x0001, 0, 0x62, &[ev]);
2136 let eit = EventInformationTable::parse(§ion).unwrap();
2137 assert!(eit.other_transport_stream);
2138 assert!(eit.schedule);
2139 assert_eq!(eit.last_table_id, 0x62);
2140 assert!(eit.events[0].start_time.is_none());
2142 assert!(eit.events[0].free_ca_mode);
2143 }
2144
2145 #[test]
2146 fn eit_multiple_events() {
2147 let e0 = build_eit_event(
2148 10,
2149 [0xC0, 0x79, 0x12, 0x00, 0x00],
2150 [0, 0x30, 0],
2151 4,
2152 false,
2153 &[],
2154 );
2155 let e1 = build_eit_event(
2156 11,
2157 [0xC0, 0x79, 0x12, 0x30, 0x00],
2158 [0x01, 0, 0],
2159 1,
2160 false,
2161 &[],
2162 );
2163 let section = build_eit_section(
2164 EIT_ACTUAL_PF_TABLE_ID,
2165 0x0064,
2166 0,
2167 0x0001,
2168 0x0001,
2169 0,
2170 0x4E,
2171 &[e0, e1],
2172 );
2173 let eit = EventInformationTable::parse(§ion).unwrap();
2174 assert_eq!(eit.events.len(), 2);
2175 assert_eq!(eit.events[0].event_id, 10);
2176 assert_eq!(eit.events[1].event_id, 11);
2177 assert_eq!(eit.events[0].duration.minutes, 30);
2178 assert_eq!(eit.events[1].duration.hours, 1);
2179 }
2180
2181 #[test]
2182 fn eit_table_id_classification() {
2183 assert!(EventInformationTable::is_eit_table_id(0x4E));
2184 assert!(EventInformationTable::is_eit_table_id(0x4F));
2185 assert!(EventInformationTable::is_eit_table_id(0x50));
2186 assert!(EventInformationTable::is_eit_table_id(0x5F));
2187 assert!(EventInformationTable::is_eit_table_id(0x60));
2188 assert!(EventInformationTable::is_eit_table_id(0x6F));
2189 assert!(!EventInformationTable::is_eit_table_id(0x4D));
2190 assert!(!EventInformationTable::is_eit_table_id(0x70));
2191 assert!(!EventInformationTable::is_eit_table_id(0x42));
2192 }
2193
2194 #[test]
2195 fn eit_rejects_wrong_table_id() {
2196 let ev = build_eit_event(1, [0xFF; 5], [0, 0, 0], 0, false, &[]);
2197 let mut section = build_eit_section(0x70, 0, 0, 0, 0, 0, 0, &[ev]);
2200 section[0] = 0x70;
2201 assert!(matches!(
2202 EventInformationTable::parse(§ion),
2203 Err(TsError::Unsupported(_))
2204 ));
2205 }
2206
2207 #[test]
2208 fn eit_crc_mismatch_rejected() {
2209 let ev = build_eit_event(1, [0xC0, 0x79, 0x12, 0x45, 0x00], [0, 0, 0], 4, false, &[]);
2210 let mut section = build_eit_section(
2211 EIT_ACTUAL_PF_TABLE_ID,
2212 0x0064,
2213 0,
2214 0x0001,
2215 0x0001,
2216 0,
2217 0x4E,
2218 &[ev],
2219 );
2220 let last = section.len() - 1;
2221 section[last] ^= 0xFF;
2222 assert!(matches!(
2223 EventInformationTable::parse(§ion),
2224 Err(TsError::PsiCrcMismatch { .. })
2225 ));
2226 }
2227}