use crate::descriptors::DescriptorLoop;
use crate::error::{Error, Result};
use crate::traits::Table;
use dvb_common::{Parse, Serialize};
pub const TABLE_ID_PF_ACTUAL: u8 = 0x4E;
pub const TABLE_ID_PF_OTHER: u8 = 0x4F;
pub const TABLE_ID_SCHEDULE_ACTUAL_FIRST: u8 = 0x50;
pub const TABLE_ID_SCHEDULE_ACTUAL_LAST: u8 = 0x5F;
pub const TABLE_ID_SCHEDULE_OTHER_FIRST: u8 = 0x60;
pub const TABLE_ID_SCHEDULE_OTHER_LAST: u8 = 0x6F;
pub const PID: u16 = 0x0012;
const MIN_HEADER_LEN: usize = 3;
const EXTENSION_HEADER_LEN: usize = 5;
const POST_EXTENSION_LEN: usize = 6;
const CRC_LEN: usize = 4;
const EVENT_HEADER_LEN: usize = 12;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum EitKind {
PresentFollowingActual,
PresentFollowingOther,
ScheduleActual,
ScheduleOther,
}
impl EitKind {
#[must_use]
pub fn from_table_id(table_id: u8) -> Option<Self> {
match table_id {
TABLE_ID_PF_ACTUAL => Some(Self::PresentFollowingActual),
TABLE_ID_PF_OTHER => Some(Self::PresentFollowingOther),
TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST => {
Some(Self::ScheduleActual)
}
TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST => {
Some(Self::ScheduleOther)
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct EitEvent<'a> {
pub event_id: u16,
pub start_time_raw: [u8; 5],
pub duration_raw: [u8; 3],
pub running_status: u8,
pub free_ca_mode: bool,
pub descriptors: DescriptorLoop<'a>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
pub struct Eit<'a> {
pub kind: EitKind,
pub table_id: u8,
pub service_id: u16,
pub version_number: u8,
pub current_next_indicator: bool,
pub section_number: u8,
pub last_section_number: u8,
pub transport_stream_id: u16,
pub original_network_id: u16,
pub segment_last_section_number: u8,
pub last_table_id: u8,
pub events: Vec<EitEvent<'a>>,
}
impl<'a> Parse<'a> for Eit<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let min_len = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + CRC_LEN;
if bytes.len() < min_len {
return Err(Error::BufferTooShort {
need: min_len,
have: bytes.len(),
what: "Eit",
});
}
let table_id = bytes[0];
let kind = EitKind::from_table_id(table_id).ok_or(Error::UnexpectedTableId {
table_id,
what: "Eit",
expected: &[
TABLE_ID_PF_ACTUAL,
TABLE_ID_PF_OTHER,
TABLE_ID_SCHEDULE_ACTUAL_FIRST,
TABLE_ID_SCHEDULE_OTHER_FIRST,
],
})?;
let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
let total = MIN_HEADER_LEN + section_length as usize;
if bytes.len() < total {
return Err(Error::SectionLengthOverflow {
declared: section_length as usize,
available: bytes.len() - MIN_HEADER_LEN,
});
}
let service_id = u16::from_be_bytes([bytes[3], bytes[4]]);
let version_number = (bytes[5] >> 1) & 0x1F;
let current_next_indicator = (bytes[5] & 0x01) != 0;
let section_number = bytes[6];
let last_section_number = bytes[7];
let transport_stream_id = u16::from_be_bytes([bytes[8], bytes[9]]);
let original_network_id = u16::from_be_bytes([bytes[10], bytes[11]]);
let segment_last_section_number = bytes[12];
let last_table_id = bytes[13];
let events_start = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
let events_end = total - CRC_LEN;
let mut events = Vec::new();
let mut pos = events_start;
while pos + EVENT_HEADER_LEN <= events_end {
let event_id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
let start_time_raw = [
bytes[pos + 2],
bytes[pos + 3],
bytes[pos + 4],
bytes[pos + 5],
bytes[pos + 6],
];
let duration_raw = [bytes[pos + 7], bytes[pos + 8], bytes[pos + 9]];
let status_and_len_hi = bytes[pos + 10];
let running_status = (status_and_len_hi >> 5) & 0x07;
let free_ca_mode = (status_and_len_hi & 0x10) != 0;
let descriptors_loop_length =
(((status_and_len_hi & 0x0F) as usize) << 8) | bytes[pos + 11] as usize;
let desc_start = pos + EVENT_HEADER_LEN;
let desc_end = desc_start + descriptors_loop_length;
if desc_end > events_end {
return Err(Error::SectionLengthOverflow {
declared: descriptors_loop_length,
available: events_end - desc_start,
});
}
events.push(EitEvent {
event_id,
start_time_raw,
duration_raw,
running_status,
free_ca_mode,
descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
});
pos = desc_end;
}
Ok(Eit {
kind,
table_id,
service_id,
version_number,
current_next_indicator,
section_number,
last_section_number,
transport_stream_id,
original_network_id,
segment_last_section_number,
last_table_id,
events,
})
}
}
impl Serialize for Eit<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let ev_bytes: usize = self
.events
.iter()
.map(|e| EVENT_HEADER_LEN + e.descriptors.len())
.sum();
MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
let section_length: u16 = (len - MIN_HEADER_LEN) as u16;
buf[0] = self.table_id;
buf[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
buf[2] = (section_length & 0xFF) as u8;
buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
buf[6] = self.section_number;
buf[7] = self.last_section_number;
buf[8..10].copy_from_slice(&self.transport_stream_id.to_be_bytes());
buf[10..12].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[12] = self.segment_last_section_number;
buf[13] = self.last_table_id;
let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
for ev in &self.events {
buf[pos..pos + 2].copy_from_slice(&ev.event_id.to_be_bytes());
buf[pos + 2..pos + 7].copy_from_slice(&ev.start_time_raw);
buf[pos + 7..pos + 10].copy_from_slice(&ev.duration_raw);
let dll = ev.descriptors.len() as u16;
buf[pos + 10] = ((ev.running_status & 0x07) << 5)
| (u8::from(ev.free_ca_mode) << 4)
| ((dll >> 8) as u8 & 0x0F);
buf[pos + 11] = (dll & 0xFF) as u8;
let desc_start = pos + EVENT_HEADER_LEN;
buf[desc_start..desc_start + ev.descriptors.len()]
.copy_from_slice(ev.descriptors.raw());
pos = desc_start + ev.descriptors.len();
}
let crc_pos = len - CRC_LEN;
let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
Ok(len)
}
}
impl<'a> Table<'a> for Eit<'a> {
const TABLE_ID: u8 = TABLE_ID_PF_ACTUAL;
const PID: u16 = PID;
}
impl<'a> crate::traits::TableDef<'a> for Eit<'a> {
const TABLE_ID_RANGES: &'static [(u8, u8)] =
&[(TABLE_ID_PF_ACTUAL, TABLE_ID_SCHEDULE_OTHER_LAST)];
const NAME: &'static str = "EVENT_INFORMATION";
}
#[cfg(feature = "chrono")]
impl EitEvent<'_> {
#[must_use]
pub fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
use chrono::{NaiveDate, NaiveDateTime, TimeZone};
let mjd = u16::from_be_bytes([self.start_time_raw[0], self.start_time_raw[1]]);
let (y, m, d) = mjd_to_ymd(mjd);
let h = bcd_byte(self.start_time_raw[2])?;
let mi = bcd_byte(self.start_time_raw[3])?;
let s = bcd_byte(self.start_time_raw[4])?;
let date = NaiveDate::from_ymd_opt(y, m, d)?;
let time = chrono::NaiveTime::from_hms_opt(u32::from(h), u32::from(mi), u32::from(s))?;
let naive = NaiveDateTime::new(date, time);
chrono::Utc.from_local_datetime(&naive).single()
}
}
#[cfg(feature = "chrono")]
fn bcd_byte(b: u8) -> Option<u8> {
let hi = b >> 4;
let lo = b & 0x0F;
if hi > 9 || lo > 9 {
return None;
}
Some(hi * 10 + lo)
}
#[cfg(feature = "chrono")]
fn mjd_to_ymd(mjd: u16) -> (i32, u32, u32) {
let mjd = i64::from(mjd);
let y_prime = ((mjd as f64 - 15_078.2) / 365.25) as i64;
let m_prime = ((mjd as f64 - 14_956.1 - (y_prime as f64 * 365.25).floor()) / 30.6001) as i64;
let d = mjd
- 14_956
- (y_prime as f64 * 365.25).floor() as i64
- (m_prime as f64 * 30.6001).floor() as i64;
let k = if m_prime == 14 || m_prime == 15 { 1 } else { 0 };
let y = y_prime + k + 1900;
let m = m_prime - 1 - k * 12;
(y as i32, m as u32, d as u32)
}
#[cfg(test)]
mod tests {
use super::*;
type TestEvent = (u16, [u8; 5], [u8; 3], u8, bool, Vec<u8>);
fn build_eit(
table_id: u8,
service_id: u16,
version: u8,
tsid: u16,
onid: u16,
events: &[TestEvent],
) -> Vec<u8> {
let ev_bytes: usize = events
.iter()
.map(|(_, _, _, _, _, d)| EVENT_HEADER_LEN + d.len())
.sum();
let section_length: u16 =
(EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + ev_bytes + CRC_LEN) as u16;
let mut v = Vec::new();
v.push(table_id);
v.push(0xB0 | ((section_length >> 8) as u8 & 0x0F));
v.push((section_length & 0xFF) as u8);
v.extend_from_slice(&service_id.to_be_bytes());
v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
v.push(0);
v.push(0);
v.extend_from_slice(&tsid.to_be_bytes());
v.extend_from_slice(&onid.to_be_bytes());
v.push(0);
v.push(table_id);
for (eid, start, dur, rs, fca, desc) in events {
v.extend_from_slice(&eid.to_be_bytes());
v.extend_from_slice(start);
v.extend_from_slice(dur);
let dll = desc.len() as u16;
v.push(((*rs & 0x07) << 5) | (u8::from(*fca) << 4) | ((dll >> 8) as u8 & 0x0F));
v.push((dll & 0xFF) as u8);
v.extend_from_slice(desc);
}
v.extend_from_slice(&[0, 0, 0, 0]);
v
}
#[test]
fn parse_pf_actual_and_other_map_to_correct_kind() {
for (tid, expected) in [
(TABLE_ID_PF_ACTUAL, EitKind::PresentFollowingActual),
(TABLE_ID_PF_OTHER, EitKind::PresentFollowingOther),
] {
let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
assert_eq!(Eit::parse(&bytes).unwrap().kind, expected);
}
}
#[test]
fn schedule_tables_0x50_through_0x5f_all_decode_as_schedule_actual() {
for tid in TABLE_ID_SCHEDULE_ACTUAL_FIRST..=TABLE_ID_SCHEDULE_ACTUAL_LAST {
let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
assert_eq!(Eit::parse(&bytes).unwrap().kind, EitKind::ScheduleActual);
}
}
#[test]
fn schedule_tables_0x60_through_0x6f_all_decode_as_schedule_other() {
for tid in TABLE_ID_SCHEDULE_OTHER_FIRST..=TABLE_ID_SCHEDULE_OTHER_LAST {
let bytes = build_eit(tid, 1, 0, 0x20, 0x30, &[]);
assert_eq!(Eit::parse(&bytes).unwrap().kind, EitKind::ScheduleOther);
}
}
#[test]
fn event_loop_with_descriptor_bytes_preserved() {
let desc = vec![0x4D, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05];
let bytes = build_eit(
TABLE_ID_PF_ACTUAL,
1,
0,
0x20,
0x30,
&[(
42,
[0xDF, 0xA1, 0x12, 0x34, 0x56],
[0x00, 0x30, 0x00],
4,
false,
desc.clone(),
)],
);
let eit = Eit::parse(&bytes).unwrap();
assert_eq!(eit.events.len(), 1);
assert_eq!(eit.events[0].event_id, 42);
assert_eq!(eit.events[0].descriptors.raw(), &desc[..]);
}
#[test]
fn running_status_extracted() {
let bytes = build_eit(
TABLE_ID_PF_ACTUAL,
1,
0,
0x20,
0x30,
&[(1, [0; 5], [0; 3], 2, false, vec![])],
);
assert_eq!(Eit::parse(&bytes).unwrap().events[0].running_status, 2);
}
#[test]
fn free_ca_mode_flag_extracted() {
let bytes = build_eit(
TABLE_ID_PF_ACTUAL,
1,
0,
0x20,
0x30,
&[(1, [0; 5], [0; 3], 0, true, vec![])],
);
assert!(Eit::parse(&bytes).unwrap().events[0].free_ca_mode);
}
#[test]
fn serialize_round_trip_preserves_all_events() {
let desc1: [u8; 2] = [0x54, 0x00];
let eit = Eit {
kind: EitKind::PresentFollowingActual,
table_id: TABLE_ID_PF_ACTUAL,
service_id: 0x0100,
version_number: 3,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
transport_stream_id: 0x1234,
original_network_id: 0x0020,
segment_last_section_number: 0,
last_table_id: TABLE_ID_PF_ACTUAL,
events: vec![
EitEvent {
event_id: 1,
start_time_raw: [0xDF, 0xA1, 0x12, 0x34, 0x56],
duration_raw: [0x00, 0x30, 0x00],
running_status: 4,
free_ca_mode: false,
descriptors: DescriptorLoop::new(&desc1),
},
EitEvent {
event_id: 2,
start_time_raw: [0xDF, 0xA1, 0x13, 0x00, 0x00],
duration_raw: [0x01, 0x00, 0x00],
running_status: 1,
free_ca_mode: true,
descriptors: DescriptorLoop::new(&[]),
},
],
};
let mut buf = vec![0u8; eit.serialized_len()];
eit.serialize_into(&mut buf).unwrap();
let re = Eit::parse(&buf).unwrap();
assert_eq!(eit, re);
}
#[test]
fn zero_events_is_valid() {
let bytes = build_eit(TABLE_ID_PF_ACTUAL, 1, 0, 0x20, 0x30, &[]);
let eit = Eit::parse(&bytes).unwrap();
assert_eq!(eit.events.len(), 0);
}
#[test]
#[cfg(feature = "chrono")]
fn event_start_time_decodes_to_utc_datetime() {
let mjd: u16 = 59945;
let ev = EitEvent {
event_id: 1,
start_time_raw: [(mjd >> 8) as u8, (mjd & 0xFF) as u8, 0x12, 0x34, 0x56],
duration_raw: [0, 0, 0],
running_status: 0,
free_ca_mode: false,
descriptors: DescriptorLoop::new(&[]),
};
let dt = ev.start_time().unwrap();
use chrono::Datelike;
assert_eq!(dt.year(), 2023);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
use chrono::Timelike;
assert_eq!(dt.hour(), 12);
assert_eq!(dt.minute(), 34);
assert_eq!(dt.second(), 56);
}
}