use super::RunningStatus;
use crate::descriptors::DescriptorLoop;
use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
pub const TABLE_ID_ACTUAL: u8 = 0x42;
pub const TABLE_ID_OTHER: u8 = 0x46;
pub const PID: mpeg_ts::pid::Pid = mpeg_ts::pid::Pid::new(0x0011);
const MIN_HEADER_LEN: usize = 3;
const EXTENSION_HEADER_LEN: usize = 5;
const POST_EXTENSION_LEN: usize = 3;
const EIT_SCHEDULE_FLAG: u8 = 0x02;
const EIT_PRESENT_FOLLOWING_FLAG: u8 = 0x01;
use super::CRC_LEN;
const MIN_SECTION_LEN: usize = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + CRC_LEN;
const SERVICE_HEADER_LEN: usize = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum SdtKind {
Actual,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[non_exhaustive]
pub struct SdtService<'a> {
pub service_id: u16,
pub eit_schedule_flag: bool,
pub eit_present_following_flag: bool,
pub running_status: RunningStatus,
pub free_ca_mode: bool,
pub descriptors: DescriptorLoop<'a>,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_future_use: u8,
}
impl<'a> SdtService<'a> {
#[must_use]
pub fn new(
service_id: u16,
eit_schedule_flag: bool,
eit_present_following_flag: bool,
running_status: RunningStatus,
free_ca_mode: bool,
descriptors: DescriptorLoop<'a>,
) -> Self {
Self {
service_id,
eit_schedule_flag,
eit_present_following_flag,
running_status,
free_ca_mode,
descriptors,
reserved_future_use: RESERVED_FUTURE_USE_6,
}
}
}
const RESERVED_FUTURE_USE_6: u8 = super::RESERVED_6;
const RESERVED_FUTURE_USE_8: u8 = 0xFF;
const EIT_FLAGS_RESERVED_SHIFT: u32 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[non_exhaustive]
pub struct SdtSection<'a> {
pub kind: SdtKind,
pub transport_stream_id: u16,
pub version_number: u8,
pub current_next_indicator: bool,
pub section_number: u8,
pub last_section_number: u8,
pub original_network_id: u16,
pub services: Vec<SdtService<'a>>,
#[cfg_attr(feature = "serde", serde(skip))]
pub bits: super::SectionBits,
#[cfg_attr(feature = "serde", serde(skip))]
pub reserved_future_use: u8,
}
impl<'a> SdtSection<'a> {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
kind: SdtKind,
transport_stream_id: u16,
version_number: u8,
current_next_indicator: bool,
section_number: u8,
last_section_number: u8,
original_network_id: u16,
services: Vec<SdtService<'a>>,
) -> Self {
Self {
kind,
transport_stream_id,
version_number,
current_next_indicator,
section_number,
last_section_number,
original_network_id,
services,
bits: super::SectionBits::dvb(),
reserved_future_use: RESERVED_FUTURE_USE_8,
}
}
}
impl<'a> Parse<'a> for SdtSection<'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: "SdtSection",
});
}
let kind = match bytes[0] {
TABLE_ID_ACTUAL => SdtKind::Actual,
TABLE_ID_OTHER => SdtKind::Other,
other => {
return Err(Error::UnexpectedTableId {
table_id: other,
what: "SdtSection",
expected: &[TABLE_ID_ACTUAL, TABLE_ID_OTHER],
});
}
};
let section_length = super::section_length_of(bytes);
let total = super::check_section_length(
bytes.len(),
MIN_HEADER_LEN,
section_length,
MIN_SECTION_LEN,
)?;
let transport_stream_id = crate::wire::be_u16(bytes, 3, "sdt")?;
let version_number = super::version_number_of(bytes[5]);
let current_next_indicator = super::current_next_of(bytes[5]);
let section_number = bytes[6];
let last_section_number = bytes[7];
let original_network_id = crate::wire::be_u16(bytes, 8, "sdt")?;
let services_start = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
let services_end = total - CRC_LEN;
let mut services = Vec::new();
let mut pos = services_start;
while pos + SERVICE_HEADER_LEN <= services_end {
let (b2, _) = bytes
.get(pos..)
.and_then(|s| s.split_first_chunk::<2>())
.ok_or(Error::BufferTooShort {
need: pos + 2,
have: services_end,
what: "SdtSection service_id",
})?;
let service_id = u16::from_be_bytes(*b2);
let flags = bytes[pos + 2];
let eit_schedule_flag = (flags & EIT_SCHEDULE_FLAG) != 0;
let eit_present_following_flag = (flags & EIT_PRESENT_FOLLOWING_FLAG) != 0;
let status_and_len_hi = bytes[pos + 3];
let running_status =
RunningStatus::from_u8(super::running_status_of(status_and_len_hi));
let free_ca_mode = super::free_ca_mode_of(status_and_len_hi);
let descriptors_loop_length = super::dll_from_status(status_and_len_hi, bytes[pos + 4]);
let desc_start = pos + SERVICE_HEADER_LEN;
let desc_end = desc_start + descriptors_loop_length;
if desc_end > services_end {
return Err(Error::SectionLengthOverflow {
declared: descriptors_loop_length,
available: services_end.saturating_sub(desc_start),
});
}
services.push(SdtService {
service_id,
eit_schedule_flag,
eit_present_following_flag,
running_status,
free_ca_mode,
descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
reserved_future_use: flags >> EIT_FLAGS_RESERVED_SHIFT,
});
pos = desc_end;
}
if pos != services_end {
return Err(Error::BufferTooShort {
need: services_end - pos,
have: 0,
what: "SdtSection trailing service bytes",
});
}
Ok(SdtSection {
kind,
transport_stream_id,
version_number,
current_next_indicator,
section_number,
last_section_number,
original_network_id,
services,
bits: super::SectionBits::parse(bytes[1], Some(bytes[5])),
reserved_future_use: bytes[10],
})
}
}
impl Serialize for SdtSection<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let svc_bytes: usize = self
.services
.iter()
.map(|s| SERVICE_HEADER_LEN + s.descriptors.len())
.sum();
MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + svc_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(),
});
}
buf[0] = match self.kind {
SdtKind::Actual => TABLE_ID_ACTUAL,
SdtKind::Other => TABLE_ID_OTHER,
};
buf[1] = self.bits.b1()?;
super::write_section_length(buf, len - MIN_HEADER_LEN)?;
buf[3..5].copy_from_slice(&self.transport_stream_id.to_be_bytes());
buf[5] = self
.bits
.b5(self.version_number, self.current_next_indicator)?;
buf[6] = self.section_number;
buf[7] = self.last_section_number;
buf[8..10].copy_from_slice(&self.original_network_id.to_be_bytes());
buf[10] = self.reserved_future_use;
let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXTENSION_LEN;
for svc in &self.services {
buf[pos..pos + 2].copy_from_slice(&svc.service_id.to_be_bytes());
let flags = (broadcast_common::len::fit_mask(
svc.reserved_future_use,
RESERVED_FUTURE_USE_6,
"reserved_future_use",
)? << EIT_FLAGS_RESERVED_SHIFT)
| (u8::from(svc.eit_schedule_flag) << 1)
| u8::from(svc.eit_present_following_flag);
buf[pos + 2] = flags;
let dll = broadcast_common::len::fit_bits(
svc.descriptors.len() as u64,
12,
"descriptors_loop_length",
)?;
buf[pos + 3] = super::status_byte(
svc.running_status.to_u8(),
svc.free_ca_mode,
(dll >> 8) as u8,
);
buf[pos + 4] = dll as u8;
let desc_start = pos + SERVICE_HEADER_LEN;
buf[desc_start..desc_start + svc.descriptors.len()]
.copy_from_slice(svc.descriptors.raw());
pos = desc_start + svc.descriptors.len();
}
let crc_pos = len - CRC_LEN;
super::write_crc(buf, crc_pos);
Ok(len)
}
}
impl<'a> crate::traits::TableDef<'a> for SdtSection<'a> {
const TABLE_ID_RANGES: &'static [(u8, u8)] = &[
(TABLE_ID_ACTUAL, TABLE_ID_ACTUAL),
(TABLE_ID_OTHER, TABLE_ID_OTHER),
];
const NAME: &'static str = "SERVICE_DESCRIPTION";
}
#[cfg(test)]
mod tests {
use super::*;
type TestService = (u16, bool, bool, u8, bool, Vec<u8>);
fn build_sdt(
kind: SdtKind,
tsid: u16,
version: u8,
original_network_id: u16,
services: &[TestService],
) -> Vec<u8> {
let svc_bytes: usize = services
.iter()
.map(|(_, _, _, _, _, d)| SERVICE_HEADER_LEN + d.len())
.sum();
let section_length: u16 =
(EXTENSION_HEADER_LEN + POST_EXTENSION_LEN + svc_bytes + CRC_LEN) as u16;
let mut v = Vec::new();
v.push(match kind {
SdtKind::Actual => TABLE_ID_ACTUAL,
SdtKind::Other => TABLE_ID_OTHER,
});
crate::tables::push_section_header(
&mut v,
crate::tables::SECTION_B1_FLAGS_DVB,
section_length as usize,
);
v.extend_from_slice(&tsid.to_be_bytes());
v.push(crate::tables::version_byte(version, true));
v.push(0);
v.push(0);
v.extend_from_slice(&original_network_id.to_be_bytes());
v.push(0xFF);
for (sid, eit_s, eit_pf, rs, fca, desc) in services {
v.extend_from_slice(&sid.to_be_bytes());
let flags = 0xFC | (u8::from(*eit_s) << 1) | u8::from(*eit_pf);
v.push(flags);
crate::tables::push_status_and_len(&mut v, *rs, *fca, desc.len());
v.extend_from_slice(desc);
}
v.extend_from_slice(&[0, 0, 0, 0]);
v
}
#[test]
fn parse_actual_and_other_tables_distinguished_by_table_id() {
let a = build_sdt(SdtKind::Actual, 1, 0, 0x20, &[]);
let o = build_sdt(SdtKind::Other, 1, 0, 0x20, &[]);
assert!(matches!(
SdtSection::parse(&a).unwrap().kind,
SdtKind::Actual
));
assert!(matches!(
SdtSection::parse(&o).unwrap().kind,
SdtKind::Other
));
}
#[test]
fn parse_services_with_descriptor_bytes() {
let bytes = build_sdt(
SdtKind::Actual,
1,
0,
0x20,
&[(
100,
true,
true,
4,
false,
vec![0x48, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05],
)],
);
let sdt = SdtSection::parse(&bytes).unwrap();
assert_eq!(sdt.services.len(), 1);
assert_eq!(sdt.services[0].service_id, 100);
assert!(sdt.services[0].eit_schedule_flag);
assert!(sdt.services[0].eit_present_following_flag);
assert_eq!(sdt.services[0].running_status, RunningStatus::Running);
assert!(!sdt.services[0].free_ca_mode);
assert_eq!(
sdt.services[0].descriptors.raw(),
&[0x48, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05][..]
);
}
#[test]
fn service_free_ca_mode_flag_extracted() {
let bytes = build_sdt(
SdtKind::Actual,
1,
0,
0x20,
&[(1, false, false, 0, true, vec![])],
);
let sdt = SdtSection::parse(&bytes).unwrap();
assert!(sdt.services[0].free_ca_mode);
}
#[test]
fn service_running_status_extracted() {
let bytes = build_sdt(
SdtKind::Actual,
1,
0,
0x20,
&[(1, false, false, 2, false, vec![])],
);
let sdt = SdtSection::parse(&bytes).unwrap();
assert_eq!(
sdt.services[0].running_status,
RunningStatus::StartsInAFewSeconds
);
}
#[test]
fn parse_rejects_short_buffer() {
let err = SdtSection::parse(&[0x42, 0x00]).unwrap_err();
assert!(matches!(err, Error::BufferTooShort { .. }));
}
#[test]
fn parse_rejects_wrong_table_id() {
let mut bytes = build_sdt(SdtKind::Actual, 1, 0, 0x20, &[]);
bytes[0] = 0x00;
let err = SdtSection::parse(&bytes).unwrap_err();
assert!(matches!(
err,
Error::UnexpectedTableId { table_id: 0x00, .. }
));
}
#[test]
fn serialize_round_trip() {
let desc1: [u8; 4] = [0x48, 0x02, 0xAA, 0xBB];
let sdt = SdtSection::new(
SdtKind::Actual,
0x1234,
5,
true,
0,
0,
0x0020,
vec![
SdtService::new(
100,
true,
false,
RunningStatus::Running,
false,
DescriptorLoop::new(&desc1),
),
SdtService::new(
101,
false,
true,
RunningStatus::StartsInAFewSeconds,
true,
DescriptorLoop::new(&[]),
),
],
);
let mut buf = vec![0u8; sdt.serialized_len()];
sdt.serialize_into(&mut buf).unwrap();
let re = SdtSection::parse(&buf).unwrap();
assert_eq!(sdt, re);
}
#[test]
fn zero_services_is_valid() {
let bytes = build_sdt(SdtKind::Actual, 1, 0, 0x20, &[]);
let sdt = SdtSection::parse(&bytes).unwrap();
assert_eq!(sdt.services.len(), 0);
}
#[test]
fn parse_rejects_zero_section_length() {
let mut buf = vec![0u8; 64];
buf[0] = TABLE_ID_ACTUAL;
buf[1] = 0xF0;
buf[2] = 0x00;
for b in &mut buf[3..] {
*b = 0xFF;
}
assert!(matches!(
SdtSection::parse(&buf).unwrap_err(),
Error::SectionLengthOverflow { .. }
));
}
#[test]
fn serialize_rejects_service_descriptors_exceeding_section_length() {
let desc = vec![0xAAu8; 4079];
let sdt = SdtSection::new(
SdtKind::Actual,
1,
0,
true,
0,
0,
0x20,
vec![SdtService::new(
1,
false,
false,
RunningStatus::Undefined,
false,
DescriptorLoop::new(&desc),
)],
);
let mut buf = vec![0u8; sdt.serialized_len()];
assert!(matches!(
sdt.serialize_into(&mut buf).unwrap_err(),
Error::FieldOverflow(_)
));
}
#[test]
fn serialize_accepts_service_descriptors_at_section_length_boundary() {
let desc = vec![0xAAu8; 4078];
let sdt = SdtSection::new(
SdtKind::Actual,
1,
0,
true,
0,
0,
0x20,
vec![SdtService::new(
1,
false,
false,
RunningStatus::Undefined,
false,
DescriptorLoop::new(&desc),
)],
);
let mut buf = vec![0u8; sdt.serialized_len()];
sdt.serialize_into(&mut buf).expect("boundary fits");
let reparsed = SdtSection::parse(&buf).expect("reparse");
assert_eq!(sdt, reparsed);
}
#[test]
fn parse_rejects_trailing_slack_bytes() {
let mut bytes = build_sdt(
SdtKind::Actual,
1,
0,
0x20,
&[(1, false, false, 0, false, vec![])],
);
let sl = (bytes.len() - MIN_HEADER_LEN) as u16 + 2;
bytes[1] = (bytes[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
bytes[2] = (sl & 0xFF) as u8;
let crc_pos = bytes.len() - CRC_LEN;
bytes.splice(crc_pos..crc_pos, [0xFF, 0xFF]);
let err = SdtSection::parse(&bytes).unwrap_err();
assert!(matches!(
err,
Error::BufferTooShort {
what: "SdtSection trailing service bytes",
..
}
));
}
#[test]
fn parse_accepts_well_formed_service_loop_with_no_slack() {
let bytes = build_sdt(
SdtKind::Actual,
1,
0,
0x20,
&[
(1, false, false, 0, false, vec![]),
(2, true, true, 4, false, vec![0x48, 0x02, 0xAA, 0xBB]),
],
);
let sdt = SdtSection::parse(&bytes).unwrap();
assert_eq!(sdt.services.len(), 2);
}
}