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..].first_chunk::<2>().unwrap());
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..].first_chunk::<2>().unwrap());
165 let original_network_id = u16::from_be_bytes(*bytes[10..].first_chunk::<2>().unwrap());
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 (b2, _) = bytes[pos..]
175 .split_first_chunk::<2>()
176 .ok_or(Error::BufferTooShort {
177 need: pos + 2,
178 have: events_end,
179 what: "EitSection event_id",
180 })?;
181 let event_id = u16::from_be_bytes(*b2);
182 let start_time_raw = [
183 bytes[pos + 2],
184 bytes[pos + 3],
185 bytes[pos + 4],
186 bytes[pos + 5],
187 bytes[pos + 6],
188 ];
189 let duration_raw = [bytes[pos + 7], bytes[pos + 8], bytes[pos + 9]];
190 let status_and_len_hi = bytes[pos + 10];
191 let running_status = RunningStatus::from_u8((status_and_len_hi >> 5) & 0x07);
192 let free_ca_mode = (status_and_len_hi & 0x10) != 0;
193 let descriptors_loop_length =
194 (((status_and_len_hi & 0x0F) as usize) << 8) | bytes[pos + 11] as usize;
195 let desc_start = pos + EVENT_HEADER_LEN;
196 let desc_end = desc_start + descriptors_loop_length;
197 if desc_end > events_end {
198 return Err(Error::SectionLengthOverflow {
199 declared: descriptors_loop_length,
200 available: events_end.saturating_sub(desc_start),
201 });
202 }
203 events.push(EitEvent {
204 event_id,
205 start_time_raw,
206 duration_raw,
207 running_status,
208 free_ca_mode,
209 descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
210 });
211 pos = desc_end;
212 }
213
214 if pos != events_end {
215 return Err(Error::BufferTooShort {
216 need: events_end - pos,
217 have: 0,
218 what: "EitSection trailing event bytes",
219 });
220 }
221
222 Ok(EitSection {
223 kind,
224 table_id,
225 service_id,
226 version_number,
227 current_next_indicator,
228 section_number,
229 last_section_number,
230 transport_stream_id,
231 original_network_id,
232 segment_last_section_number,
233 last_table_id,
234 events,
235 })
236 }
237}
238
239impl Serialize for EitSection<'_> {
240 type Error = crate::error::Error;
241 fn serialized_len(&self) -> usize {
242 let ev_bytes: usize = self
243 .events
244 .iter()
245 .map(|e| EVENT_HEADER_LEN + e.descriptors.len())
246 .sum();
247 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN
248 }
249
250 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
251 let len = self.serialized_len();
252 if buf.len() < len {
253 return Err(Error::OutputBufferTooSmall {
254 need: len,
255 have: buf.len(),
256 });
257 }
258 let section_length_usize = len - MIN_HEADER_LEN;
259 if section_length_usize > 0x0FFF {
260 return Err(Error::SectionLengthOverflow {
261 declared: section_length_usize,
262 available: 0x0FFF,
263 });
264 }
265 let section_length: u16 = section_length_usize as u16;
266 buf[0] = self.table_id;
267 buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
268 buf[2] = (section_length & 0xFF) as u8;
269 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
270 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
271 buf[6] = self.section_number;
272 buf[7] = self.last_section_number;
273 buf[8..10].copy_from_slice(&self.transport_stream_id.to_be_bytes());
274 buf[10..12].copy_from_slice(&self.original_network_id.to_be_bytes());
275 buf[12] = self.segment_last_section_number;
276 buf[13] = self.last_table_id;
277
278 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
279 for ev in &self.events {
280 buf[pos..pos + 2].copy_from_slice(&ev.event_id.to_be_bytes());
281 buf[pos + 2..pos + 7].copy_from_slice(&ev.start_time_raw);
282 buf[pos + 7..pos + 10].copy_from_slice(&ev.duration_raw);
283 let dll = ev.descriptors.len() as u16;
284 buf[pos + 10] = (ev.running_status.to_u8() << 5)
285 | (u8::from(ev.free_ca_mode) << 4)
286 | ((dll >> 8) as u8 & 0x0F);
287 buf[pos + 11] = (dll & 0xFF) as u8;
288 let desc_start = pos + EVENT_HEADER_LEN;
289 buf[desc_start..desc_start + ev.descriptors.len()]
290 .copy_from_slice(ev.descriptors.raw());
291 pos = desc_start + ev.descriptors.len();
292 }
293
294 let crc_pos = len - CRC_LEN;
295 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
296 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
297 Ok(len)
298 }
299}
300impl<'a> crate::traits::TableDef<'a> for EitSection<'a> {
301 const TABLE_ID_RANGES: &'static [(u8, u8)] =
302 &[(TABLE_ID_PF_ACTUAL, TABLE_ID_SCHEDULE_OTHER_LAST)];
303 const NAME: &'static str = "EVENT_INFORMATION";
304}
305
306impl<'a> EitEvent<'a> {
307 #[must_use]
314 pub fn start_time(&self) -> Option<dvb_common::time::MjdBcdDateTime> {
315 dvb_common::time::decode_mjd_bcd(self.start_time_raw)
316 }
317
318 pub fn set_start_time_decoded(
324 &mut self,
325 dt: dvb_common::time::MjdBcdDateTime,
326 ) -> crate::Result<()> {
327 self.start_time_raw =
328 dvb_common::time::encode_mjd_bcd(dt).ok_or(crate::Error::ValueOutOfRange {
329 field: "EitEvent::start_time",
330 reason: "date not representable in 16-bit MJD",
331 })?;
332 Ok(())
333 }
334
335 #[must_use]
340 pub fn duration(&self) -> Option<core::time::Duration> {
341 dvb_common::time::decode_bcd_duration(self.duration_raw)
342 }
343
344 pub fn set_duration(&mut self, duration: core::time::Duration) -> crate::Result<()> {
350 self.duration_raw = dvb_common::time::encode_bcd_duration(duration).ok_or(
351 crate::Error::ValueOutOfRange {
352 field: "EitEvent::duration",
353 reason: "duration must be < 100 hours",
354 },
355 )?;
356 Ok(())
357 }
358
359 #[must_use]
361 pub fn start_time_raw(&self) -> [u8; 5] {
362 self.start_time_raw
363 }
364
365 #[must_use]
367 pub fn duration_raw(&self) -> [u8; 3] {
368 self.duration_raw
369 }
370
371 #[allow(clippy::too_many_arguments)]
377 #[must_use]
378 pub fn new(
379 event_id: u16,
380 start_time_raw: [u8; 5],
381 duration_raw: [u8; 3],
382 running_status: RunningStatus,
383 free_ca_mode: bool,
384 descriptors: DescriptorLoop<'a>,
385 ) -> Self {
386 Self {
387 event_id,
388 start_time_raw,
389 duration_raw,
390 running_status,
391 free_ca_mode,
392 descriptors,
393 }
394 }
395}
396
397#[cfg(feature = "chrono")]
398impl EitEvent<'_> {
399 #[must_use]
404 pub fn start_time_chrono(&self) -> Option<chrono::DateTime<chrono::Utc>> {
405 dvb_common::time::decode_mjd_bcd_utc(self.start_time_raw)
406 }
407
408 pub fn set_start_time(
414 &mut self,
415 start_time: chrono::DateTime<chrono::Utc>,
416 ) -> crate::Result<()> {
417 self.start_time_raw = dvb_common::time::encode_mjd_bcd_utc(start_time).ok_or(
418 crate::Error::ValueOutOfRange {
419 field: "EitEvent::start_time",
420 reason: "date not representable in 16-bit MJD",
421 },
422 )?;
423 Ok(())
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 type TestEvent = (u16, [u8; 5], [u8; 3], u8, bool, Vec<u8>);
432
433 fn build_eit(
434 table_id: u8,
435 service_id: u16,
436 version: u8,
437 tsid: u16,
438 onid: u16,
439 events: &[TestEvent],
440 ) -> Vec<u8> {
441 let ev_bytes: usize = events
442 .iter()
443 .map(|(_, _, _, _, _, d)| EVENT_HEADER_LEN + d.len())
444 .sum();
445 let section_length: u16 =
446 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN) as u16;
447 let mut v = Vec::new();
448 v.push(table_id);
449 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
450 v.push((section_length & 0xFF) as u8);
451 v.extend_from_slice(&service_id.to_be_bytes());
452 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
453 v.push(0);
454 v.push(0);
455 v.extend_from_slice(&tsid.to_be_bytes());
456 v.extend_from_slice(&onid.to_be_bytes());
457 v.push(0);
458 v.push(table_id);
459 for (eid, start, dur, rs, fca, desc) in events {
460 v.extend_from_slice(&eid.to_be_bytes());
461 v.extend_from_slice(start);
462 v.extend_from_slice(dur);
463 let dll = desc.len() as u16;
464 v.push(((*rs & 0x07) << 5) | (u8::from(*fca) << 4) | ((dll >> 8) as u8 & 0x0F));
465 v.push((dll & 0xFF) as u8);
466 v.extend_from_slice(desc);
467 }
468 v.extend_from_slice(&[0, 0, 0, 0]);
469 v
470 }
471
472 #[test]
473 fn parse_pf_actual_and_other_map_to_correct_kind() {
474 for (tid, expected) in [
475 (TABLE_ID_PF_ACTUAL, EitKind::PresentFollowingActual),
476 (TABLE_ID_PF_OTHER, EitKind::PresentFollowingOther),
477 ] {
478 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
479 assert_eq!(EitSection::parse(&bytes).unwrap().kind, expected);
480 }
481 }
482
483 #[test]
484 fn schedule_tables_0x50_through_0x5f_all_decode_as_schedule_actual() {
485 for tid in TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST {
486 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
487 assert_eq!(
488 EitSection::parse(&bytes).unwrap().kind,
489 EitKind::ScheduleActual
490 );
491 }
492 }
493
494 #[test]
495 fn schedule_tables_0x60_through_0x6f_all_decode_as_schedule_other() {
496 for tid in TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST {
497 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
498 assert_eq!(
499 EitSection::parse(&bytes).unwrap().kind,
500 EitKind::ScheduleOther
501 );
502 }
503 }
504
505 #[test]
506 fn event_loop_with_descriptor_bytes_preserved() {
507 let desc = vec![0x4D, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
508 let bytes = build_eit(
509 TABLE_ID_PF_ACTUAL,
510 1,
511 0,
512 0x20,
513 0x30,
514 &[(
515 42,
516 [0xDF, 0xA1, 0x12, 0x34, 0x56],
517 [0x00, 0x30, 0x00],
518 4,
519 false,
520 desc.clone(),
521 )],
522 );
523 let eit = EitSection::parse(&bytes).unwrap();
524 assert_eq!(eit.events.len(), 1);
525 assert_eq!(eit.events[0].event_id, 42);
526 assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
527 }
528
529 #[test]
530 fn running_status_extracted() {
531 let bytes = build_eit(
532 TABLE_ID_PF_ACTUAL,
533 1,
534 0,
535 0x20,
536 0x30,
537 &[(1, [0; 5], [0; 3], 2, false, vec![])],
538 );
539 assert_eq!(
540 EitSection::parse(&bytes).unwrap().events[0].running_status,
541 RunningStatus::StartsInAFewSeconds
542 );
543 }
544
545 #[test]
546 fn free_ca_mode_flag_extracted() {
547 let bytes = build_eit(
548 TABLE_ID_PF_ACTUAL,
549 1,
550 0,
551 0x20,
552 0x30,
553 &[(1, [0; 5], [0; 3], 0, true, vec![])],
554 );
555 assert!(EitSection::parse(&bytes).unwrap().events[0].free_ca_mode);
556 }
557
558 #[test]
559 fn serialize_round_trip_preserves_all_events() {
560 let desc1: [u8; 2] = [0x54, 0x00];
561 let eit = EitSection {
562 kind: EitKind::PresentFollowingActual,
563 table_id: TABLE_ID_PF_ACTUAL,
564 service_id: 0x0100,
565 version_number: 3,
566 current_next_indicator: true,
567 section_number: 0,
568 last_section_number: 0,
569 transport_stream_id: 0x1234,
570 original_network_id: 0x0020,
571 segment_last_section_number: 0,
572 last_table_id: TABLE_ID_PF_ACTUAL,
573 events: vec![
574 EitEvent::new(
575 1,
576 [0xDF, 0xA1, 0x12, 0x34, 0x56],
577 [0x00, 0x30, 0x00],
578 RunningStatus::Running,
579 false,
580 DescriptorLoop::new(&desc1),
581 ),
582 EitEvent::new(
583 2,
584 [0xDF, 0xA1, 0x13, 0x00, 0x00],
585 [0x01, 0x00, 0x00],
586 RunningStatus::NotRunning,
587 true,
588 DescriptorLoop::new(&[]),
589 ),
590 ],
591 };
592 let mut buf = vec![0u8; eit.serialized_len()];
593 eit.serialize_into(&mut buf).unwrap();
594 let re = EitSection::parse(&buf).unwrap();
595 assert_eq!(eit, re);
596 }
597
598 #[test]
599 fn zero_events_is_valid() {
600 let bytes = build_eit(TABLE_ID_PF_ACTUAL, 1, 0, 0x20, 0x30, &[]);
601 let eit = EitSection::parse(&bytes).unwrap();
602 assert_eq!(eit.events.len(), 0);
603 }
604
605 #[test]
606 #[cfg(feature = "chrono")]
607 fn event_start_time_decodes_to_utc_datetime() {
608 let mjd: u16 = 59945;
610 let ev = EitEvent::new(
611 1,
612 [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
613 [0, 0, 0],
614 RunningStatus::Undefined,
615 false,
616 DescriptorLoop::new(&[]),
617 );
618 let dt = ev.start_time_chrono().unwrap();
619 use chrono::Datelike;
620 assert_eq!(dt.year(), 2023);
621 assert_eq!(dt.month(), 1);
622 assert_eq!(dt.day(), 1);
623 use chrono::Timelike;
624 assert_eq!(dt.hour(), 12);
625 assert_eq!(dt.minute(), 34);
626 assert_eq!(dt.second(), 56);
627 }
628
629 #[test]
630 fn event_start_time_decodes_without_chrono() {
631 let mjd: u16 = 59945;
632 let ev = EitEvent::new(
633 1,
634 [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
635 [0, 0, 0],
636 RunningStatus::Undefined,
637 false,
638 DescriptorLoop::new(&[]),
639 );
640 let dt = ev.start_time().unwrap();
641 assert_eq!(dt.year, 2023);
643 assert_eq!(dt.month, 1);
644 assert_eq!(dt.day, 1);
645 assert_eq!(dt.hour, 12);
646 assert_eq!(dt.minute, 34);
647 assert_eq!(dt.second, 56);
648 }
649
650 #[test]
651 fn parse_rejects_wrong_tag() {
652 let bytes = build_eit(0x00, 1, 0, 0x20, 0x30, &[]);
653 let err = EitSection::parse(&bytes).unwrap_err();
654 assert!(matches!(
655 err,
656 Error::UnexpectedTableId { table_id: 0x00, .. }
657 ));
658 }
659
660 #[test]
661 fn parse_rejects_truncated_header() {
662 let bytes = [0x4Eu8, 0xF0, 0x00];
663 let err = EitSection::parse(&bytes).unwrap_err();
664 assert!(matches!(
665 err,
666 Error::BufferTooShort {
667 need: 18,
668 have: 3,
669 ..
670 }
671 ));
672 }
673
674 #[test]
675 fn parse_rejects_event_descriptor_loop_overflow() {
676 let section_length: u16 =
677 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + EVENT_HEADER_LEN + CRC_LEN) as u16;
678 let mut v = Vec::new();
679 v.push(TABLE_ID_PF_ACTUAL);
680 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
681 v.push((section_length & 0xFF) as u8);
682 v.extend_from_slice(&1u16.to_be_bytes());
683 v.push(0xC1);
684 v.push(0);
685 v.push(0);
686 v.extend_from_slice(&0x0020u16.to_be_bytes());
687 v.extend_from_slice(&0x0030u16.to_be_bytes());
688 v.push(0);
689 v.push(TABLE_ID_PF_ACTUAL);
690 v.extend_from_slice(&1u16.to_be_bytes());
691 v.extend_from_slice(&[0u8; 5]);
692 v.extend_from_slice(&[0u8; 3]);
693 v.push(0x00);
694 v.push(0x0A);
695 v.extend_from_slice(&[0u8; 4]);
696 let err = EitSection::parse(&v).unwrap_err();
699 assert!(matches!(
700 err,
701 Error::SectionLengthOverflow { declared: 10, .. }
702 ));
703 }
704
705 #[test]
706 fn structured_fields_segment_and_last_table_id_preserved() {
707 let desc: [u8; 2] = [0x54, 0x00];
708 let bytes = build_eit(
709 TABLE_ID_SCHEDULE_ACTUAL_FIRST,
710 0x0100,
711 7,
712 0x0020,
713 0x0030,
714 &[(
715 42,
716 [0xDF, 0xA1, 0x12, 0x34, 0x56],
717 [0x00, 0x30, 0x00],
718 4,
719 false,
720 desc.to_vec(),
721 )],
722 );
723 let eit = EitSection::parse(&bytes).unwrap();
724 assert_eq!(eit.kind, EitKind::ScheduleActual);
725 assert_eq!(eit.table_id, TABLE_ID_SCHEDULE_ACTUAL_FIRST);
726 assert_eq!(eit.service_id, 0x0100);
727 assert_eq!(eit.version_number, 7);
728 assert!(eit.current_next_indicator);
729 assert_eq!(eit.section_number, 0);
730 assert_eq!(eit.last_section_number, 0);
731 assert_eq!(eit.transport_stream_id, 0x0020);
732 assert_eq!(eit.original_network_id, 0x0030);
733 assert_eq!(eit.segment_last_section_number, 0);
734 assert_eq!(eit.last_table_id, TABLE_ID_SCHEDULE_ACTUAL_FIRST);
735 assert_eq!(eit.events.len(), 1);
736 assert_eq!(eit.events[0].event_id, 42);
737 assert_eq!(eit.events[0].running_status, RunningStatus::Running);
738 assert!(!eit.events[0].free_ca_mode);
739 assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
741 }
742
743 #[test]
744 fn parse_rejects_zero_section_length() {
745 let mut buf = vec![0u8; 64];
746 buf[0] = TABLE_ID_PF_ACTUAL;
747 buf[1] = 0xF0;
748 buf[2] = 0x00;
749 for b in &mut buf[3..] {
750 *b = 0xFF;
751 }
752 assert!(matches!(
753 EitSection::parse(&buf).unwrap_err(),
754 Error::SectionLengthOverflow { .. }
755 ));
756 }
757
758 #[test]
759 fn parse_rejects_trailing_slack_bytes() {
760 let section_length: u16 =
761 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + EVENT_HEADER_LEN + 1 + CRC_LEN) as u16;
762 let mut v = Vec::new();
763 v.push(TABLE_ID_PF_ACTUAL);
764 v.push(super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F));
765 v.push((section_length & 0xFF) as u8);
766 v.extend_from_slice(&1u16.to_be_bytes());
767 v.push(0xC1);
768 v.push(0);
769 v.push(0);
770 v.extend_from_slice(&0x0020u16.to_be_bytes());
771 v.extend_from_slice(&0x0030u16.to_be_bytes());
772 v.push(0);
773 v.push(TABLE_ID_PF_ACTUAL);
774 v.extend_from_slice(&1u16.to_be_bytes());
775 v.extend_from_slice(&[0u8; 5]);
776 v.extend_from_slice(&[0u8; 3]);
777 v.push(0x00);
778 v.push(0x00);
779 v.push(0xFF);
780 v.extend_from_slice(&[0u8; 4]);
781 let err = EitSection::parse(&v).unwrap_err();
782 assert!(matches!(
783 err,
784 Error::BufferTooShort {
785 what: "EitSection trailing event bytes",
786 ..
787 }
788 ));
789 }
790}