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