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