1use crate::error::{Error, Result};
12use broadcast_common::{Parse, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[cfg_attr(feature = "serde", serde(transparent))]
18pub struct ResourceId(pub u32);
19
20impl ResourceId {
21 pub const LEN: usize = 4;
23
24 #[must_use]
27 pub const fn id_type(self) -> u8 {
28 (self.0 >> 30) as u8
29 }
30
31 #[must_use]
33 pub const fn is_private(self) -> bool {
34 self.id_type() == 3
35 }
36
37 #[must_use]
40 pub const fn resource_class(self) -> u16 {
41 ((self.0 >> 16) & 0x3FFF) as u16
42 }
43
44 #[must_use]
46 pub const fn resource_type(self) -> u16 {
47 ((self.0 >> 6) & 0x03FF) as u16
48 }
49
50 #[must_use]
52 pub const fn resource_version(self) -> u8 {
53 (self.0 & 0x3F) as u8
54 }
55
56 #[must_use]
59 pub fn name(self) -> &'static str {
60 match self {
61 RESOURCE_MANAGER => "resource_manager",
62 APPLICATION_INFORMATION => "application_information",
63 CONDITIONAL_ACCESS_SUPPORT => "conditional_access_support",
64 HOST_CONTROL => "host_control",
65 DATE_TIME => "date_time",
66 MMI => "mmi",
67 _ => "unknown",
68 }
69 }
70}
71
72impl core::fmt::Display for ResourceId {
73 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 match self.name() {
75 "unknown" => write!(f, "resource_id(0x{:08X})", self.0),
76 n => write!(f, "{n}(0x{:08X})", self.0),
77 }
78 }
79}
80
81impl<'a> Parse<'a> for ResourceId {
82 type Error = Error;
83 fn parse(bytes: &'a [u8]) -> Result<Self> {
84 let chunk = bytes.first_chunk::<4>().ok_or(Error::BufferTooShort {
85 need: 4,
86 have: bytes.len(),
87 what: "resource_identifier",
88 })?;
89 Ok(Self(u32::from_be_bytes(*chunk)))
90 }
91}
92
93impl Serialize for ResourceId {
94 type Error = Error;
95 fn serialized_len(&self) -> usize {
96 Self::LEN
97 }
98 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
99 if buf.len() < Self::LEN {
100 return Err(Error::OutputBufferTooSmall {
101 need: Self::LEN,
102 have: buf.len(),
103 });
104 }
105 buf[..Self::LEN].copy_from_slice(&self.0.to_be_bytes());
106 Ok(Self::LEN)
107 }
108}
109
110pub const RESOURCE_MANAGER: ResourceId = ResourceId(0x0001_0041);
112pub const APPLICATION_INFORMATION: ResourceId = ResourceId(0x0002_0041);
114pub const CONDITIONAL_ACCESS_SUPPORT: ResourceId = ResourceId(0x0003_0041);
116pub const HOST_CONTROL: ResourceId = ResourceId(0x0020_0041);
118pub const DATE_TIME: ResourceId = ResourceId(0x0024_0041);
120pub const MMI: ResourceId = ResourceId(0x0040_0041);
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn packs_per_table_57() {
129 let rm = RESOURCE_MANAGER;
131 assert_eq!(rm.id_type(), 0);
132 assert!(!rm.is_private());
133 assert_eq!(rm.resource_class(), 1);
134 assert_eq!(rm.resource_type(), 1);
135 assert_eq!(rm.resource_version(), 1);
136 assert_eq!(rm.name(), "resource_manager");
137 }
138
139 #[test]
140 fn ca_support_fields() {
141 assert_eq!(CONDITIONAL_ACCESS_SUPPORT.resource_class(), 3);
142 assert_eq!(
143 CONDITIONAL_ACCESS_SUPPORT.name(),
144 "conditional_access_support"
145 );
146 }
147
148 #[test]
149 fn private_resource_type() {
150 let p = ResourceId(0xC000_0000);
151 assert!(p.is_private());
152 assert_eq!(p.id_type(), 3);
153 assert_eq!(p.name(), "unknown");
154 }
155
156 #[test]
157 fn round_trip() {
158 let r = DATE_TIME;
159 let bytes = r.to_bytes();
160 assert_eq!(bytes, [0x00, 0x24, 0x00, 0x41]);
161 assert_eq!(ResourceId::parse(&bytes).unwrap(), r);
162 }
163
164 #[test]
165 fn mutating_changes_bytes() {
166 let mut bytes = MMI.to_bytes();
167 let a = ResourceId::parse(&bytes).unwrap();
168 bytes[0] ^= 0xFF;
169 let b = ResourceId::parse(&bytes).unwrap();
170 assert_ne!(a, b);
171 }
172}