use super::service_gateway::ServiceGatewayApdu;
use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use alloc::vec::Vec;
use dvb_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const EIT_SECTION_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x10);
pub const EIT_SECTION_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x11);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum EitResponseCode {
NotOnPresentDocument,
NotAvailable,
SectionFound,
Reserved,
}
impl EitResponseCode {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v & 0x03 {
0b00 => Self::NotOnPresentDocument,
0b01 => Self::NotAvailable,
0b10 => Self::SectionFound,
_ => Self::Reserved,
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::NotOnPresentDocument => 0b00,
Self::NotAvailable => 0b01,
Self::SectionFound => 0b10,
Self::Reserved => 0b11,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::NotOnPresentDocument => "Section not on the present document",
Self::NotAvailable => "Section not available",
Self::SectionFound => "Section found",
Self::Reserved => "reserved",
}
}
}
dvb_common::impl_spec_display!(EitResponseCode);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EitSectionReq {
pub table_id: u16,
pub service_id: u16,
pub section_number: u8,
pub original_network_id: u16,
pub ok_to_disrupt_service: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EitEvent<'a> {
pub event_id: u16,
pub start_time: [u8; 5],
pub duration: [u8; 3],
pub running_status: u8,
pub free_ca_mode: bool,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub descriptors: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EitSectionAck<'a> {
pub response_code: EitResponseCode,
#[cfg_attr(feature = "serde", serde(borrow))]
pub events: Vec<EitEvent<'a>>,
}
const EIT_SECTION_REQ_BODY: usize = 2 + 2 + 1 + 2 + 1;
impl<'a> Parse<'a> for EitSectionReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::EIT_SECTION_REQ, "EITSectionReq")?;
if body.len() < EIT_SECTION_REQ_BODY {
return Err(Error::BufferTooShort {
need: EIT_SECTION_REQ_BODY,
have: body.len(),
what: "EITSectionReq",
});
}
Ok(Self {
table_id: u16::from_be_bytes([body[0], body[1]]),
service_id: u16::from_be_bytes([body[2], body[3]]),
section_number: body[4],
original_network_id: u16::from_be_bytes([body[5], body[6]]),
ok_to_disrupt_service: (body[7] & 0x01) != 0,
})
}
}
impl Serialize for EitSectionReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(EIT_SECTION_REQ_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::EIT_SECTION_REQ, EIT_SECTION_REQ_BODY, buf)?;
buf[pos..pos + 2].copy_from_slice(&self.table_id.to_be_bytes());
buf[pos + 2..pos + 4].copy_from_slice(&self.service_id.to_be_bytes());
buf[pos + 4] = self.section_number;
buf[pos + 5..pos + 7].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[pos + 7] = u8::from(self.ok_to_disrupt_service);
Ok(pos + EIT_SECTION_REQ_BODY)
}
}
const EIT_SECTION_ACK_PREFIX: usize = 2;
const EIT_EVENT_FIXED: usize = 2 + 5 + 3 + 2;
impl<'a> Parse<'a> for EitSectionAck<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::EIT_SECTION_ACK, "EITSectionAck")?;
if body.len() < EIT_SECTION_ACK_PREFIX {
return Err(Error::BufferTooShort {
need: EIT_SECTION_ACK_PREFIX,
have: body.len(),
what: "EITSectionAck",
});
}
let response_code = EitResponseCode::from_u8((body[0] >> 4) & 0x03);
let length = ((u16::from(body[0] & 0x0F) << 8) | u16::from(body[1])) as usize;
let loop_start = EIT_SECTION_ACK_PREFIX;
let loop_end = loop_start + length;
if body.len() < loop_end {
return Err(Error::LengthMismatch {
what: "EITSectionAck event loop",
declared: length,
actual: body.len() - loop_start,
});
}
let mut events = Vec::new();
let mut p = loop_start;
while p < loop_end {
if loop_end - p < EIT_EVENT_FIXED {
return Err(Error::InvalidObject {
what: "EITSectionAck event",
reason: "truncated event header",
});
}
let event_id = u16::from_be_bytes([body[p], body[p + 1]]);
let mut start_time = [0u8; 5];
start_time.copy_from_slice(&body[p + 2..p + 7]);
let mut duration = [0u8; 3];
duration.copy_from_slice(&body[p + 7..p + 10]);
let b10 = body[p + 10];
let b11 = body[p + 11];
let running_status = (b10 >> 5) & 0x07;
let free_ca_mode = (b10 & 0x10) != 0;
let dll = ((u16::from(b10 & 0x0F) << 8) | u16::from(b11)) as usize;
let desc_start = p + EIT_EVENT_FIXED;
let desc_end = desc_start + dll;
if desc_end > loop_end {
return Err(Error::LengthMismatch {
what: "EITSectionAck event descriptors",
declared: dll,
actual: loop_end - desc_start,
});
}
events.push(EitEvent {
event_id,
start_time,
duration,
running_status,
free_ca_mode,
descriptors: &body[desc_start..desc_end],
});
p = desc_end;
}
Ok(Self {
response_code,
events,
})
}
}
impl EitSectionAck<'_> {
fn loop_len(&self) -> usize {
self.events
.iter()
.map(|e| EIT_EVENT_FIXED + e.descriptors.len())
.sum()
}
}
impl Serialize for EitSectionAck<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(EIT_SECTION_ACK_PREFIX + self.loop_len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let length = self.loop_len();
if length > 0x0FFF {
return Err(Error::InvalidObject {
what: "EITSectionAck",
reason: "event loop longer than 4095 bytes",
});
}
let body_len = EIT_SECTION_ACK_PREFIX + length;
let mut pos = objects::write_apdu_header(tag::EIT_SECTION_ACK, body_len, buf)?;
let len16 = length as u16;
buf[pos] = (self.response_code.to_u8() << 4) | ((len16 >> 8) as u8 & 0x0F);
buf[pos + 1] = len16 as u8;
pos += EIT_SECTION_ACK_PREFIX;
for e in &self.events {
if e.descriptors.len() > 0x0FFF {
return Err(Error::InvalidObject {
what: "EITSectionAck event",
reason: "event descriptors loop longer than 4095 bytes",
});
}
buf[pos..pos + 2].copy_from_slice(&e.event_id.to_be_bytes());
buf[pos + 2..pos + 7].copy_from_slice(&e.start_time);
buf[pos + 7..pos + 10].copy_from_slice(&e.duration);
let dll = e.descriptors.len() as u16;
buf[pos + 10] = ((e.running_status & 0x07) << 5)
| (u8::from(e.free_ca_mode) << 4)
| ((dll >> 8) as u8 & 0x0F);
buf[pos + 11] = dll as u8;
pos += EIT_EVENT_FIXED;
buf[pos..pos + e.descriptors.len()].copy_from_slice(e.descriptors);
pos += e.descriptors.len();
}
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum BroadcastServiceGatewayApdu<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
ServiceGateway(ServiceGatewayApdu<'a>),
EitSectionReq(EitSectionReq),
EitSectionAck(EitSectionAck<'a>),
}
impl<'a> BroadcastServiceGatewayApdu<'a> {
pub fn parse(body: &'a [u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "broadcast_service_gateway apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::EIT_SECTION_REQ => Ok(Self::EitSectionReq(EitSectionReq::parse(body)?)),
tag::EIT_SECTION_ACK => Ok(Self::EitSectionAck(EitSectionAck::parse(body)?)),
_ => Ok(Self::ServiceGateway(ServiceGatewayApdu::parse(body)?)),
}
}
}
impl Serialize for BroadcastServiceGatewayApdu<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::ServiceGateway(o) => o.serialized_len(),
Self::EitSectionReq(o) => o.serialized_len(),
Self::EitSectionAck(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::ServiceGateway(o) => o.serialize_into(buf),
Self::EitSectionReq(o) => o.serialize_into(buf),
Self::EitSectionAck(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::super::service_gateway::{ServiceListReq, ServiceReference};
use super::*;
#[test]
fn eit_section_req_round_trips_and_bites() {
let req = EitSectionReq {
table_id: 0x004E,
service_id: 0x0064,
section_number: 0x00,
original_network_id: 0x0001,
ok_to_disrupt_service: true,
};
let bytes = req.to_bytes();
assert_eq!(
bytes,
[0x9F, 0x80, 0x10, 0x08, 0x00, 0x4E, 0x00, 0x64, 0x00, 0x00, 0x01, 0x01]
);
assert_eq!(EitSectionReq::parse(&bytes).unwrap(), req);
let mut other = req;
other.ok_to_disrupt_service = false;
assert_ne!(bytes, other.to_bytes());
assert_eq!(other.to_bytes()[11], 0x00);
}
#[test]
fn eit_section_ack_multi_event_round_trips_and_bites() {
let d0 = [0x4D, 0x00]; let e0 = EitEvent {
event_id: 0x1001,
start_time: [0xC0, 0x79, 0x12, 0x45, 0x00],
duration: [0x01, 0x30, 0x00],
running_status: 4,
free_ca_mode: false,
descriptors: &d0,
};
let e1 = EitEvent {
event_id: 0x1002,
start_time: [0xC0, 0x79, 0x14, 0x15, 0x00],
duration: [0x00, 0x45, 0x00],
running_status: 1,
free_ca_mode: true,
descriptors: &[],
};
let ack = EitSectionAck {
response_code: EitResponseCode::SectionFound,
events: alloc::vec![e0, e1],
};
let bytes = ack.to_bytes();
assert_eq!(bytes[0..6], [0x9F, 0x80, 0x11, 0x1C, 0x20, 0x1A]);
assert_eq!(bytes[16], 0x80);
assert_eq!(bytes[17], 0x02);
let parsed = EitSectionAck::parse(&bytes).unwrap();
assert_eq!(parsed, ack);
assert_eq!(parsed.events.len(), 2);
assert_eq!(parsed.events[0].running_status, 4);
assert!(parsed.events[1].free_ca_mode);
let mut other = ack.clone();
other.events[0].event_id = 0x1003;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn eit_section_ack_empty_loop() {
let ack = EitSectionAck {
response_code: EitResponseCode::NotAvailable,
events: alloc::vec![],
};
let bytes = ack.to_bytes();
assert_eq!(bytes, [0x9F, 0x80, 0x11, 0x02, 0x10, 0x00]);
assert_eq!(EitSectionAck::parse(&bytes).unwrap(), ack);
assert_eq!(ack.response_code.name(), "Section not available");
}
#[test]
fn bsg_routes_eit_tags() {
let req = EitSectionReq {
table_id: 0x4E,
service_id: 1,
section_number: 0,
original_network_id: 1,
ok_to_disrupt_service: false,
}
.to_bytes();
assert!(matches!(
BroadcastServiceGatewayApdu::parse(&req).unwrap(),
BroadcastServiceGatewayApdu::EitSectionReq(_)
));
}
#[test]
fn bsg_delegates_generic_gateway_tags() {
let req = ServiceListReq.to_bytes();
let parsed = BroadcastServiceGatewayApdu::parse(&req).unwrap();
assert!(matches!(
parsed,
BroadcastServiceGatewayApdu::ServiceGateway(ServiceGatewayApdu::ServiceListReq(_))
));
assert_eq!(parsed.to_bytes(), req);
use super::super::service_gateway::ServiceDescAck;
let sda = ServiceDescAck {
service: ServiceReference {
original_network_id: 1,
service_id: 0x64,
},
eit_schedule_flag: true,
eit_present_following_flag: true,
running_status: 4,
free_ca_mode: false,
descriptors: &[0x48, 0x01, 0x01],
}
.to_bytes();
let parsed = BroadcastServiceGatewayApdu::parse(&sda).unwrap();
assert!(matches!(
parsed,
BroadcastServiceGatewayApdu::ServiceGateway(ServiceGatewayApdu::ServiceDescAck(_))
));
assert_eq!(parsed.to_bytes(), sda);
}
}