Skip to main content

ufs/
bytes.rs

1//! Bounds-checked readers for both byte orders (the Paranoid Gatekeeper
2//! standard).
3//!
4//! UFS is endianness-agnostic on disk: the filesystem is written in the byte
5//! order of the host that created it, and the superblock magic disambiguates
6//! which order to read (like ZFS). So every reader comes in a little-endian and
7//! a big-endian form, and the [`Endian`] selector picks between them once the
8//! magic has resolved the order.
9//!
10//! Every reader yields `0` when the requested range lies outside the buffer, so
11//! a malformed or truncated image can never panic a parser. Callers that need
12//! to distinguish "field absent" from "field is zero" bounds-check the buffer
13//! length up front and surface [`crate::UfsError::Truncated`].
14
15/// On-disk byte order, resolved from the superblock magic.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Endian {
18    /// Least-significant byte first (x86, most modern hosts).
19    Little,
20    /// Most-significant byte first (SPARC, historic big-endian hosts).
21    Big,
22}
23
24impl Endian {
25    /// Read a `u16` at `off` in this byte order, or `0` if out of range.
26    #[must_use]
27    pub fn u16(self, data: &[u8], off: usize) -> u16 {
28        match self {
29            Endian::Little => le_u16(data, off),
30            Endian::Big => be_u16(data, off),
31        }
32    }
33
34    /// Read a `u32` at `off` in this byte order, or `0` if out of range.
35    #[must_use]
36    pub fn u32(self, data: &[u8], off: usize) -> u32 {
37        match self {
38            Endian::Little => le_u32(data, off),
39            Endian::Big => be_u32(data, off),
40        }
41    }
42
43    /// Read an `i32` at `off` in this byte order, or `0` if out of range.
44    #[must_use]
45    pub fn i32(self, data: &[u8], off: usize) -> i32 {
46        self.u32(data, off) as i32
47    }
48
49    /// Read a `u64` at `off` in this byte order, or `0` if out of range.
50    #[must_use]
51    pub fn u64(self, data: &[u8], off: usize) -> u64 {
52        match self {
53            Endian::Little => le_u64(data, off),
54            Endian::Big => be_u64(data, off),
55        }
56    }
57
58    /// Read an `i64` at `off` in this byte order, or `0` if out of range.
59    #[must_use]
60    pub fn i64(self, data: &[u8], off: usize) -> i64 {
61        self.u64(data, off) as i64
62    }
63}
64
65/// Read a little-endian `u16` at `off`, or `0` if out of range.
66#[must_use]
67pub fn le_u16(data: &[u8], off: usize) -> u16 {
68    let mut b = [0u8; 2];
69    if let Some(s) = data.get(off..off.saturating_add(2)) {
70        b.copy_from_slice(s);
71    }
72    u16::from_le_bytes(b)
73}
74
75/// Read a big-endian `u16` at `off`, or `0` if out of range.
76#[must_use]
77pub fn be_u16(data: &[u8], off: usize) -> u16 {
78    let mut b = [0u8; 2];
79    if let Some(s) = data.get(off..off.saturating_add(2)) {
80        b.copy_from_slice(s);
81    }
82    u16::from_be_bytes(b)
83}
84
85/// Read a little-endian `u32` at `off`, or `0` if out of range.
86#[must_use]
87pub fn le_u32(data: &[u8], off: usize) -> u32 {
88    let mut b = [0u8; 4];
89    if let Some(s) = data.get(off..off.saturating_add(4)) {
90        b.copy_from_slice(s);
91    }
92    u32::from_le_bytes(b)
93}
94
95/// Read a big-endian `u32` at `off`, or `0` if out of range.
96#[must_use]
97pub fn be_u32(data: &[u8], off: usize) -> u32 {
98    let mut b = [0u8; 4];
99    if let Some(s) = data.get(off..off.saturating_add(4)) {
100        b.copy_from_slice(s);
101    }
102    u32::from_be_bytes(b)
103}
104
105/// Read a little-endian `u64` at `off`, or `0` if out of range.
106#[must_use]
107pub fn le_u64(data: &[u8], off: usize) -> u64 {
108    let mut b = [0u8; 8];
109    if let Some(s) = data.get(off..off.saturating_add(8)) {
110        b.copy_from_slice(s);
111    }
112    u64::from_le_bytes(b)
113}
114
115/// Read a big-endian `u64` at `off`, or `0` if out of range.
116#[must_use]
117pub fn be_u64(data: &[u8], off: usize) -> u64 {
118    let mut b = [0u8; 8];
119    if let Some(s) = data.get(off..off.saturating_add(8)) {
120        b.copy_from_slice(s);
121    }
122    u64::from_be_bytes(b)
123}
124
125/// Read a single byte at `off`, or `0` if out of range.
126#[must_use]
127pub fn u8_at(data: &[u8], off: usize) -> u8 {
128    data.get(off).copied().unwrap_or(0)
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn readers_decode_both_orders() {
137        let d = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
138        assert_eq!(le_u16(&d, 0), 0x2211);
139        assert_eq!(be_u16(&d, 0), 0x1122);
140        assert_eq!(le_u32(&d, 0), 0x4433_2211);
141        assert_eq!(be_u32(&d, 0), 0x1122_3344);
142        assert_eq!(le_u64(&d, 0), 0x8877_6655_4433_2211);
143        assert_eq!(be_u64(&d, 0), 0x1122_3344_5566_7788);
144        assert_eq!(u8_at(&d, 3), 0x44);
145    }
146
147    #[test]
148    fn readers_yield_zero_out_of_range() {
149        assert_eq!(le_u16(&[0x12], 0), 0);
150        assert_eq!(be_u16(&[0x12], 0), 0);
151        assert_eq!(le_u32(&[0, 0, 0], 0), 0);
152        assert_eq!(be_u32(&[0, 0, 0], 0), 0);
153        assert_eq!(le_u64(&[0; 7], 0), 0);
154        assert_eq!(be_u64(&[0; 7], 0), 0);
155        assert_eq!(u8_at(&[], 0), 0);
156    }
157
158    #[test]
159    fn endian_selector_dispatches_both_orders() {
160        let d = [0xaa, 0xbb, 0xcc, 0xdd, 0x01, 0x02, 0x03, 0x04];
161        assert_eq!(Endian::Little.u16(&d, 0), 0xbbaa);
162        assert_eq!(Endian::Big.u16(&d, 0), 0xaabb);
163        assert_eq!(Endian::Little.u32(&d, 0), 0xddcc_bbaa);
164        assert_eq!(Endian::Big.u32(&d, 0), 0xaabb_ccdd);
165        assert_eq!(Endian::Little.i32(&d, 0), 0xddcc_bbaa_u32 as i32);
166        assert_eq!(Endian::Big.i32(&d, 0), 0xaabb_ccdd_u32 as i32);
167        assert_eq!(Endian::Little.u64(&d, 0), 0x0403_0201_ddcc_bbaa);
168        assert_eq!(Endian::Big.u64(&d, 0), 0xaabb_ccdd_0102_0304);
169        assert_eq!(Endian::Little.i64(&d, 0), 0x0403_0201_ddcc_bbaa_u64 as i64);
170        assert_eq!(Endian::Big.i64(&d, 0), 0xaabb_ccdd_0102_0304_u64 as i64);
171    }
172}