Skip to main content

xdrs/serialize/
reader.rs

1//! Reading XDR values from a serialization format.
2//!
3//! This module defines the [`Reader`] trait, which provides methods for
4//! reading [XDR][RFC-4506] primitive types (as defined in RFC 4506)
5//! from some underlying serialization format.
6//! Implementations of this trait can be used with [`ReadXDR`](crate::ReadXDR)
7//! to deserialize complex types.
8//!
9//! [RFC-4506]: https://datatracker.ietf.org/doc/html/rfc4506
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14use core::mem::MaybeUninit as MU;
15
16/// A reader that can produce XDR values, some borrowed with lifetime `'r`.
17///
18/// This trait provides methods for reading XDR primitive types from an
19/// underlying serialization format. The lifetime parameter `'r` represents
20/// the lifetime of data borrowed from the reader.
21///
22/// Implementations of this trait are used by [`ReadXDR`](crate::ReadXDR)
23/// to deserialize complex types.
24///
25/// # Safety
26///
27/// [`read_opaque_fixed`](Reader::read_opaque_fixed) must return a slice
28/// of exactly `len` bytes on success. Unsafe code in this crate relies on
29/// that invariant.
30pub unsafe trait Reader<'r>: Sized {
31    /// Reads a signed 64-bit integer.
32    fn read_i64(&mut self) -> ReadResult<i64>;
33
34    /// Reads a signed 32-bit integer.
35    fn read_i32(&mut self) -> ReadResult<i32>;
36
37    /// Reads a signed 16-bit integer.
38    fn read_i16(&mut self) -> ReadResult<i16>;
39
40    /// Reads an unsigned 64-bit integer.
41    fn read_u64(&mut self) -> ReadResult<u64>;
42
43    /// Reads an unsigned 32-bit integer.
44    fn read_u32(&mut self) -> ReadResult<u32>;
45
46    /// Reads an unsigned 16-bit integer.
47    fn read_u16(&mut self) -> ReadResult<u16>;
48
49    /// Reads a 32-bit IEEE 754 floating-point value.
50    fn read_f32(&mut self) -> ReadResult<f32>;
51
52    /// Reads a 64-bit IEEE 754 floating-point value.
53    fn read_f64(&mut self) -> ReadResult<f64>;
54
55    /// Reads a byte array of known length, borrowing the contents from the reader.
56    ///
57    /// On success, the returned slice must have exactly `len` bytes.
58    /// Callers may rely on this invariant for soundness.
59    fn read_opaque_fixed(&mut self, len: usize) -> ReadResult<&'r [u8]>;
60
61    /// Reads a byte sequence of variable length (not exceeding `max`), borrowing the contents from the reader.
62    fn read_opaque_variable(&mut self, max: u32) -> ReadResult<&'r [u8]>;
63
64    /// Reads a boolean value.
65    fn read_bool(&mut self) -> ReadResult<bool> {
66        match self.read_i32()? {
67            0 => Ok(false),
68            1 => Ok(true),
69            _ => Err(ReadError::bad_bool()),
70        }
71    }
72
73    /// Reads a byte array of known length, copying into a provided buffer
74    /// rather than borrowing from the reader.
75    ///
76    /// Panics if `dst.len()` does not equal `len as usize`.
77    ///
78    fn read_opaque_fixed_into<'a>(
79        &mut self,
80        dst: &'a mut [MU<u8>],
81        len: u32,
82    ) -> ReadResult<&'a mut [u8]> {
83        debug_assert_eq!(dst.len(), len as usize);
84
85        const _: () = assert!(core::mem::size_of::<usize>() >= core::mem::size_of::<u32>());
86
87        let data = self.read_opaque_fixed(len as usize)?;
88
89        // Cannot panic: read_opaque_fixed returns exactly `len` bytes, and
90        // dst.len() == len as usize (caller invariant).
91        let initialized = dst.write_copy_of_slice(data);
92
93        Ok(initialized)
94    }
95
96    /// Reads a byte sequence of variable length, copying into a provided buffer.
97    /// rather than borrowing from the reader.
98    ///
99    /// Panics if `dst.len()` is less than the actual length of the input data.
100    ///
101    fn read_opaque_variable_into<'a>(
102        &mut self,
103        dst: &'a mut [MU<u8>],
104        max: u32,
105    ) -> ReadResult<&'a mut [u8]> {
106        debug_assert!(dst.len() >= max as usize);
107
108        let len = self.read_u32()?;
109
110        if len > max {
111            return Err(ReadError::max_length_exceeded());
112        }
113
114        // Panic-safe: dst[..len as usize] has exactly `len` elements.
115        self.read_opaque_fixed_into(&mut dst[..len as usize], len)
116    }
117
118    /// Reads a byte array of known length, returning an owned `Vec<u8>`.
119    ///
120    /// *Requires feature `alloc`.*
121    #[cfg(feature = "alloc")]
122    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
123    fn read_opaque_fixed_owned(&mut self, len: u32) -> ReadResult<Vec<u8>> {
124        let bytes = self.read_opaque_fixed(len as usize)?;
125        let buf = Vec::from(bytes); // copy from input
126        Ok(buf)
127    }
128
129    /// Reads a byte sequence of variable length, returning an owned `Vec<u8>`.
130    ///
131    /// *Requires feature `alloc`.*
132    #[cfg(feature = "alloc")]
133    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
134    fn read_opaque_variable_owned(&mut self, max: u32) -> ReadResult<Vec<u8>> {
135        let bytes = self.read_opaque_variable(max)?;
136        let buf = Vec::from(bytes); // copy from input
137        Ok(buf)
138    }
139}
140
141/// A result type for reader operations.
142pub type ReadResult<T> = core::result::Result<T, ReadError>;
143
144/// Errors that can occur when reading XDR data.
145#[cfg(feature = "detailed-errors")]
146#[derive(Copy, Clone, Debug)]
147pub enum ReadError {
148    /// The input ended before the requested number of bytes could be read.
149    SourceEmpty {
150        /// The number of bytes the reader tried to consume.
151        requested: usize,
152        /// The number of bytes left in the input.
153        remaining: usize,
154    },
155
156    /// Decoding completed but bytes remain unconsumed in the input.
157    SourceRemaining {
158        /// The number of unconsumed bytes left in the input.
159        remaining: u64,
160    },
161
162    /// A CXS integer header byte used a reserved combination of its
163    /// size/value bit fields (see `CXS.md`).
164    InvalidHeaderByte {
165        /// The invalid header byte.
166        h: u8,
167    },
168
169    /// The decoded integer does not fit in the target integer type.
170    IntegerOutOfRange,
171
172    /// A decoded boolean was neither 0 (`FALSE`) nor 1 (`TRUE`).
173    BadBool,
174
175    /// A decoded union discriminant did not match any known variant.
176    BadDiscriminator(i32),
177
178    /// A decoded length exceeds the maximum declared for the
179    /// variable-length array, opaque data, or string.
180    MaxLengthExceeded,
181
182    /// A TXS padding byte was non-zero (rejected unless feature
183    /// `permissive` is enabled).
184    NonZeroPadding,
185
186    /// A decoded string was not valid UTF-8.
187    BadUTF8,
188
189    /// A CXS integer was not encoded in the minimal number of bytes
190    /// (rejected unless feature `permissive` is enabled).
191    NotMinimalEncoding,
192}
193
194/// An error that occurred while reading XDR data.
195///
196/// Compact builds intentionally omit diagnostic details to minimize contract
197/// code size. Enable the default `detailed-errors` feature to retain precise
198/// error variants and payloads.
199#[cfg(not(feature = "detailed-errors"))]
200#[derive(Copy, Clone, Debug)]
201pub struct ReadError;
202
203impl ReadError {
204    #[doc(hidden)]
205    #[inline]
206    pub fn source_empty(requested: usize, remaining: usize) -> Self {
207        #[cfg(feature = "detailed-errors")]
208        {
209            Self::SourceEmpty {
210                requested,
211                remaining,
212            }
213        }
214        #[cfg(not(feature = "detailed-errors"))]
215        {
216            let _ = (requested, remaining);
217            Self
218        }
219    }
220
221    #[doc(hidden)]
222    #[inline]
223    pub fn source_remaining(remaining: u64) -> Self {
224        #[cfg(feature = "detailed-errors")]
225        {
226            Self::SourceRemaining { remaining }
227        }
228        #[cfg(not(feature = "detailed-errors"))]
229        {
230            let _ = remaining;
231            Self
232        }
233    }
234
235    #[doc(hidden)]
236    #[inline]
237    pub fn invalid_header_byte(h: u8) -> Self {
238        #[cfg(feature = "detailed-errors")]
239        {
240            Self::InvalidHeaderByte { h }
241        }
242        #[cfg(not(feature = "detailed-errors"))]
243        {
244            let _ = h;
245            Self
246        }
247    }
248
249    #[doc(hidden)]
250    #[inline]
251    pub fn integer_out_of_range() -> Self {
252        #[cfg(feature = "detailed-errors")]
253        {
254            Self::IntegerOutOfRange
255        }
256        #[cfg(not(feature = "detailed-errors"))]
257        {
258            Self
259        }
260    }
261
262    #[doc(hidden)]
263    #[inline]
264    pub fn bad_bool() -> Self {
265        #[cfg(feature = "detailed-errors")]
266        {
267            Self::BadBool
268        }
269        #[cfg(not(feature = "detailed-errors"))]
270        {
271            Self
272        }
273    }
274
275    #[doc(hidden)]
276    #[inline]
277    pub fn bad_discriminator(discriminant: i32) -> Self {
278        #[cfg(feature = "detailed-errors")]
279        {
280            Self::BadDiscriminator(discriminant)
281        }
282        #[cfg(not(feature = "detailed-errors"))]
283        {
284            let _ = discriminant;
285            Self
286        }
287    }
288
289    #[doc(hidden)]
290    #[inline]
291    pub fn max_length_exceeded() -> Self {
292        #[cfg(feature = "detailed-errors")]
293        {
294            Self::MaxLengthExceeded
295        }
296        #[cfg(not(feature = "detailed-errors"))]
297        {
298            Self
299        }
300    }
301
302    #[doc(hidden)]
303    #[inline]
304    pub fn non_zero_padding() -> Self {
305        #[cfg(feature = "detailed-errors")]
306        {
307            Self::NonZeroPadding
308        }
309        #[cfg(not(feature = "detailed-errors"))]
310        {
311            Self
312        }
313    }
314
315    #[doc(hidden)]
316    #[inline]
317    pub fn bad_utf8() -> Self {
318        #[cfg(feature = "detailed-errors")]
319        {
320            Self::BadUTF8
321        }
322        #[cfg(not(feature = "detailed-errors"))]
323        {
324            Self
325        }
326    }
327
328    #[doc(hidden)]
329    #[inline]
330    pub fn not_minimal_encoding() -> Self {
331        #[cfg(feature = "detailed-errors")]
332        {
333            Self::NotMinimalEncoding
334        }
335        #[cfg(not(feature = "detailed-errors"))]
336        {
337            Self
338        }
339    }
340}
341
342#[cfg(feature = "detailed-errors")]
343impl core::fmt::Display for ReadError {
344    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
345        match self {
346            ReadError::SourceEmpty {
347                requested,
348                remaining,
349            } => write!(
350                f,
351                "unexpected end of input (requested: {requested}, remaining: {remaining})"
352            ),
353            ReadError::SourceRemaining { remaining } => {
354                write!(f, "input remaining after decoding: {remaining} bytes")
355            }
356            ReadError::InvalidHeaderByte { h } => write!(f, "invalid header byte: 0x{h:02x}"),
357            ReadError::IntegerOutOfRange => write!(f, "decoded integer out of range"),
358            ReadError::BadBool => write!(f, "bad bool value"),
359            ReadError::BadDiscriminator(d) => write!(f, "unexpected discriminator value: {d}"),
360            ReadError::MaxLengthExceeded => write!(f, "length exceeds array maximum"),
361            ReadError::NonZeroPadding => write!(f, "non-zero XDR padding"),
362            ReadError::BadUTF8 => write!(f, "bad UTF8"),
363            ReadError::NotMinimalEncoding => write!(f, "not the minimal-size encoding"),
364        }
365    }
366}
367
368#[cfg(not(feature = "detailed-errors"))]
369impl core::fmt::Display for ReadError {
370    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371        f.write_str("XDR decoding failed")
372    }
373}
374
375impl core::error::Error for ReadError {}