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::{Parse, Serialize};
13
14/// A `resource_identifier()` — 4 octets, carried verbatim as a big-endian `u32`.
15#[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    /// Wire length of a `resource_identifier()` in bytes.
22    pub const LEN: usize = 4;
23
24    /// `resource_id_type` — the two MSBs of the first octet (0/1/2 = public,
25    /// 3 = private).
26    #[must_use]
27    pub const fn id_type(self) -> u8 {
28        (self.0 >> 30) as u8
29    }
30
31    /// True if this is a private resource (`resource_id_type == 3`).
32    #[must_use]
33    pub const fn is_private(self) -> bool {
34        self.id_type() == 3
35    }
36
37    /// `resource_class` (14 bits) for a public resource — meaningless if
38    /// [`is_private`](Self::is_private).
39    #[must_use]
40    pub const fn resource_class(self) -> u16 {
41        ((self.0 >> 16) & 0x3FFF) as u16
42    }
43
44    /// `resource_type` (10 bits) for a public resource.
45    #[must_use]
46    pub const fn resource_type(self) -> u16 {
47        ((self.0 >> 6) & 0x03FF) as u16
48    }
49
50    /// `resource_version` (6 bits) for a public resource.
51    #[must_use]
52    pub const fn resource_version(self) -> u8 {
53        (self.0 & 0x3F) as u8
54    }
55
56    /// Diagnostic name for the well-known public resources (Table 57), matched
57    /// on the full identifier; `"unknown"` otherwise.
58    #[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
110/// Resource Manager — `00010041` (Table 57).
111pub const RESOURCE_MANAGER: ResourceId = ResourceId(0x0001_0041);
112/// Application Information — `00020041`.
113pub const APPLICATION_INFORMATION: ResourceId = ResourceId(0x0002_0041);
114/// Conditional Access Support — `00030041`.
115pub const CONDITIONAL_ACCESS_SUPPORT: ResourceId = ResourceId(0x0003_0041);
116/// Host Control — `00200041`.
117pub const HOST_CONTROL: ResourceId = ResourceId(0x0020_0041);
118/// Date-Time — `00240041`.
119pub const DATE_TIME: ResourceId = ResourceId(0x0024_0041);
120/// MMI — `00400041`.
121pub 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        // Resource Manager: class=1, type=1, version=1 -> 0x00010041.
130        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}