1use crate::descriptors::DescriptorLoop;
11use crate::error::{Error, Result};
12use crate::traits::Table;
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 EVENT_HEADER_LEN: usize = 12;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize))]
41pub enum EitKind {
42 PresentFollowingActual,
44 PresentFollowingOther,
46 ScheduleActual,
48 ScheduleOther,
50}
51
52impl EitKind {
53 #[must_use]
55 pub fn from_table_id(table_id: u8) -> Option<Self> {
56 match table_id {
57 TABLE_ID_PF_ACTUAL => Some(Self::PresentFollowingActual),
58 TABLE_ID_PF_OTHER => Some(Self::PresentFollowingOther),
59 TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST => {
60 Some(Self::ScheduleActual)
61 }
62 TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST => {
63 Some(Self::ScheduleOther)
64 }
65 _ => None,
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize))]
73#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
74pub struct EitEvent<'a> {
75 pub event_id: u16,
77 pub start_time_raw: [u8; 5],
79 pub duration_raw: [u8; 3],
81 pub running_status: u8,
83 pub free_ca_mode: bool,
85 pub descriptors: DescriptorLoop<'a>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
94pub struct EitSection<'a> {
95 pub kind: EitKind,
97 pub table_id: u8,
99 pub service_id: u16,
101 pub version_number: u8,
103 pub current_next_indicator: bool,
105 pub section_number: u8,
107 pub last_section_number: u8,
109 pub transport_stream_id: u16,
111 pub original_network_id: u16,
113 pub segment_last_section_number: u8,
115 pub last_table_id: u8,
117 pub events: Vec<EitEvent<'a>>,
119}
120
121impl<'a> Parse<'a> for EitSection<'a> {
122 type Error = crate::error::Error;
123 fn parse(bytes: &'a [u8]) -> Result<Self> {
124 let min_len = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + CRC_LEN;
125 if bytes.len() < min_len {
126 return Err(Error::BufferTooShort {
127 need: min_len,
128 have: bytes.len(),
129 what: "EitSection",
130 });
131 }
132
133 let table_id = bytes[0];
134 let kind = EitKind::from_table_id(table_id).ok_or(Error::UnexpectedTableId {
135 table_id,
136 what: "EitSection",
137 expected: &[
138 TABLE_ID_PF_ACTUAL,
139 TABLE_ID_PF_OTHER,
140 TABLE_ID_SCHEDULE_ACTUAL_FIRST,
141 TABLE_ID_SCHEDULE_OTHER_FIRST,
142 ],
143 })?;
144
145 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
146 let total = MIN_HEADER_LEN + section_length as usize;
147 if bytes.len() < total {
148 return Err(Error::SectionLengthOverflow {
149 declared: section_length as usize,
150 available: bytes.len() - MIN_HEADER_LEN,
151 });
152 }
153
154 let service_id = u16::from_be_bytes([bytes[3], bytes[4]]);
155 let version_number = (bytes[5] >> 1) & 0x1F;
156 let current_next_indicator = (bytes[5] & 0x01) != 0;
157 let section_number = bytes[6];
158 let last_section_number = bytes[7];
159
160 let transport_stream_id = u16::from_be_bytes([bytes[8], bytes[9]]);
161 let original_network_id = u16::from_be_bytes([bytes[10], bytes[11]]);
162 let segment_last_section_number = bytes[12];
163 let last_table_id = bytes[13];
164
165 let events_start = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
166 let events_end = total - CRC_LEN;
167 let mut events = Vec::new();
168 let mut pos = events_start;
169 while pos + EVENT_HEADER_LEN <= events_end {
170 let event_id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
171 let start_time_raw = [
172 bytes[pos + 2],
173 bytes[pos + 3],
174 bytes[pos + 4],
175 bytes[pos + 5],
176 bytes[pos + 6],
177 ];
178 let duration_raw = [bytes[pos + 7], bytes[pos + 8], bytes[pos + 9]];
179 let status_and_len_hi = bytes[pos + 10];
180 let running_status = (status_and_len_hi >> 5) & 0x07;
181 let free_ca_mode = (status_and_len_hi & 0x10) != 0;
182 let descriptors_loop_length =
183 (((status_and_len_hi & 0x0F) as usize) << 8) | bytes[pos + 11] as usize;
184 let desc_start = pos + EVENT_HEADER_LEN;
185 let desc_end = desc_start + descriptors_loop_length;
186 if desc_end > events_end {
187 return Err(Error::SectionLengthOverflow {
188 declared: descriptors_loop_length,
189 available: events_end - desc_start,
190 });
191 }
192 events.push(EitEvent {
193 event_id,
194 start_time_raw,
195 duration_raw,
196 running_status,
197 free_ca_mode,
198 descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
199 });
200 pos = desc_end;
201 }
202
203 Ok(EitSection {
204 kind,
205 table_id,
206 service_id,
207 version_number,
208 current_next_indicator,
209 section_number,
210 last_section_number,
211 transport_stream_id,
212 original_network_id,
213 segment_last_section_number,
214 last_table_id,
215 events,
216 })
217 }
218}
219
220impl Serialize for EitSection<'_> {
221 type Error = crate::error::Error;
222 fn serialized_len(&self) -> usize {
223 let ev_bytes: usize = self
224 .events
225 .iter()
226 .map(|e| EVENT_HEADER_LEN + e.descriptors.len())
227 .sum();
228 MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN
229 }
230
231 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
232 let len = self.serialized_len();
233 if buf.len() < len {
234 return Err(Error::OutputBufferTooSmall {
235 need: len,
236 have: buf.len(),
237 });
238 }
239 let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
240 buf[0] = self.table_id;
241 buf[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
242 buf[2] = (section_length & 0xFF) as u8;
243 buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
244 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
245 buf[6] = self.section_number;
246 buf[7] = self.last_section_number;
247 buf[8..10].copy_from_slice(&self.transport_stream_id.to_be_bytes());
248 buf[10..12].copy_from_slice(&self.original_network_id.to_be_bytes());
249 buf[12] = self.segment_last_section_number;
250 buf[13] = self.last_table_id;
251
252 let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
253 for ev in &self.events {
254 buf[pos..pos + 2].copy_from_slice(&ev.event_id.to_be_bytes());
255 buf[pos + 2..pos + 7].copy_from_slice(&ev.start_time_raw);
256 buf[pos + 7..pos + 10].copy_from_slice(&ev.duration_raw);
257 let dll = ev.descriptors.len() as u16;
258 buf[pos + 10] = ((ev.running_status & 0x07) << 5)
259 | (u8::from(ev.free_ca_mode) << 4)
260 | ((dll >> 8) as u8 & 0x0F);
261 buf[pos + 11] = (dll & 0xFF) as u8;
262 let desc_start = pos + EVENT_HEADER_LEN;
263 buf[desc_start..desc_start + ev.descriptors.len()]
264 .copy_from_slice(ev.descriptors.raw());
265 pos = desc_start + ev.descriptors.len();
266 }
267
268 let crc_pos = len - CRC_LEN;
269 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
270 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
271 Ok(len)
272 }
273}
274
275impl<'a> Table<'a> for EitSection<'a> {
276 const TABLE_ID: u8 = TABLE_ID_PF_ACTUAL;
277 const PID: u16 = PID;
278}
279
280impl<'a> crate::traits::TableDef<'a> for EitSection<'a> {
281 const TABLE_ID_RANGES: &'static [(u8, u8)] =
282 &[(TABLE_ID_PF_ACTUAL, TABLE_ID_SCHEDULE_OTHER_LAST)];
283 const NAME: &'static str = "EVENT_INFORMATION";
284}
285
286impl EitEvent<'_> {
287 #[must_use]
292 pub fn duration(&self) -> Option<core::time::Duration> {
293 dvb_common::time::decode_bcd_duration(self.duration_raw)
294 }
295
296 pub fn set_duration(&mut self, duration: core::time::Duration) -> crate::Result<()> {
302 self.duration_raw = dvb_common::time::encode_bcd_duration(duration).ok_or(
303 crate::Error::ValueOutOfRange {
304 field: "EitEvent::duration",
305 reason: "duration must be < 100 hours",
306 },
307 )?;
308 Ok(())
309 }
310}
311
312#[cfg(feature = "chrono")]
313impl EitEvent<'_> {
314 #[must_use]
319 pub fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
320 dvb_common::time::decode_mjd_bcd_utc(self.start_time_raw)
321 }
322
323 pub fn set_start_time(
329 &mut self,
330 start_time: chrono::DateTime<chrono::Utc>,
331 ) -> crate::Result<()> {
332 self.start_time_raw = dvb_common::time::encode_mjd_bcd_utc(start_time).ok_or(
333 crate::Error::ValueOutOfRange {
334 field: "EitEvent::start_time",
335 reason: "date not representable in 16-bit MJD",
336 },
337 )?;
338 Ok(())
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 type TestEvent = (u16, [u8; 5], [u8; 3], u8, bool, Vec<u8>);
347
348 fn build_eit(
349 table_id: u8,
350 service_id: u16,
351 version: u8,
352 tsid: u16,
353 onid: u16,
354 events: &[TestEvent],
355 ) -> Vec<u8> {
356 let ev_bytes: usize = events
357 .iter()
358 .map(|(_, _, _, _, _, d)| EVENT_HEADER_LEN + d.len())
359 .sum();
360 let section_length: u16 =
361 (EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN) as u16;
362 let mut v = Vec::new();
363 v.push(table_id);
364 v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
365 v.push((section_length & 0xFF) as u8);
366 v.extend_from_slice(&service_id.to_be_bytes());
367 v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
368 v.push(0);
369 v.push(0);
370 v.extend_from_slice(&tsid.to_be_bytes());
371 v.extend_from_slice(&onid.to_be_bytes());
372 v.push(0);
373 v.push(table_id);
374 for (eid, start, dur, rs, fca, desc) in events {
375 v.extend_from_slice(&eid.to_be_bytes());
376 v.extend_from_slice(start);
377 v.extend_from_slice(dur);
378 let dll = desc.len() as u16;
379 v.push(((*rs & 0x07) << 5) | (u8::from(*fca) << 4) | ((dll >> 8) as u8 & 0x0F));
380 v.push((dll & 0xFF) as u8);
381 v.extend_from_slice(desc);
382 }
383 v.extend_from_slice(&[0, 0, 0, 0]);
384 v
385 }
386
387 #[test]
388 fn parse_pf_actual_and_other_map_to_correct_kind() {
389 for (tid, expected) in [
390 (TABLE_ID_PF_ACTUAL, EitKind::PresentFollowingActual),
391 (TABLE_ID_PF_OTHER, EitKind::PresentFollowingOther),
392 ] {
393 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
394 assert_eq!(EitSection::parse(&bytes).unwrap().kind, expected);
395 }
396 }
397
398 #[test]
399 fn schedule_tables_0x50_through_0x5f_all_decode_as_schedule_actual() {
400 for tid in TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST {
401 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
402 assert_eq!(
403 EitSection::parse(&bytes).unwrap().kind,
404 EitKind::ScheduleActual
405 );
406 }
407 }
408
409 #[test]
410 fn schedule_tables_0x60_through_0x6f_all_decode_as_schedule_other() {
411 for tid in TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST {
412 let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
413 assert_eq!(
414 EitSection::parse(&bytes).unwrap().kind,
415 EitKind::ScheduleOther
416 );
417 }
418 }
419
420 #[test]
421 fn event_loop_with_descriptor_bytes_preserved() {
422 let desc = vec![0x4D, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
423 let bytes = build_eit(
424 TABLE_ID_PF_ACTUAL,
425 1,
426 0,
427 0x20,
428 0x30,
429 &[(
430 42,
431 [0xDF, 0xA1, 0x12, 0x34, 0x56],
432 [0x00, 0x30, 0x00],
433 4,
434 false,
435 desc.clone(),
436 )],
437 );
438 let eit = EitSection::parse(&bytes).unwrap();
439 assert_eq!(eit.events.len(), 1);
440 assert_eq!(eit.events[0].event_id, 42);
441 assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
442 }
443
444 #[test]
445 fn running_status_extracted() {
446 let bytes = build_eit(
447 TABLE_ID_PF_ACTUAL,
448 1,
449 0,
450 0x20,
451 0x30,
452 &[(1, [0; 5], [0; 3], 2, false, vec![])],
453 );
454 assert_eq!(
455 EitSection::parse(&bytes).unwrap().events[0].running_status,
456 2
457 );
458 }
459
460 #[test]
461 fn free_ca_mode_flag_extracted() {
462 let bytes = build_eit(
463 TABLE_ID_PF_ACTUAL,
464 1,
465 0,
466 0x20,
467 0x30,
468 &[(1, [0; 5], [0; 3], 0, true, vec![])],
469 );
470 assert!(EitSection::parse(&bytes).unwrap().events[0].free_ca_mode);
471 }
472
473 #[test]
474 fn serialize_round_trip_preserves_all_events() {
475 let desc1: [u8; 2] = [0x54, 0x00];
476 let eit = EitSection {
477 kind: EitKind::PresentFollowingActual,
478 table_id: TABLE_ID_PF_ACTUAL,
479 service_id: 0x0100,
480 version_number: 3,
481 current_next_indicator: true,
482 section_number: 0,
483 last_section_number: 0,
484 transport_stream_id: 0x1234,
485 original_network_id: 0x0020,
486 segment_last_section_number: 0,
487 last_table_id: TABLE_ID_PF_ACTUAL,
488 events: vec![
489 EitEvent {
490 event_id: 1,
491 start_time_raw: [0xDF, 0xA1, 0x12, 0x34, 0x56],
492 duration_raw: [0x00, 0x30, 0x00],
493 running_status: 4,
494 free_ca_mode: false,
495 descriptors: DescriptorLoop::new(&desc1),
496 },
497 EitEvent {
498 event_id: 2,
499 start_time_raw: [0xDF, 0xA1, 0x13, 0x00, 0x00],
500 duration_raw: [0x01, 0x00, 0x00],
501 running_status: 1,
502 free_ca_mode: true,
503 descriptors: DescriptorLoop::new(&[]),
504 },
505 ],
506 };
507 let mut buf = vec![0u8; eit.serialized_len()];
508 eit.serialize_into(&mut buf).unwrap();
509 let re = EitSection::parse(&buf).unwrap();
510 assert_eq!(eit, re);
511 }
512
513 #[test]
514 fn zero_events_is_valid() {
515 let bytes = build_eit(TABLE_ID_PF_ACTUAL, 1, 0, 0x20, 0x30, &[]);
516 let eit = EitSection::parse(&bytes).unwrap();
517 assert_eq!(eit.events.len(), 0);
518 }
519
520 #[test]
521 #[cfg(feature = "chrono")]
522 fn event_start_time_decodes_to_utc_datetime() {
523 let mjd: u16 = 59945;
525 let ev = EitEvent {
526 event_id: 1,
527 start_time_raw: [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
528 duration_raw: [0, 0, 0],
529 running_status: 0,
530 free_ca_mode: false,
531 descriptors: DescriptorLoop::new(&[]),
532 };
533 let dt = ev.start_time().unwrap();
534 use chrono::Datelike;
535 assert_eq!(dt.year(), 2023);
536 assert_eq!(dt.month(), 1);
537 assert_eq!(dt.day(), 1);
538 use chrono::Timelike;
539 assert_eq!(dt.hour(), 12);
540 assert_eq!(dt.minute(), 34);
541 assert_eq!(dt.second(), 56);
542 }
543}