rakata_core/
resource_id.rs

1use std::fmt::{Display, Formatter, LowerHex, UpperHex};
2use thiserror::Error;
3
4#[cfg(feature = "tracing")]
5macro_rules! trace_debug {
6    ($($arg:tt)*) => {
7        tracing::debug!($($arg)*);
8    };
9}
10
11#[cfg(not(feature = "tracing"))]
12macro_rules! trace_debug {
13    ($($arg:tt)*) => {};
14}
15
16/// Packed KEY/BIF resource identifier bitmask for the low 20-bit resource index.
17pub const RESOURCE_INDEX_MASK: u32 = 0x000F_FFFF;
18/// Maximum KEY/BIF BIF index representable in a packed resource ID.
19pub const MAX_BIF_INDEX: u32 = 0x0000_0FFF;
20
21/// Error returned when `(bif_index, resource_index)` parts exceed packed limits.
22#[derive(Debug, Clone, PartialEq, Eq, Error)]
23pub enum ResourceIdError {
24    /// Packed parts exceed the format's fixed bit widths.
25    #[error(
26        "resource id parts out of range (bif_index={bif_index}, resource_index={resource_index})"
27    )]
28    InvalidParts {
29        /// BIF index (must be `<= 0xFFF`).
30        bif_index: u32,
31        /// Resource index (must be `<= 0xFFFFF`).
32        resource_index: u32,
33    },
34}
35
36/// Packed KEY/BIF resource identifier.
37///
38/// Bit layout:
39/// - bits `31..20`: BIF index
40/// - bits `19..0`: resource index within that BIF
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct ResourceId(u32);
44
45impl ResourceId {
46    /// Creates a `ResourceId` from a raw on-disk value.
47    pub const fn from_raw(raw: u32) -> Self {
48        Self(raw)
49    }
50
51    /// Packs `(bif_index, resource_index)` into a `ResourceId`.
52    #[cfg_attr(
53        feature = "tracing",
54        tracing::instrument(level = "debug", fields(bif_index, resource_index))
55    )]
56    pub fn from_parts(bif_index: u32, resource_index: u32) -> Result<Self, ResourceIdError> {
57        if bif_index > MAX_BIF_INDEX || resource_index > RESOURCE_INDEX_MASK {
58            trace_debug!("resource id parts are out of range");
59            return Err(ResourceIdError::InvalidParts {
60                bif_index,
61                resource_index,
62            });
63        }
64        let raw = (bif_index << 20) | resource_index;
65        trace_debug!(raw, "packed resource id from parts");
66        Ok(Self(raw))
67    }
68
69    /// Returns the raw on-disk value.
70    pub const fn raw(self) -> u32 {
71        self.0
72    }
73
74    /// Returns the packed BIF index (`bits 31..20`).
75    pub const fn bif_index(self) -> u32 {
76        self.0 >> 20
77    }
78
79    /// Returns the packed resource index (`bits 19..0`).
80    pub const fn resource_index(self) -> u32 {
81        self.0 & RESOURCE_INDEX_MASK
82    }
83}
84
85impl From<u32> for ResourceId {
86    fn from(value: u32) -> Self {
87        Self::from_raw(value)
88    }
89}
90
91impl From<ResourceId> for u32 {
92    fn from(value: ResourceId) -> Self {
93        value.raw()
94    }
95}
96
97impl Display for ResourceId {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{}", self.0)
100    }
101}
102
103impl LowerHex for ResourceId {
104    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105        LowerHex::fmt(&self.0, f)
106    }
107}
108
109impl UpperHex for ResourceId {
110    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
111        UpperHex::fmt(&self.0, f)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn from_parts_packs_expected_bit_layout() {
121        let resource_id = ResourceId::from_parts(0xabc, 0x54321).expect("must fit");
122        assert_eq!(resource_id.raw(), 0xabc5_4321);
123        assert_eq!(resource_id.bif_index(), 0xabc);
124        assert_eq!(resource_id.resource_index(), 0x54321);
125    }
126
127    #[test]
128    fn from_parts_rejects_out_of_range_values() {
129        let err = ResourceId::from_parts(0x1000, 0).expect_err("must fail");
130        assert_eq!(
131            err,
132            ResourceIdError::InvalidParts {
133                bif_index: 0x1000,
134                resource_index: 0,
135            }
136        );
137    }
138}