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