Skip to main content

xdrs/cxs/
buf_reader.rs

1//! CXS buffer reader implementation.
2//!
3//! This module provides [`BufReader`], which implements [`Reader`] for reading
4//! CXS-encoded XDR data from a byte slice.
5
6#[cfg(not(feature = "permissive"))]
7use super::limits::*;
8
9use crate::serialize::reader::*;
10
11/// Read CXS-encoded XDR data from a byte slice.
12///
13/// This reader implements the [`Reader`] trait for the CXS (Compact XDR
14/// Serialization) format. It reads data from a byte slice and tracks the
15/// current position.
16pub struct BufReader<'r> {
17    data: &'r [u8],
18    pos: usize,
19}
20
21impl<'a> BufReader<'a> {
22    /// Creates a new `BufReader` that reads from the given byte slice.
23    ///
24    /// The reader starts at position 0.
25    pub fn new(data: &'a [u8]) -> Self {
26        let pos = 0;
27        Self { data, pos }
28    }
29
30    /// Returns the current read position in the buffer.
31    pub fn position(&self) -> usize {
32        self.pos
33    }
34
35    /// Returns the number of bytes remaining to be read.
36    pub fn remaining(&self) -> usize {
37        self.data.len() - self.pos
38    }
39
40    /// Returns a slice of the remaining unread bytes.
41    pub fn remaining_slice(&self) -> &'a [u8] {
42        &self.data[self.pos..]
43    }
44
45    #[inline]
46    fn get_input(&mut self, requested: u32) -> ReadResult<&'a [u8]> {
47        let requested = requested as usize;
48        let remaining = self.data.len() - self.pos;
49        if requested > remaining {
50            // Without the hint, LLVM duplicates the error return into every
51            // fixed-width arm of read_unsigned/read_signed: +36 B of RV32
52            // .text per contract. Native codegen is left unchanged.
53            #[cfg(all(target_arch = "riscv32", target_os = "none"))]
54            core::hint::cold_path();
55            return Err(ReadError::source_empty(requested, remaining));
56        }
57        // The cursor is always within data. A request that fits in the
58        // remaining input also makes this addition safe from overflow.
59        let extent = self.pos + requested;
60
61        // SAFETY: requested <= remaining implies pos <= extent <= data.len().
62        let buf = unsafe { self.data.get_unchecked(self.pos..extent) };
63
64        self.pos = extent;
65
66        Ok(buf)
67    }
68
69    #[inline]
70    fn read_byte(&mut self) -> ReadResult<u8> {
71        let byte = self.get_input(1)?;
72
73        // SAFETY: get_input(1) returns exactly 1 byte.
74        Ok(unsafe { *byte.get_unchecked(0) })
75    }
76
77    #[cfg_attr(
78        any(
79            target_family = "wasm",
80            all(target_arch = "riscv32", target_os = "none"),
81        ),
82        inline(never)
83    )]
84    #[cfg_attr(
85        not(any(
86            target_family = "wasm",
87            all(target_arch = "riscv32", target_os = "none"),
88        )),
89        inline(always)
90    )]
91    fn read_signed(&mut self) -> ReadResult<i64> {
92        let h = self.read_byte()?;
93        let s = (h & 0xc0) >> 6;
94        let v = h & !0xc0;
95        let n: i64;
96
97        if v == 63 {
98            // The number is encoded in the following bytes
99
100            // SAFETY: get_input(N) returns exactly N initialized bytes
101            // on success, so the pointer from as_ptr() is valid for an
102            // N-byte read_unaligned.
103            n = match s {
104                0 => self.read_byte()? as i8 as i64,
105                1 => i16::from_le(unsafe {
106                    core::ptr::read_unaligned(self.get_input(2)?.as_ptr().cast::<i16>())
107                }) as i64,
108                2 => i32::from_le(unsafe {
109                    core::ptr::read_unaligned(self.get_input(4)?.as_ptr().cast::<i32>())
110                }) as i64,
111                3 => i64::from_le(unsafe {
112                    core::ptr::read_unaligned(self.get_input(8)?.as_ptr().cast::<i64>())
113                }),
114                _ => unreachable!(),
115            };
116
117            #[cfg(not(feature = "permissive"))]
118            {
119                // validate that the smallest possible encoding was used
120
121                const N_BYTE_REPR_MINMAX: [(i64, i64); 4] = [
122                    (IH_MAX as i64 + 1, IH_MIN as i64 - 1),
123                    (i8::MAX as i64 + 1, i8::MIN as i64 - 1),
124                    (i16::MAX as i64 + 1, i16::MIN as i64 - 1),
125                    (i32::MAX as i64 + 1, i32::MIN as i64 - 1),
126                ];
127                let (repr_min, repr_max) = N_BYTE_REPR_MINMAX[s as usize];
128
129                if n > 0 {
130                    if n < repr_min {
131                        return Err(ReadError::not_minimal_encoding());
132                    }
133                } else if n > repr_max {
134                    return Err(ReadError::not_minimal_encoding());
135                }
136            }
137        } else {
138            // The number is the 6-bit, one's-complement signed integer in `v`
139
140            if s != 0 {
141                return Err(ReadError::invalid_header_byte(h));
142            }
143
144            if v & 0b10_0000 != 0 {
145                // The sign-bit is set, so obtain the number by inverting and
146                // negating:
147                n = -(((!v) & 0b11_1111) as i64);
148            } else {
149                n = v as i64;
150            }
151        }
152
153        Ok(n)
154    }
155
156    #[cfg_attr(
157        any(
158            target_family = "wasm",
159            all(target_arch = "riscv32", target_os = "none"),
160        ),
161        inline(never)
162    )]
163    #[cfg_attr(
164        not(any(
165            target_family = "wasm",
166            all(target_arch = "riscv32", target_os = "none"),
167        )),
168        inline(always)
169    )]
170    fn read_unsigned(&mut self) -> ReadResult<u64> {
171        let h = self.read_byte()?;
172        let s = (h & 0xc0) >> 6;
173        let v = h & !0xc0;
174
175        if v == 63 {
176            // The number is encoded in the following bytes
177
178            // SAFETY: get_input(N) returns exactly N initialized bytes
179            // on success, so the pointer from as_ptr() is valid for an
180            // N-byte read_unaligned.
181            let uint = match s {
182                0 => self.read_byte()? as u64,
183                1 => u16::from_le(unsafe {
184                    core::ptr::read_unaligned(self.get_input(2)?.as_ptr().cast::<u16>())
185                }) as u64,
186                2 => u32::from_le(unsafe {
187                    core::ptr::read_unaligned(self.get_input(4)?.as_ptr().cast::<u32>())
188                }) as u64,
189                3 => u64::from_le(unsafe {
190                    core::ptr::read_unaligned(self.get_input(8)?.as_ptr().cast::<u64>())
191                }),
192                _ => unreachable!(),
193            };
194
195            #[cfg(not(feature = "permissive"))]
196            {
197                // validate that the smallest possible encoding was used
198
199                const N_BYTE_REPR_MIN: [u64; 4] = [
200                    63,
201                    u8::MAX as u64 + 1,
202                    u16::MAX as u64 + 1,
203                    u32::MAX as u64 + 1,
204                ];
205                let repr_min = N_BYTE_REPR_MIN[s as usize];
206
207                if uint < repr_min {
208                    return Err(ReadError::not_minimal_encoding());
209                }
210            }
211
212            Ok(uint)
213        } else {
214            // The number is the 6-bit unsigned integer in `v`
215
216            if s != 0 {
217                return Err(ReadError::invalid_header_byte(h));
218            }
219
220            Ok(v as u64)
221        }
222    }
223}
224
225macro_rules! read_signed {
226    { $( [$f:ident, $ty:ty] ),* $(,)? } => { $(
227
228        #[inline]
229        fn $f(&mut self) -> ReadResult<$ty> {
230            let n = self.read_signed()?;
231            let n = n
232                .try_into()
233                .map_err(|_| ReadError::integer_out_of_range())?;
234            Ok(n)
235        }
236
237    )* };
238}
239
240macro_rules! read_unsigned {
241    { $( [$f:ident, $ty:ty] ),* $(,)? } => { $(
242
243        #[inline]
244        fn $f(&mut self) -> ReadResult<$ty> {
245            let n = self.read_unsigned()?;
246            let n = n
247                .try_into()
248                .map_err(|_| ReadError::integer_out_of_range())?;
249            Ok(n)
250        }
251
252    )* };
253}
254
255// SAFETY: read_opaque_fixed returns exactly `len` bytes (or an error).
256unsafe impl<'r> Reader<'r> for BufReader<'r> {
257    read_signed! {
258        [read_i64, i64],
259        [read_i32, i32],
260        [read_i16, i16],
261    }
262
263    read_unsigned! {
264        [read_u64, u64],
265        [read_u32, u32],
266        [read_u16, u16],
267    }
268
269    /// Reads a 32-bit IEEE 754 float from raw little-endian bytes.
270    ///
271    /// CXS encodes floats as fixed-size raw bytes (not variable-width
272    /// compressed). Little-endian byte order follows CXS convention
273    /// (not RFC 4506 big-endian).
274    #[inline]
275    fn read_f32(&mut self) -> ReadResult<f32> {
276        let input = self.read_opaque_fixed(4)?;
277        let mut bytes = [0u8; 4];
278        // SAFETY: read_opaque_fixed(4) returns exactly 4 bytes.
279        unsafe { core::ptr::copy_nonoverlapping(input.as_ptr(), bytes.as_mut_ptr(), 4) };
280        Ok(f32::from_le_bytes(bytes))
281    }
282
283    /// Reads a 64-bit IEEE 754 double from raw little-endian bytes.
284    ///
285    /// CXS encodes floats as fixed-size raw bytes (not variable-width
286    /// compressed). Little-endian byte order follows CXS convention
287    /// (not RFC 4506 big-endian).
288    #[inline]
289    fn read_f64(&mut self) -> ReadResult<f64> {
290        let input = self.read_opaque_fixed(8)?;
291        let mut bytes = [0u8; 8];
292        // SAFETY: read_opaque_fixed(8) returns exactly 8 bytes.
293        unsafe { core::ptr::copy_nonoverlapping(input.as_ptr(), bytes.as_mut_ptr(), 8) };
294        Ok(f64::from_le_bytes(bytes))
295    }
296
297    #[inline]
298    fn read_opaque_fixed(&mut self, len: usize) -> ReadResult<&'r [u8]> {
299        // SAFETY precondition: len must fit in u32 (all callers originate
300        // from u32 values: read_u32, ValidU32, or small literals).
301        debug_assert!(len <= u32::MAX as usize);
302        self.get_input(len as u32)
303    }
304
305    #[inline(always)]
306    fn read_opaque_variable(&mut self, max: u32) -> ReadResult<&'r [u8]> {
307        let len = self.read_u32()?;
308
309        if len > max {
310            return Err(ReadError::max_length_exceeded());
311        }
312
313        self.read_opaque_fixed(len as usize)
314    }
315}