Skip to main content

dvb_ci/
resource.rs

1//! `resource_identifier()` — the 4-octet resource identity — ETSI EN 50221
2//! §8.2.2, Table 15 (PDF p. 24) + §8.8.1, Table 57 (PDF p. 54).
3//!
4//! The two MSBs of the first octet are `resource_id_type`. Type 0/1/2 indicate a
5//! public resource laid out as `resource_class` (14b) + `resource_type` (10b) +
6//! `resource_version` (6b); type 3 indicates a private resource laid out as
7//! `private_resource_definer` (10b) + `private_resource_identity` (20b). Both
8//! layouts pack to exactly 32 bits, so we carry the identifier verbatim as a
9//! `u32` and expose typed views over it — no information is lost on round-trip.
10
11use crate::error::{Error, Result};
12use broadcast_common::len::need_out;
13use broadcast_common::{Parse, Serialize};
14
15/// A `resource_identifier()` — 4 octets, carried verbatim as a big-endian `u32`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize))]
18#[cfg_attr(feature = "serde", serde(transparent))]
19pub struct ResourceId(pub u32);
20
21impl ResourceId {
22    /// Wire length of a `resource_identifier()` in bytes.
23    pub const LEN: usize = 4;
24
25    /// `resource_id_type` — the two MSBs of the first octet (0/1/2 = public,
26    /// 3 = private).
27    #[must_use]
28    pub const fn id_type(self) -> u8 {
29        (self.0 >> 30) as u8
30    }
31
32    /// True if this is a private resource (`resource_id_type == 3`).
33    #[must_use]
34    pub const fn is_private(self) -> bool {
35        self.id_type() == 3
36    }
37
38    /// `resource_class` (14 bits) for a public resource — meaningless if
39    /// [`is_private`](Self::is_private).
40    #[must_use]
41    pub const fn resource_class(self) -> u16 {
42        ((self.0 >> 16) & 0x3FFF) as u16
43    }
44
45    /// `resource_type` (10 bits) for a public resource.
46    #[must_use]
47    pub const fn resource_type(self) -> u16 {
48        ((self.0 >> 6) & 0x03FF) as u16
49    }
50
51    /// `resource_version` (6 bits) for a public resource.
52    #[must_use]
53    pub const fn resource_version(self) -> u8 {
54        (self.0 & 0x3F) as u8
55    }
56
57    /// Diagnostic name for the well-known public resources (Table 57), matched
58    /// on the full identifier; `"unknown"` otherwise.
59    #[must_use]
60    pub fn name(self) -> &'static str {
61        match self {
62            RESOURCE_MANAGER => "resource_manager",
63            APPLICATION_INFORMATION => "application_information",
64            CONDITIONAL_ACCESS_SUPPORT => "conditional_access_support",
65            HOST_CONTROL => "host_control",
66            DATE_TIME => "date_time",
67            MMI => "mmi",
68            _ => "unknown",
69        }
70    }
71}
72
73impl core::fmt::Display for ResourceId {
74    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75        match self.name() {
76            "unknown" => write!(f, "resource_id(0x{:08X})", self.0),
77            n => write!(f, "{n}(0x{:08X})", self.0),
78        }
79    }
80}
81
82impl<'a> Parse<'a> for ResourceId {
83    type Error = Error;
84    fn parse(bytes: &'a [u8]) -> Result<Self> {
85        let chunk = bytes.first_chunk::<4>().ok_or(Error::BufferTooShort(
86            broadcast_common::error::BufferTooShort {
87                need: 4,
88                have: bytes.len(),
89                what: "resource_identifier",
90            },
91        ))?;
92        Ok(Self(u32::from_be_bytes(*chunk)))
93    }
94}
95
96impl Serialize for ResourceId {
97    type Error = Error;
98    fn serialized_len(&self) -> usize {
99        Self::LEN
100    }
101    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
102        need_out(buf, Self::LEN)?;
103        buf[..Self::LEN].copy_from_slice(&self.0.to_be_bytes());
104        Ok(Self::LEN)
105    }
106}
107
108/// Resource Manager — `00010041` (Table 57).
109pub const RESOURCE_MANAGER: ResourceId = ResourceId(0x0001_0041);
110/// Application Information — `00020041`.
111pub const APPLICATION_INFORMATION: ResourceId = ResourceId(0x0002_0041);
112/// Conditional Access Support — `00030041`.
113pub const CONDITIONAL_ACCESS_SUPPORT: ResourceId = ResourceId(0x0003_0041);
114/// Host Control — `00200041`.
115pub const HOST_CONTROL: ResourceId = ResourceId(0x0020_0041);
116/// Date-Time — `00240041`.
117pub const DATE_TIME: ResourceId = ResourceId(0x0024_0041);
118/// MMI — `00400041`.
119pub const MMI: ResourceId = ResourceId(0x0040_0041);
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn packs_per_table_57() {
127        // Resource Manager: class=1, type=1, version=1 -> 0x00010041.
128        let rm = RESOURCE_MANAGER;
129        assert_eq!(rm.id_type(), 0);
130        assert!(!rm.is_private());
131        assert_eq!(rm.resource_class(), 1);
132        assert_eq!(rm.resource_type(), 1);
133        assert_eq!(rm.resource_version(), 1);
134        assert_eq!(rm.name(), "resource_manager");
135    }
136
137    #[test]
138    fn ca_support_fields() {
139        assert_eq!(CONDITIONAL_ACCESS_SUPPORT.resource_class(), 3);
140        assert_eq!(
141            CONDITIONAL_ACCESS_SUPPORT.name(),
142            "conditional_access_support"
143        );
144    }
145
146    #[test]
147    fn private_resource_type() {
148        let p = ResourceId(0xC000_0000);
149        assert!(p.is_private());
150        assert_eq!(p.id_type(), 3);
151        assert_eq!(p.name(), "unknown");
152    }
153
154    #[test]
155    fn round_trip() {
156        let r = DATE_TIME;
157        let bytes = r.try_to_bytes().unwrap();
158        assert_eq!(bytes, [0x00, 0x24, 0x00, 0x41]);
159        assert_eq!(ResourceId::parse(&bytes).unwrap(), r);
160    }
161
162    #[test]
163    fn mutating_changes_bytes() {
164        let mut bytes = MMI.try_to_bytes().unwrap();
165        let a = ResourceId::parse(&bytes).unwrap();
166        bytes[0] ^= 0xFF;
167        let b = ResourceId::parse(&bytes).unwrap();
168        assert_ne!(a, b);
169    }
170}