ssz/
encode.rs

1use super::*;
2
3mod impls;
4
5/// Provides SSZ encoding (serialization) via the `as_ssz_bytes(&self)` method.
6///
7/// See `examples/` for manual implementations or the crate root for implementations using
8/// `#[derive(Encode)]`.
9pub trait Encode {
10    /// Returns `true` if this object has a fixed-length.
11    ///
12    /// I.e., there are no variable length items in this object or any of it's contained objects.
13    fn is_ssz_fixed_len() -> bool;
14
15    /// Append the encoding `self` to `buf`.
16    ///
17    /// Note, variable length objects need only to append their "variable length" portion, they do
18    /// not need to provide their offset.
19    fn ssz_append(&self, buf: &mut Vec<u8>);
20
21    /// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
22    ///
23    /// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
24    /// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
25    /// represents their length.
26    fn ssz_fixed_len() -> usize {
27        BYTES_PER_LENGTH_OFFSET
28    }
29
30    /// Returns the size (in bytes) when `self` is serialized.
31    ///
32    /// Returns the same value as `self.as_ssz_bytes().len()` but this method is significantly more
33    /// efficient.
34    fn ssz_bytes_len(&self) -> usize;
35
36    /// Returns the full-form encoding of this object.
37    ///
38    /// The default implementation of this method should suffice for most cases.
39    fn as_ssz_bytes(&self) -> Vec<u8> {
40        let mut buf = vec![];
41
42        self.ssz_append(&mut buf);
43
44        buf
45    }
46}
47
48/// Allow for encoding an ordered series of distinct or indistinct objects as SSZ bytes.
49///
50/// **You must call `finalize(..)` after the final `append(..)` call** to ensure the bytes are
51/// written to `buf`.
52///
53/// ## Example
54///
55/// Use `SszEncoder` to produce identical output to `foo.as_ssz_bytes()`:
56///
57/// ```rust
58/// use ssz_derive::{Encode, Decode};
59/// use ssz::{Decode, Encode, SszEncoder};
60///
61/// #[derive(PartialEq, Debug, Encode, Decode)]
62/// struct Foo {
63///     a: u64,
64///     b: Vec<u16>,
65/// }
66///
67/// fn ssz_encode_example() {
68///     let foo = Foo {
69///         a: 42,
70///         b: vec![1, 3, 3, 7]
71///     };
72///
73///     let mut buf: Vec<u8> = vec![];
74///     let offset = <u64 as Encode>::ssz_fixed_len() + <Vec<u16> as Encode>::ssz_fixed_len();
75///
76///     let mut encoder = SszEncoder::container(&mut buf, offset);
77///
78///     encoder.append(&foo.a);
79///     encoder.append(&foo.b);
80///
81///     encoder.finalize();
82///
83///     assert_eq!(foo.as_ssz_bytes(), buf);
84/// }
85///
86/// ```
87pub struct SszEncoder<'a> {
88    offset: usize,
89    buf: &'a mut Vec<u8>,
90    variable_bytes: Vec<u8>,
91}
92
93impl<'a> SszEncoder<'a> {
94    /// Instantiate a new encoder for encoding a SSZ container.
95    pub fn container(buf: &'a mut Vec<u8>, num_fixed_bytes: usize) -> Self {
96        buf.reserve(num_fixed_bytes);
97
98        Self {
99            offset: num_fixed_bytes,
100            buf,
101            variable_bytes: vec![],
102        }
103    }
104
105    /// Append some `item` to the SSZ bytes.
106    pub fn append<T: Encode>(&mut self, item: &T) {
107        self.append_parameterized(T::is_ssz_fixed_len(), |buf| item.ssz_append(buf))
108    }
109
110    /// Uses `ssz_append` to append the encoding of some item to the SSZ bytes.
111    pub fn append_parameterized<F>(&mut self, is_ssz_fixed_len: bool, ssz_append: F)
112    where
113        F: Fn(&mut Vec<u8>),
114    {
115        if is_ssz_fixed_len {
116            ssz_append(self.buf);
117        } else {
118            self.buf
119                .extend_from_slice(&encode_length(self.offset + self.variable_bytes.len()));
120
121            ssz_append(&mut self.variable_bytes);
122        }
123    }
124
125    /// Write the variable bytes to `self.bytes`.
126    ///
127    /// This method must be called after the final `append(..)` call when serializing
128    /// variable-length items.
129    pub fn finalize(&mut self) -> &mut Vec<u8> {
130        self.buf.append(&mut self.variable_bytes);
131
132        self.buf
133    }
134}
135
136/// Encode `len` as a little-endian byte array of `BYTES_PER_LENGTH_OFFSET` length.
137///
138/// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised.
139pub fn encode_length(len: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
140    // Note: it is possible for `len` to be larger than what can be encoded in
141    // `BYTES_PER_LENGTH_OFFSET` bytes, triggering this debug assertion.
142    //
143    // These are the alternatives to using a `debug_assert` here:
144    //
145    // 1. Use `assert`.
146    // 2. Push an error to the caller (e.g., `Option` or `Result`).
147    // 3. Ignore it completely.
148    //
149    // I have avoided (1) because it's basically a choice between "produce invalid SSZ" or "kill
150    // the entire program". I figure it may be possible for an attacker to trigger this assert and
151    // take the program down -- I think producing invalid SSZ is a better option than this.
152    //
153    // I have avoided (2) because this error will need to be propagated upstream, making encoding a
154    // function which may fail. I don't think this is ergonomic and the upsides don't outweigh the
155    // downsides.
156    //
157    // I figure a `debug_assertion` is better than (3) as it will give us a change to detect the
158    // error during testing.
159    //
160    // If you have a different opinion, feel free to start an issue and tag @paulhauner.
161    debug_assert!(len <= MAX_LENGTH_VALUE);
162
163    let mut bytes = [0; BYTES_PER_LENGTH_OFFSET];
164    bytes.copy_from_slice(&len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET]);
165    bytes
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_encode_length() {
174        assert_eq!(encode_length(0), [0; 4]);
175
176        assert_eq!(encode_length(1), [1, 0, 0, 0]);
177
178        assert_eq!(
179            encode_length(MAX_LENGTH_VALUE),
180            [255; BYTES_PER_LENGTH_OFFSET]
181        );
182    }
183
184    #[test]
185    #[should_panic]
186    #[cfg(debug_assertions)]
187    fn test_encode_length_above_max_debug_panics() {
188        encode_length(MAX_LENGTH_VALUE + 1);
189    }
190
191    #[test]
192    #[cfg(not(debug_assertions))]
193    fn test_encode_length_above_max_not_debug_does_not_panic() {
194        assert_eq!(&encode_length(MAX_LENGTH_VALUE + 1)[..], &[0; 4]);
195    }
196}