1use crate::descriptors::DescriptorLoop;
11use crate::error::{Error, Result};
12use crate::tables::RunningStatus;
13use alloc::vec::Vec;
14use dvb_common::{Parse, Serialize};
15
16pub const TABLE_ID_PF_ACTUAL: u8 = 0x4E;
18pub const TABLE_ID_PF_OTHER: u8 = 0x4F;
20pub const TABLE_ID_SCHEDULE_ACTUAL_FIRST: u8 = 0x50;
22pub const TABLE_ID_SCHEDULE_ACTUAL_LAST: u8 = 0x5F;
24pub const TABLE_ID_SCHEDULE_OTHER_FIRST: u8 = 0x60;
26pub const TABLE_ID_SCHEDULE_OTHER_LAST: u8 = 0x6F;
28pub const PID: u16 = 0x0012;
30
31const MIN_HEADER_LEN: usize = 3;
32const EXTENSION_HEADER_LEN: usize = 5;
33const POST_EXTENSION_LEN: usize = 6;
36const CRC_LEN: usize = 4;
37const MIN_SECTION_LEN: usize = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + CRC_LEN;
38const EVENT_HEADER_LEN: usize = 12;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize))]
43#[non_exhaustive]
44pub enum EitKind {
45 PresentFollowingActual,
47 PresentFollowingOther,
49 ScheduleActual,
51 ScheduleOther,
53}
54
55impl EitKind {
56 #[must_use]
58 pub fn from_table_id(table_id: u8) -> Option<Self> {
59 match table_id {
60 TABLE_ID_PF_ACTUAL => Some(Self::PresentFollowingActual),
61 TABLE_ID_PF_OTHER => Some(Self::PresentFollowingOther),
62 TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST => {
63 Some(Self::ScheduleActual)
64 }
65 TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST => {
66 Some(Self::ScheduleOther)
67 }
68 _ => None,
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
77pub struct EitEvent<'a> {
78 pub event_id: u16,
80 pub(crate) start_time_raw: [u8; 5],
83 pub(crate) duration_raw: [u8; 3],
86 pub running_status: RunningStatus,
88 pub free_ca_mode: bool,
90 pub descriptors: DescriptorLoop<'a>,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize))]
98#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
99pub struct EitSection<'a> {
100 pub kind: EitKind,
102 pub table_id: u8,
104 pub service_id: u16,
106 pub version_number: u8,
108 pub current_next_indicator: bool,
110 pub section_number: u8,
112 pub last_section_number: u8,
114 pub transport_stream_id: u16,
116 pub original_network_id: u16,
118 pub segment_last_section_number: u8,
120 pub last_table_id: u8,
122 pub events: Vec<EitEvent<'a>>,
124}
125
126impl<'a> Parse<'a> for EitSection<'a> {
127 type Error = crate::error::Error;
128 fn parse(bytes: &'a [u8]) -> Result<Self> {
129 let min_len = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + CRC_LEN;
130 if bytes.len() < min_len {
131 return Err(Error::BufferTooShort {
132 need: min_len,
133 have: bytes.len(),
134 what: "EitSection",
135 });
136 }
137
138 let table_id = bytes[0];
139 let kind = EitKind::from_table_id(table_id).ok_or(Error::UnexpectedTableId {
140 table_id,
141 what: "EitSection",
142 expected: &[
143 TABLE_ID_PF_ACTUAL,
144 TABLE_ID_PF_OTHER,
145 TABLE_ID_SCHEDULE_ACTUAL_FIRST,
146 TABLE_ID_SCHEDULE_OTHER_FIRST,
147 ],
148 })?;
149
150 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
151 let total = super::check_section_length(
152 bytes.len(),
153 MIN_HEADER_LEN,
154 section_length as usize,
155 MIN_SECTION_LEN,
156 )?;
157
158 let service_id = u16::from_be_bytes([bytes[3], bytes[4]]);
159 let version_number = (bytes[5] >> 1) & 0x1F;
160 let current_next_indicator = (bytes[5] & 0x01) != 0;
161 let section_number = bytes[6];
162 let last_section_number = bytes[7];
163
164 let transport_stream_id = u16::from_be_bytes([bytes[8], bytes[9]]);
165 let original_network_id = u16::from_be_bytes([bytes[10], bytes[11]]);
166 let segment_last_section_number = bytes[12];
167 let last_table_id = bytes[13];
168
169 let events_start = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
170 let events_end = total - CRC_LEN;
171 let mut events = Vec::new();
172 let mut pos = events_start;
173 while pos + EVENT_HEADER_LEN <= events_end {
174 let event_id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
175 let start_time_raw = [
176 bytes[pos + 2],
177 bytes[pos + 3],
178 bytes[pos + 4],
179 bytes[pos + 5],
180 bytes[pos + 6],
181 ];
182 let duration_raw = [bytes[pos + 7], bytes[pos + 8], bytes[pos + 9]];
183 let status_and_len_hi = bytes[pos + 10];
184 let running_status = RunningStatus::from_u8((status_and_len_hi >> 5) & 0x07);
185 let free_ca_mode = (status_and_len_hi & 0x10) != 0;
186 let descriptors_loop_length =
187 (((status_and_len_hi & 0x0F) as usize) << 8) | bytes[pos + 11] as usize;
188 let desc_start = pos + EVENT_HEADER_LEN;
189 let desc_end = desc_start + descriptors_loop_length;
190 if desc_end > events_end {
191 return Err(Error::SectionLengthOverflow {
192 declared: descriptors_loop_length,
193 available: events_end.saturating_sub(desc_start),
194 });
195 }
196 events.push(EitEvent {
197 event_id,
198 start_time_raw,
199 duration_raw,
200 running_status,
201 free_ca_mode,
202 descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
203 });
204 pos = desc_end;
205 }
206
207 if pos != events_end {
208 return Err(Error::BufferTooShort {
209 need: events_end - pos,
210 have: 0,
211 what: "EitSection trailing event bytes",
212 });
213 }
214
215 Ok(EitSection {
216 kind,
217 table_id,
218 service_id,
219 version_number,
220 current_next_indicator,
221 section_number,
222 last_section_number,
223 transport_stream_id,
224 original_network_id,
225 segment_last_section_number,
226 last_table_id,
227 events,
228 })
229 }
230}
231
232impl Serialize for EitSection<'_> {
233 type Error = crate::error::Error;
234 fn serialized_len(&self) -> usize {
235 let ev_bytes: usize = self
236 .events
237 .iter()
238 .map(|e| EVENT_HEADER_LEN + e.descriptors.len())
239 .sum();
240 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN
241 }
242
243 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
244 let len = self.serialized_len();
245 if buf.len() < len {
246 return Err(Error::OutputBufferTooSmall {
247 need: len,
248 have: buf.len(),
249 });
250 }
251 let section_length_usize = len - MIN_HEADER_LEN;
252 if section_length_usize > 0x0FFF {
253 return Err(Error::SectionLengthOverflow {
254 declared: section_length_usize,
255 available: 0x0FFF,
256 });
257 }
258 let section_length: u16 = section_length_usize as u16;
259 buf[0] = self.table_id;
260 buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
261 buf[2] = (section_length & 0xFF) as u8;
262 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
263 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
264 buf[6] = self.section_number;
265 buf[7] = self.last_section_number;
266 buf[8..10].copy_from_slice(&self.transport_stream_id.to_be_bytes());
267 buf[10..12].copy_from_slice(&self.original_network_id.to_be_bytes());
268 buf[12] = self.segment_last_section_number;
269 buf[13] = self.last_table_id;
270
271 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
272 for ev in &self.events {
273 buf[pos..pos + 2].copy_from_slice(&ev.event_id.to_be_bytes());
274 buf[pos + 2..pos + 7].copy_from_slice(&ev.start_time_raw);
275 buf[pos + 7..pos + 10].copy_from_slice(&ev.duration_raw);
276 let dll = ev.descriptors.len() as u16;
277 buf[pos + 10] = (ev.running_status.to_u8() << 5)
278 | (u8::from(ev.free_ca_mode) << 4)
279 | ((dll >> 8) as u8 & 0x0F);
280 buf[pos + 11] = (dll & 0xFF) as u8;
281 let desc_start = pos + EVENT_HEADER_LEN;
282 buf[desc_start..desc_start + ev.descriptors.len()]
283 .copy_from_slice(ev.descriptors.raw());
284 pos = desc_start + ev.descriptors.len();
285 }
286
287 let crc_pos = len - CRC_LEN;
288 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
289 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
290 Ok(len)
291 }
292}
293impl<'a> crate::traits::TableDef<'a> for EitSection<'a> {
294 const TABLE_ID_RANGES: &'static [(u8, u8)] =
295 &[(TABLE_ID_PF_ACTUAL, TABLE_ID_SCHEDULE_OTHER_LAST)];
296 const NAME: &'static str = "EVENT_INFORMATION";
297}
298
299impl<'a> EitEvent<'a> {
300 #[must_use]
307 pub fn start_time(&self) -> Option<dvb_common::time::MjdBcdDateTime> {
308 dvb_common::time::decode_mjd_bcd(self.start_time_raw)
309 }
310
311 pub fn set_start_time_decoded(
317 &mut self,
318 dt: dvb_common::time::MjdBcdDateTime,
319 ) -> crate::Result<()> {
320 self.start_time_raw =
321 dvb_common::time::encode_mjd_bcd(dt).ok_or(crate::Error::ValueOutOfRange {
322 field: "EitEvent::start_time",
323 reason: "date not representable in 16-bit MJD",
324 })?;
325 Ok(())
326 }
327
328 #[must_use]
333 pub fn duration(&self) -> Option<core::time::Duration> {
334 dvb_common::time::decode_bcd_duration(self.duration_raw)
335 }
336
337 pub fn set_duration(&mut self, duration: core::time::Duration) -> crate::Result<()> {
343 self.duration_raw = dvb_common::time::encode_bcd_duration(duration).ok_or(
344 crate::Error::ValueOutOfRange {
345 field: "EitEvent::duration",
346 reason: "duration must be < 100 hours",
347 },
348 )?;
349 Ok(())
350 }
351
352 #[must_use]
354 pub fn start_time_raw(&self) -> [u8; 5] {
355 self.start_time_raw
356 }
357
358 #[must_use]
360 pub fn duration_raw(&self) -> [u8; 3] {
361 self.duration_raw
362 }
363
364 #[allow(clippy::too_many_arguments)]
370 #[must_use]
371 pub fn new(
372 event_id: u16,
373 start_time_raw: [u8; 5],
374 duration_raw: [u8; 3],
375 running_status: RunningStatus,
376 free_ca_mode: bool,
377 descriptors: DescriptorLoop<'a>,
378 ) -> Self {
379 Self {
380 event_id,
381 start_time_raw,
382 duration_raw,
383 running_status,
384 free_ca_mode,
385 descriptors,
386 }
387 }
388}
389
390#[cfg(feature = "chrono")]
391impl EitEvent<'_> {
392 #[must_use]
397 pub fn start_time_chrono(&self) -> Option<chrono::DateTime<chrono::Utc>> {
398 dvb_common::time::decode_mjd_bcd_utc(self.start_time_raw)
399 }
400
401 pub fn set_start_time(
407 &mut self,
408 start_time: chrono::DateTime<chrono::Utc>,
409 ) -> crate::Result<()> {
410 self.start_time_raw = dvb_common::time::encode_mjd_bcd_utc(start_time).ok_or(
411 crate::Error::ValueOutOfRange {
412 field: "EitEvent::start_time",
413 reason: "date not representable in 16-bit MJD",
414 },
415 )?;
416 Ok(())
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 type TestEvent = (u16, [u8; 5], [u8; 3], u8, bool, Vec<u8>);
425
426 fn build_eit(
427 table_id: u8,
428 service_id: u16,
429 version: u8,
430 tsid: u16,
431 onid: u16,
432 events: &[TestEvent],
433 ) -> Vec<u8> {
434 let ev_bytes: usize = events
435 .iter()
436 .map(|(_, _, _, _, _, d)| EVENT_HEADER_LEN + d.len())
437 .sum();
438 let section_length: u16 =
439 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN) as u16;
440 let mut v = Vec::new();
441 v.push(table_id);
442 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
443 v.push((section_length & 0xFF) as u8);
444 v.extend_from_slice(&service_id.to_be_bytes());
445 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
446 v.push(0);
447 v.push(0);
448 v.extend_from_slice(&tsid.to_be_bytes());
449 v.extend_from_slice(&onid.to_be_bytes());
450 v.push(0);
451 v.push(table_id);
452 for (eid, start, dur, rs, fca, desc) in events {
453 v.extend_from_slice(&eid.to_be_bytes());
454 v.extend_from_slice(start);
455 v.extend_from_slice(dur);
456 let dll = desc.len() as u16;
457 v.push(((*rs & 0x07) << 5) | (u8::from(*fca) << 4) | ((dll >> 8) as u8 & 0x0F));
458 v.push((dll & 0xFF) as u8);
459 v.extend_from_slice(desc);
460 }
461 v.extend_from_slice(&[0, 0, 0, 0]);
462 v
463 }
464
465 #[test]
466 fn parse_pf_actual_and_other_map_to_correct_kind() {
467 for (tid, expected) in [
468 (TABLE_ID_PF_ACTUAL, EitKind::PresentFollowingActual),
469 (TABLE_ID_PF_OTHER, EitKind::PresentFollowingOther),
470 ] {
471 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
472 assert_eq!(EitSection::parse(&bytes).unwrap().kind, expected);
473 }
474 }
475
476 #[test]
477 fn schedule_tables_0x50_through_0x5f_all_decode_as_schedule_actual() {
478 for tid in TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST {
479 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
480 assert_eq!(
481 EitSection::parse(&bytes).unwrap().kind,
482 EitKind::ScheduleActual
483 );
484 }
485 }
486
487 #[test]
488 fn schedule_tables_0x60_through_0x6f_all_decode_as_schedule_other() {
489 for tid in TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST {
490 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
491 assert_eq!(
492 EitSection::parse(&bytes).unwrap().kind,
493 EitKind::ScheduleOther
494 );
495 }
496 }
497
498 #[test]
499 fn event_loop_with_descriptor_bytes_preserved() {
500 let desc = vec![0x4D, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
501 let bytes = build_eit(
502 TABLE_ID_PF_ACTUAL,
503 1,
504 0,
505 0x20,
506 0x30,
507 &[(
508 42,
509 [0xDF, 0xA1, 0x12, 0x34, 0x56],
510 [0x00, 0x30, 0x00],
511 4,
512 false,
513 desc.clone(),
514 )],
515 );
516 let eit = EitSection::parse(&bytes).unwrap();
517 assert_eq!(eit.events.len(), 1);
518 assert_eq!(eit.events[0].event_id, 42);
519 assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
520 }
521
522 #[test]
523 fn running_status_extracted() {
524 let bytes = build_eit(
525 TABLE_ID_PF_ACTUAL,
526 1,
527 0,
528 0x20,
529 0x30,
530 &[(1, [0; 5], [0; 3], 2, false, vec![])],
531 );
532 assert_eq!(
533 EitSection::parse(&bytes).unwrap().events[0].running_status,
534 RunningStatus::StartsInAFewSeconds
535 );
536 }
537
538 #[test]
539 fn free_ca_mode_flag_extracted() {
540 let bytes = build_eit(
541 TABLE_ID_PF_ACTUAL,
542 1,
543 0,
544 0x20,
545 0x30,
546 &[(1, [0; 5], [0; 3], 0, true, vec![])],
547 );
548 assert!(EitSection::parse(&bytes).unwrap().events[0].free_ca_mode);
549 }
550
551 #[test]
552 fn serialize_round_trip_preserves_all_events() {
553 let desc1: [u8; 2] = [0x54, 0x00];
554 let eit = EitSection {
555 kind: EitKind::PresentFollowingActual,
556 table_id: TABLE_ID_PF_ACTUAL,
557 service_id: 0x0100,
558 version_number: 3,
559 current_next_indicator: true,
560 section_number: 0,
561 last_section_number: 0,
562 transport_stream_id: 0x1234,
563 original_network_id: 0x0020,
564 segment_last_section_number: 0,
565 last_table_id: TABLE_ID_PF_ACTUAL,
566 events: vec![
567 EitEvent::new(
568 1,
569 [0xDF, 0xA1, 0x12, 0x34, 0x56],
570 [0x00, 0x30, 0x00],
571 RunningStatus::Running,
572 false,
573 DescriptorLoop::new(&desc1),
574 ),
575 EitEvent::new(
576 2,
577 [0xDF, 0xA1, 0x13, 0x00, 0x00],
578 [0x01, 0x00, 0x00],
579 RunningStatus::NotRunning,
580 true,
581 DescriptorLoop::new(&[]),
582 ),
583 ],
584 };
585 let mut buf = vec![0u8; eit.serialized_len()];
586 eit.serialize_into(&mut buf).unwrap();
587 let re = EitSection::parse(&buf).unwrap();
588 assert_eq!(eit, re);
589 }
590
591 #[test]
592 fn zero_events_is_valid() {
593 let bytes = build_eit(TABLE_ID_PF_ACTUAL, 1, 0, 0x20, 0x30, &[]);
594 let eit = EitSection::parse(&bytes).unwrap();
595 assert_eq!(eit.events.len(), 0);
596 }
597
598 #[test]
599 #[cfg(feature = "chrono")]
600 fn event_start_time_decodes_to_utc_datetime() {
601 let mjd: u16 = 59945;
603 let ev = EitEvent::new(
604 1,
605 [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
606 [0, 0, 0],
607 RunningStatus::Undefined,
608 false,
609 DescriptorLoop::new(&[]),
610 );
611 let dt = ev.start_time_chrono().unwrap();
612 use chrono::Datelike;
613 assert_eq!(dt.year(), 2023);
614 assert_eq!(dt.month(), 1);
615 assert_eq!(dt.day(), 1);
616 use chrono::Timelike;
617 assert_eq!(dt.hour(), 12);
618 assert_eq!(dt.minute(), 34);
619 assert_eq!(dt.second(), 56);
620 }
621
622 #[test]
623 fn event_start_time_decodes_without_chrono() {
624 let mjd: u16 = 59945;
625 let ev = EitEvent::new(
626 1,
627 [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
628 [0, 0, 0],
629 RunningStatus::Undefined,
630 false,
631 DescriptorLoop::new(&[]),
632 );
633 let dt = ev.start_time().unwrap();
634 assert_eq!(dt.year, 2023);
636 assert_eq!(dt.month, 1);
637 assert_eq!(dt.day, 1);
638 assert_eq!(dt.hour, 12);
639 assert_eq!(dt.minute, 34);
640 assert_eq!(dt.second, 56);
641 }
642
643 #[test]
644 fn parse_rejects_wrong_tag() {
645 let bytes = build_eit(0x00, 1, 0, 0x20, 0x30, &[]);
646 let err = EitSection::parse(&bytes).unwrap_err();
647 assert!(matches!(
648 err,
649 Error::UnexpectedTableId { table_id: 0x00, .. }
650 ));
651 }
652
653 #[test]
654 fn parse_rejects_truncated_header() {
655 let bytes = [0x4Eu8, 0xF0, 0x00];
656 let err = EitSection::parse(&bytes).unwrap_err();
657 assert!(matches!(
658 err,
659 Error::BufferTooShort {
660 need: 18,
661 have: 3,
662 ..
663 }
664 ));
665 }
666
667 #[test]
668 fn parse_rejects_event_descriptor_loop_overflow() {
669 let section_length: u16 =
670 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + EVENT_HEADER_LEN + CRC_LEN) as u16;
671 let mut v = Vec::new();
672 v.push(TABLE_ID_PF_ACTUAL);
673 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
674 v.push((section_length & 0xFF) as u8);
675 v.extend_from_slice(&1u16.to_be_bytes());
676 v.push(0xC1);
677 v.push(0);
678 v.push(0);
679 v.extend_from_slice(&0x0020u16.to_be_bytes());
680 v.extend_from_slice(&0x0030u16.to_be_bytes());
681 v.push(0);
682 v.push(TABLE_ID_PF_ACTUAL);
683 v.extend_from_slice(&1u16.to_be_bytes());
684 v.extend_from_slice(&[0u8; 5]);
685 v.extend_from_slice(&[0u8; 3]);
686 v.push(0x00);
687 v.push(0x0A);
688 v.extend_from_slice(&[0u8; 4]);
689 let err = EitSection::parse(&v).unwrap_err();
692 assert!(matches!(
693 err,
694 Error::SectionLengthOverflow { declared: 10, .. }
695 ));
696 }
697
698 #[test]
699 fn structured_fields_segment_and_last_table_id_preserved() {
700 let desc: [u8; 2] = [0x54, 0x00];
701 let bytes = build_eit(
702 TABLE_ID_SCHEDULE_ACTUAL_FIRST,
703 0x0100,
704 7,
705 0x0020,
706 0x0030,
707 &[(
708 42,
709 [0xDF, 0xA1, 0x12, 0x34, 0x56],
710 [0x00, 0x30, 0x00],
711 4,
712 false,
713 desc.to_vec(),
714 )],
715 );
716 let eit = EitSection::parse(&bytes).unwrap();
717 assert_eq!(eit.kind, EitKind::ScheduleActual);
718 assert_eq!(eit.table_id, TABLE_ID_SCHEDULE_ACTUAL_FIRST);
719 assert_eq!(eit.service_id, 0x0100);
720 assert_eq!(eit.version_number, 7);
721 assert!(eit.current_next_indicator);
722 assert_eq!(eit.section_number, 0);
723 assert_eq!(eit.last_section_number, 0);
724 assert_eq!(eit.transport_stream_id, 0x0020);
725 assert_eq!(eit.original_network_id, 0x0030);
726 assert_eq!(eit.segment_last_section_number, 0);
727 assert_eq!(eit.last_table_id, TABLE_ID_SCHEDULE_ACTUAL_FIRST);
728 assert_eq!(eit.events.len(), 1);
729 assert_eq!(eit.events[0].event_id, 42);
730 assert_eq!(eit.events[0].running_status, RunningStatus::Running);
731 assert!(!eit.events[0].free_ca_mode);
732 assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
734 }
735
736 #[test]
737 fn parse_rejects_zero_section_length() {
738 let mut buf = vec![0u8; 64];
739 buf[0] = TABLE_ID_PF_ACTUAL;
740 buf[1] = 0xF0;
741 buf[2] = 0x00;
742 for b in &mut buf[3..] {
743 *b = 0xFF;
744 }
745 assert!(matches!(
746 EitSection::parse(&buf).unwrap_err(),
747 Error::SectionLengthOverflow { .. }
748 ));
749 }
750
751 #[test]
752 fn parse_rejects_trailing_slack_bytes() {
753 let section_length: u16 =
754 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + EVENT_HEADER_LEN + 1 + CRC_LEN) as u16;
755 let mut v = Vec::new();
756 v.push(TABLE_ID_PF_ACTUAL);
757 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
758 v.push((section_length & 0xFF) as u8);
759 v.extend_from_slice(&1u16.to_be_bytes());
760 v.push(0xC1);
761 v.push(0);
762 v.push(0);
763 v.extend_from_slice(&0x0020u16.to_be_bytes());
764 v.extend_from_slice(&0x0030u16.to_be_bytes());
765 v.push(0);
766 v.push(TABLE_ID_PF_ACTUAL);
767 v.extend_from_slice(&1u16.to_be_bytes());
768 v.extend_from_slice(&[0u8; 5]);
769 v.extend_from_slice(&[0u8; 3]);
770 v.push(0x00);
771 v.push(0x00);
772 v.push(0xFF);
773 v.extend_from_slice(&[0u8; 4]);
774 let err = EitSection::parse(&v).unwrap_err();
775 assert!(matches!(
776 err,
777 Error::BufferTooShort {
778 what: "EitSection trailing event bytes",
779 ..
780 }
781 ));
782 }
783}