ssz/
decode.rs

1use super::*;
2use smallvec::{smallvec, SmallVec};
3use std::cmp::Ordering;
4
5type SmallVec8<T> = SmallVec<[T; 8]>;
6
7pub mod impls;
8pub mod try_from_iter;
9
10/// Returned when SSZ decoding fails.
11#[derive(Debug, PartialEq, Clone)]
12pub enum DecodeError {
13    /// The bytes supplied were too short to be decoded into the specified type.
14    InvalidByteLength { len: usize, expected: usize },
15    /// The given bytes were too short to be read as a length prefix.
16    InvalidLengthPrefix { len: usize, expected: usize },
17    /// A length offset pointed to a byte that was out-of-bounds (OOB).
18    ///
19    /// A bytes may be OOB for the following reasons:
20    ///
21    /// - It is `>= bytes.len()`.
22    /// - When decoding variable length items, the 1st offset points "backwards" into the fixed
23    ///   length items (i.e., `length[0] < BYTES_PER_LENGTH_OFFSET`).
24    /// - When decoding variable-length items, the `n`'th offset was less than the `n-1`'th offset.
25    OutOfBoundsByte { i: usize },
26    /// An offset points “backwards” into the fixed-bytes portion of the message, essentially
27    /// double-decoding bytes that will also be decoded as fixed-length.
28    ///
29    /// <https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#1-Offset-into-fixed-portion>
30    OffsetIntoFixedPortion(usize),
31    /// The first offset does not point to the byte that follows the fixed byte portion,
32    /// essentially skipping a variable-length byte.
33    ///
34    /// <https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#2-Skip-first-variable-byte>
35    OffsetSkipsVariableBytes(usize),
36    /// An offset points to bytes prior to the previous offset. Depending on how you look at it,
37    /// this either double-decodes bytes or makes the first offset a negative-length.
38    ///
39    /// <https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#3-Offsets-are-decreasing>
40    OffsetsAreDecreasing(usize),
41    /// An offset references byte indices that do not exist in the source bytes.
42    ///
43    /// <https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#4-Offsets-are-out-of-bounds>
44    OffsetOutOfBounds(usize),
45    /// A variable-length list does not have a fixed portion that is cleanly divisible by
46    /// `BYTES_PER_LENGTH_OFFSET`.
47    InvalidListFixedBytesLen(usize),
48    /// Some item has a `ssz_fixed_len` of zero. This is illegal.
49    ZeroLengthItem,
50    /// The given bytes were invalid for some application-level reason.
51    BytesInvalid(String),
52    /// The given union selector is out of bounds.
53    UnionSelectorInvalid(u8),
54    /// The given bytes could not be successfully decoded into any variant of the transparent enum.
55    NoMatchingVariant,
56}
57
58/// Performs checks on the `offset` based upon the other parameters provided.
59///
60/// ## Detail
61///
62/// - `offset`: the offset bytes (e.g., result of `read_offset(..)`).
63/// - `previous_offset`: unless this is the first offset in the SSZ object, the value of the
64///   previously-read offset. Used to ensure offsets are not decreasing.
65/// - `num_bytes`: the total number of bytes in the SSZ object. Used to ensure the offset is not
66///   out of bounds.
67/// - `num_fixed_bytes`: the number of fixed-bytes in the struct, if it is known. Used to ensure
68///   that the first offset doesn't skip any variable bytes.
69///
70/// ## References
71///
72/// The checks here are derived from this document:
73///
74/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view
75pub fn sanitize_offset(
76    offset: usize,
77    previous_offset: Option<usize>,
78    num_bytes: usize,
79    num_fixed_bytes: Option<usize>,
80) -> Result<usize, DecodeError> {
81    if num_fixed_bytes.is_some_and(|fixed_bytes| offset < fixed_bytes) {
82        Err(DecodeError::OffsetIntoFixedPortion(offset))
83    } else if previous_offset.is_none()
84        && num_fixed_bytes.is_some_and(|fixed_bytes| offset != fixed_bytes)
85    {
86        Err(DecodeError::OffsetSkipsVariableBytes(offset))
87    } else if offset > num_bytes {
88        Err(DecodeError::OffsetOutOfBounds(offset))
89    } else if previous_offset.is_some_and(|prev| prev > offset) {
90        Err(DecodeError::OffsetsAreDecreasing(offset))
91    } else {
92        Ok(offset)
93    }
94}
95
96/// Provides SSZ decoding (de-serialization) via the `from_ssz_bytes(&bytes)` method.
97///
98/// See `examples/` for manual implementations or the crate root for implementations using
99/// `#[derive(Decode)]`.
100pub trait Decode: Sized {
101    /// Returns `true` if this object has a fixed-length.
102    ///
103    /// I.e., there are no variable length items in this object or any of it's contained objects.
104    fn is_ssz_fixed_len() -> bool;
105
106    /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
107    ///
108    /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
109    /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
110    /// represents their length.
111    fn ssz_fixed_len() -> usize {
112        BYTES_PER_LENGTH_OFFSET
113    }
114
115    /// Attempts to decode `Self` from `bytes`, returning a `DecodeError` on failure.
116    ///
117    /// The supplied bytes must be the exact length required to decode `Self`, excess bytes will
118    /// result in an error.
119    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
120}
121
122#[derive(Copy, Clone, Debug)]
123pub struct Offset {
124    position: usize,
125    offset: usize,
126}
127
128/// Builds an `SszDecoder`.
129///
130/// The purpose of this struct is to split some SSZ bytes into individual slices. The builder is
131/// then converted into a `SszDecoder` which decodes those values into object instances.
132///
133/// See [`SszDecoder`](struct.SszDecoder.html) for usage examples.
134pub struct SszDecoderBuilder<'a> {
135    bytes: &'a [u8],
136    items: SmallVec8<&'a [u8]>,
137    offsets: SmallVec8<Offset>,
138    items_index: usize,
139}
140
141impl<'a> SszDecoderBuilder<'a> {
142    /// Instantiate a new builder that should build a `SszDecoder` over the given `bytes` which
143    /// are assumed to be the SSZ encoding of some object.
144    pub fn new(bytes: &'a [u8]) -> Self {
145        Self {
146            bytes,
147            items: smallvec![],
148            offsets: smallvec![],
149            items_index: 0,
150        }
151    }
152
153    /// Registers a variable-length object as the next item in `bytes`, without specifying the
154    /// actual type.
155    ///
156    /// ## Notes
157    ///
158    /// Use of this function is generally discouraged since it cannot detect if some type changes
159    /// from variable to fixed length.
160    ///
161    /// Use `Self::register_type` wherever possible.
162    pub fn register_anonymous_variable_length_item(&mut self) -> Result<(), DecodeError> {
163        struct Anonymous;
164
165        impl Decode for Anonymous {
166            fn is_ssz_fixed_len() -> bool {
167                false
168            }
169
170            fn from_ssz_bytes(_bytes: &[u8]) -> Result<Self, DecodeError> {
171                unreachable!("Anonymous should never be decoded")
172            }
173        }
174
175        self.register_type::<Anonymous>()
176    }
177
178    /// Declares that some type `T` is the next item in `bytes`.
179    pub fn register_type<T: Decode>(&mut self) -> Result<(), DecodeError> {
180        self.register_type_parameterized(T::is_ssz_fixed_len(), T::ssz_fixed_len())
181    }
182
183    /// Declares that a type with the given parameters is the next item in `bytes`.
184    pub fn register_type_parameterized(
185        &mut self,
186        is_ssz_fixed_len: bool,
187        ssz_fixed_len: usize,
188    ) -> Result<(), DecodeError> {
189        if is_ssz_fixed_len {
190            let start = self.items_index;
191            self.items_index += ssz_fixed_len;
192
193            let slice =
194                self.bytes
195                    .get(start..self.items_index)
196                    .ok_or(DecodeError::InvalidByteLength {
197                        len: self.bytes.len(),
198                        expected: self.items_index,
199                    })?;
200
201            self.items.push(slice);
202        } else {
203            self.offsets.push(Offset {
204                position: self.items.len(),
205                offset: sanitize_offset(
206                    read_offset(&self.bytes[self.items_index..])?,
207                    self.offsets.last().map(|o| o.offset),
208                    self.bytes.len(),
209                    None,
210                )?,
211            });
212
213            // Push an empty slice into items; it will be replaced later.
214            self.items.push(&[]);
215
216            self.items_index += BYTES_PER_LENGTH_OFFSET;
217        }
218
219        Ok(())
220    }
221
222    fn finalize(&mut self) -> Result<(), DecodeError> {
223        if let Some(first_offset) = self.offsets.first().map(|o| o.offset) {
224            // Check to ensure the first offset points to the byte immediately following the
225            // fixed-length bytes.
226            match first_offset.cmp(&self.items_index) {
227                Ordering::Less => return Err(DecodeError::OffsetIntoFixedPortion(first_offset)),
228                Ordering::Greater => {
229                    return Err(DecodeError::OffsetSkipsVariableBytes(first_offset))
230                }
231                Ordering::Equal => (),
232            }
233
234            // Iterate through each pair of offsets, grabbing the slice between each of the offsets.
235            for pair in self.offsets.windows(2) {
236                let a = pair[0];
237                let b = pair[1];
238
239                self.items[a.position] = &self.bytes[a.offset..b.offset];
240            }
241
242            // Handle the last offset, pushing a slice from it's start through to the end of
243            // `self.bytes`.
244            if let Some(last) = self.offsets.last() {
245                self.items[last.position] = &self.bytes[last.offset..]
246            }
247        } else {
248            // If the container is fixed-length, ensure there are no excess bytes.
249            if self.items_index != self.bytes.len() {
250                return Err(DecodeError::InvalidByteLength {
251                    len: self.bytes.len(),
252                    expected: self.items_index,
253                });
254            }
255        }
256
257        Ok(())
258    }
259
260    /// Finalizes the builder, returning a `SszDecoder` that may be used to instantiate objects.
261    pub fn build(mut self) -> Result<SszDecoder<'a>, DecodeError> {
262        self.finalize()?;
263
264        Ok(SszDecoder { items: self.items })
265    }
266}
267
268/// Decodes some slices of SSZ into object instances. Should be instantiated using
269/// [`SszDecoderBuilder`](struct.SszDecoderBuilder.html).
270///
271/// ## Example
272///
273/// ```rust
274/// use ssz_derive::{Encode, Decode};
275/// use ssz::{Decode, Encode, SszDecoder, SszDecoderBuilder};
276///
277/// #[derive(PartialEq, Debug, Encode, Decode)]
278/// struct Foo {
279///     a: u64,
280///     b: Vec<u16>,
281/// }
282///
283/// fn ssz_decoding_example() {
284///     let foo = Foo {
285///         a: 42,
286///         b: vec![1, 3, 3, 7]
287///     };
288///
289///     let bytes = foo.as_ssz_bytes();
290///
291///     let mut builder = SszDecoderBuilder::new(&bytes);
292///
293///     builder.register_type::<u64>().unwrap();
294///     builder.register_type::<Vec<u16>>().unwrap();
295///
296///     let mut decoder = builder.build().unwrap();
297///
298///     let decoded_foo = Foo {
299///         a: decoder.decode_next().unwrap(),
300///         b: decoder.decode_next().unwrap(),
301///     };
302///
303///     assert_eq!(foo, decoded_foo);
304/// }
305///
306/// ```
307pub struct SszDecoder<'a> {
308    items: SmallVec8<&'a [u8]>,
309}
310
311impl<'a> SszDecoder<'a> {
312    /// Decodes the next item.
313    ///
314    /// # Panics
315    ///
316    /// Panics when attempting to decode more items than actually exist.
317    pub fn decode_next<T: Decode>(&mut self) -> Result<T, DecodeError> {
318        self.decode_next_with(|slice| T::from_ssz_bytes(slice))
319    }
320
321    /// Decodes the next item using the provided function.
322    pub fn decode_next_with<T, F>(&mut self, f: F) -> Result<T, DecodeError>
323    where
324        F: FnOnce(&'a [u8]) -> Result<T, DecodeError>,
325    {
326        f(self.items.remove(0))
327    }
328}
329
330/// Takes `bytes`, assuming it is the encoding for a SSZ union, and returns the union-selector and
331/// the body (trailing bytes).
332///
333/// ## Errors
334///
335/// Returns an error if:
336///
337/// - `bytes` is empty.
338/// - the union selector is not a valid value (i.e., larger than the maximum number of variants.
339pub fn split_union_bytes(bytes: &[u8]) -> Result<(UnionSelector, &[u8]), DecodeError> {
340    let selector = bytes
341        .first()
342        .copied()
343        .ok_or(DecodeError::OutOfBoundsByte { i: 0 })
344        .and_then(UnionSelector::new)?;
345    let body = bytes
346        .get(1..)
347        .ok_or(DecodeError::OutOfBoundsByte { i: 1 })?;
348    Ok((selector, body))
349}
350
351/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >=
352/// BYTES_PER_LENGTH_OFFSET`.
353pub fn read_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
354    decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or(
355        DecodeError::InvalidLengthPrefix {
356            len: bytes.len(),
357            expected: BYTES_PER_LENGTH_OFFSET,
358        },
359    )?)
360}
361
362/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() !=
363/// BYTES_PER_LENGTH_OFFSET`.
364fn decode_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
365    let len = bytes.len();
366    let expected = BYTES_PER_LENGTH_OFFSET;
367
368    if len != expected {
369        Err(DecodeError::InvalidLengthPrefix { len, expected })
370    } else {
371        let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default();
372        array.clone_from_slice(bytes);
373
374        Ok(u32::from_le_bytes(array) as usize)
375    }
376}