use std::any::Any;
use std::collections::HashMap;
#[cfg(not(feature = "serde"))]
pub trait TableObject: std::fmt::Debug + Any + Send + Sync {
fn as_any(&self) -> &dyn Any;
}
#[cfg(feature = "serde")]
pub trait TableObject: std::fmt::Debug + Any + Send + Sync + erased_serde::Serialize {
fn as_any(&self) -> &dyn Any;
}
#[cfg(not(feature = "serde"))]
impl<T> TableObject for T
where
T: std::fmt::Debug + Any + Send + Sync,
{
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(feature = "serde")]
impl<T> TableObject for T
where
T: std::fmt::Debug + Any + Send + Sync + serde::Serialize,
{
fn as_any(&self) -> &dyn Any {
self
}
}
impl dyn TableObject {
#[must_use]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
self.as_any().downcast_ref::<T>()
}
#[must_use]
pub fn is<T: Any>(&self) -> bool {
self.as_any().is::<T>()
}
}
#[cfg(feature = "serde")]
#[allow(clippy::borrowed_box)]
pub(crate) fn serialize_erased<S: serde::Serializer>(
v: &Box<dyn TableObject>,
s: S,
) -> Result<S::Ok, S::Error> {
erased_serde::serialize(&**v, s)
}
pub(crate) type CustomParse =
Box<dyn for<'a> Fn(&'a [u8]) -> crate::Result<Box<dyn TableObject>> + Send + Sync>;
#[derive(Default)]
pub struct TableRegistry {
custom: HashMap<u8, CustomParse>,
}
impl TableRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub(crate) fn lookup(&self, table_id: u8) -> Option<&CustomParse> {
self.custom.get(&table_id)
}
pub fn register<T>(&mut self) -> &mut Self
where
T: for<'a> crate::traits::TableDef<'a> + TableObject + 'static,
{
let ranges = <T as crate::traits::TableDef<'static>>::TABLE_ID_RANGES;
for &(lo, hi) in ranges {
for id in lo..=hi {
self.custom.insert(
id,
Box::new(|b| {
Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn TableObject>)
}),
);
}
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tables::AnyTableSection;
use crate::traits::TableDef;
use dvb_common::Parse;
const CUSTOM_TABLE_ID: u8 = 0x90;
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct CustomTable {
table_id: u8,
payload: Vec<u8>,
}
impl<'a> Parse<'a> for CustomTable {
type Error = crate::Error;
fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
if bytes.is_empty() {
return Err(crate::Error::BufferTooShort {
need: 1,
have: 0,
what: "CustomTable",
});
}
Ok(Self {
table_id: bytes[0],
payload: bytes[1..].to_vec(),
})
}
}
impl<'a> TableDef<'a> for CustomTable {
const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(CUSTOM_TABLE_ID, CUSTOM_TABLE_ID)];
const NAME: &'static str = "CUSTOM_TABLE";
}
const MULTI_LO: u8 = 0x90;
const MULTI_MID: u8 = 0x92;
const MULTI_HI: u8 = 0x93;
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct MultiRangeTable {
table_id: u8,
}
impl<'a> Parse<'a> for MultiRangeTable {
type Error = crate::Error;
fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
if bytes.is_empty() {
return Err(crate::Error::BufferTooShort {
need: 1,
have: 0,
what: "MultiRangeTable",
});
}
Ok(Self { table_id: bytes[0] })
}
}
impl<'a> TableDef<'a> for MultiRangeTable {
const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(MULTI_LO, MULTI_LO), (MULTI_MID, MULTI_HI)];
const NAME: &'static str = "MULTI_RANGE_TABLE";
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
struct OverridePat {
table_id: u8,
}
impl<'a> Parse<'a> for OverridePat {
type Error = crate::Error;
fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
if bytes.is_empty() {
return Err(crate::Error::BufferTooShort {
need: 1,
have: 0,
what: "OverridePat",
});
}
Ok(Self { table_id: bytes[0] })
}
}
impl<'a> TableDef<'a> for OverridePat {
const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(0x00, 0x00)];
const NAME: &'static str = "OVERRIDE_PAT";
}
#[test]
fn custom_table_dispatches_to_other() {
let mut reg = TableRegistry::new();
reg.register::<CustomTable>();
let bytes = [CUSTOM_TABLE_ID, 0xAA, 0xBB];
let result = AnyTableSection::parse_with(®, &bytes).unwrap();
match result {
AnyTableSection::Other {
table_id,
ref value,
} => {
assert_eq!(table_id, CUSTOM_TABLE_ID);
let ct = value.downcast_ref::<CustomTable>().unwrap();
assert_eq!(ct.table_id, CUSTOM_TABLE_ID);
assert_eq!(ct.payload, &[0xAA, 0xBB]);
}
other => panic!("expected Other, got {other:?}"),
}
}
#[test]
fn multi_range_registers_all_ids() {
let mut reg = TableRegistry::new();
reg.register::<MultiRangeTable>();
assert!(reg.lookup(MULTI_LO).is_some());
assert!(reg.lookup(MULTI_MID).is_some());
assert!(reg.lookup(MULTI_MID + 1).is_some());
assert!(reg.lookup(MULTI_HI).is_some());
assert!(reg.lookup(MULTI_LO + 1).is_none());
for id in [MULTI_LO, MULTI_MID, MULTI_MID + 1, MULTI_HI] {
let bytes = [id, 0x00];
let result = AnyTableSection::parse_with(®, &bytes).unwrap();
match result {
AnyTableSection::Other { table_id, .. } => assert_eq!(table_id, id),
other => panic!("id {id:#04x}: expected Other, got {other:?}"),
}
}
let bytes = [MULTI_LO + 1, 0x00];
let result = AnyTableSection::parse_with(®, &bytes).unwrap();
assert!(
matches!(result, AnyTableSection::Unknown { .. }),
"expected Unknown for unregistered id 0x91, got {result:?}",
);
}
#[test]
fn fallback_to_builtin_for_unregistered() {
let reg = TableRegistry::new();
use crate::tables::pat::{PatEntry, PatSection};
use dvb_common::Serialize;
let pat = PatSection {
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries: vec![PatEntry {
program_number: 1,
pid: 0x0100,
}],
};
let mut buf = vec![0u8; pat.serialized_len()];
pat.serialize_into(&mut buf).unwrap();
let result = AnyTableSection::parse_with(®, &buf).unwrap();
match result {
AnyTableSection::PatSection(p) => assert_eq!(p.entries.len(), 1),
other => panic!("expected PatSection, got {other:?}"),
}
}
#[test]
fn override_builtin_yields_other() {
let mut reg = TableRegistry::new();
reg.register::<OverridePat>();
let bytes = [0x00u8, 0x01, 0x02];
let result = AnyTableSection::parse_with(®, &bytes).unwrap();
match result {
AnyTableSection::Other {
table_id,
ref value,
} => {
assert_eq!(table_id, 0x00);
let op = value.downcast_ref::<OverridePat>().unwrap();
assert_eq!(op.table_id, 0x00);
}
other => panic!("expected Other (override), got {other:?}"),
}
}
#[test]
fn empty_bytes_returns_buffer_too_short() {
let reg = TableRegistry::new();
let result = AnyTableSection::parse_with(®, &[]);
assert!(matches!(
result,
Err(crate::Error::BufferTooShort {
need: 1,
have: 0,
..
})
));
}
#[cfg(feature = "serde")]
#[test]
fn serde_other_variant() {
let mut reg = TableRegistry::new();
reg.register::<CustomTable>();
let bytes = [CUSTOM_TABLE_ID, 0xAA, 0xBB];
let result = AnyTableSection::parse_with(®, &bytes).unwrap();
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"other\""), "unexpected JSON: {json}");
}
}