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}