use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct ResourceId(pub u32);
impl ResourceId {
pub const LEN: usize = 4;
#[must_use]
pub const fn id_type(self) -> u8 {
(self.0 >> 30) as u8
}
#[must_use]
pub const fn is_private(self) -> bool {
self.id_type() == 3
}
#[must_use]
pub const fn resource_class(self) -> u16 {
((self.0 >> 16) & 0x3FFF) as u16
}
#[must_use]
pub const fn resource_type(self) -> u16 {
((self.0 >> 6) & 0x03FF) as u16
}
#[must_use]
pub const fn resource_version(self) -> u8 {
(self.0 & 0x3F) as u8
}
#[must_use]
pub fn name(self) -> &'static str {
match self {
RESOURCE_MANAGER => "resource_manager",
APPLICATION_INFORMATION => "application_information",
CONDITIONAL_ACCESS_SUPPORT => "conditional_access_support",
HOST_CONTROL => "host_control",
DATE_TIME => "date_time",
MMI => "mmi",
_ => "unknown",
}
}
}
impl core::fmt::Display for ResourceId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.name() {
"unknown" => write!(f, "resource_id(0x{:08X})", self.0),
n => write!(f, "{n}(0x{:08X})", self.0),
}
}
}
impl<'a> Parse<'a> for ResourceId {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let chunk = bytes.first_chunk::<4>().ok_or(Error::BufferTooShort {
need: 4,
have: bytes.len(),
what: "resource_identifier",
})?;
Ok(Self(u32::from_be_bytes(*chunk)))
}
}
impl Serialize for ResourceId {
type Error = Error;
fn serialized_len(&self) -> usize {
Self::LEN
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if buf.len() < Self::LEN {
return Err(Error::OutputBufferTooSmall {
need: Self::LEN,
have: buf.len(),
});
}
buf[..Self::LEN].copy_from_slice(&self.0.to_be_bytes());
Ok(Self::LEN)
}
}
pub const RESOURCE_MANAGER: ResourceId = ResourceId(0x0001_0041);
pub const APPLICATION_INFORMATION: ResourceId = ResourceId(0x0002_0041);
pub const CONDITIONAL_ACCESS_SUPPORT: ResourceId = ResourceId(0x0003_0041);
pub const HOST_CONTROL: ResourceId = ResourceId(0x0020_0041);
pub const DATE_TIME: ResourceId = ResourceId(0x0024_0041);
pub const MMI: ResourceId = ResourceId(0x0040_0041);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packs_per_table_57() {
let rm = RESOURCE_MANAGER;
assert_eq!(rm.id_type(), 0);
assert!(!rm.is_private());
assert_eq!(rm.resource_class(), 1);
assert_eq!(rm.resource_type(), 1);
assert_eq!(rm.resource_version(), 1);
assert_eq!(rm.name(), "resource_manager");
}
#[test]
fn ca_support_fields() {
assert_eq!(CONDITIONAL_ACCESS_SUPPORT.resource_class(), 3);
assert_eq!(
CONDITIONAL_ACCESS_SUPPORT.name(),
"conditional_access_support"
);
}
#[test]
fn private_resource_type() {
let p = ResourceId(0xC000_0000);
assert!(p.is_private());
assert_eq!(p.id_type(), 3);
assert_eq!(p.name(), "unknown");
}
#[test]
fn round_trip() {
let r = DATE_TIME;
let bytes = r.to_bytes();
assert_eq!(bytes, [0x00, 0x24, 0x00, 0x41]);
assert_eq!(ResourceId::parse(&bytes).unwrap(), r);
}
#[test]
fn mutating_changes_bytes() {
let mut bytes = MMI.to_bytes();
let a = ResourceId::parse(&bytes).unwrap();
bytes[0] ^= 0xFF;
let b = ResourceId::parse(&bytes).unwrap();
assert_ne!(a, b);
}
}