ssz/
decode.rs

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