Skip to main content

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    ///
53    /// # Errors
54    ///
55    /// [`ResourceIdError::InvalidParts`] when either half is too wide for its
56    /// field. A resource id gives the BIF index twelve bits and the resource
57    /// index twenty, so neither part survives being packed unchecked.
58    #[cfg_attr(
59        feature = "tracing",
60        tracing::instrument(level = "debug", fields(bif_index, resource_index))
61    )]
62    pub fn from_parts(bif_index: u32, resource_index: u32) -> Result<Self, ResourceIdError> {
63        if bif_index > MAX_BIF_INDEX || resource_index > RESOURCE_INDEX_MASK {
64            trace_debug!("resource id parts are out of range");
65            return Err(ResourceIdError::InvalidParts {
66                bif_index,
67                resource_index,
68            });
69        }
70        let raw = (bif_index << 20) | resource_index;
71        trace_debug!(raw, "packed resource id from parts");
72        Ok(Self(raw))
73    }
74
75    /// Returns the raw on-disk value.
76    pub const fn raw(self) -> u32 {
77        self.0
78    }
79
80    /// Returns the packed BIF index (`bits 31..20`).
81    pub const fn bif_index(self) -> u32 {
82        self.0 >> 20
83    }
84
85    /// Returns the packed resource index (`bits 19..0`).
86    pub const fn resource_index(self) -> u32 {
87        self.0 & RESOURCE_INDEX_MASK
88    }
89}
90
91impl From<u32> for ResourceId {
92    fn from(value: u32) -> Self {
93        Self::from_raw(value)
94    }
95}
96
97impl From<ResourceId> for u32 {
98    fn from(value: ResourceId) -> Self {
99        value.raw()
100    }
101}
102
103impl Display for ResourceId {
104    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{}", self.0)
106    }
107}
108
109impl LowerHex for ResourceId {
110    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
111        LowerHex::fmt(&self.0, f)
112    }
113}
114
115impl UpperHex for ResourceId {
116    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117        UpperHex::fmt(&self.0, f)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn from_parts_packs_expected_bit_layout() {
127        let resource_id = ResourceId::from_parts(0xabc, 0x54321).expect("must fit");
128        assert_eq!(resource_id.raw(), 0xabc5_4321);
129        assert_eq!(resource_id.bif_index(), 0xabc);
130        assert_eq!(resource_id.resource_index(), 0x54321);
131    }
132
133    #[test]
134    fn from_parts_rejects_out_of_range_values() {
135        let err = ResourceId::from_parts(0x1000, 0).expect_err("must fail");
136        assert_eq!(
137            err,
138            ResourceIdError::InvalidParts {
139                bif_index: 0x1000,
140                resource_index: 0,
141            }
142        );
143    }
144}