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