Skip to main content

eth_valkyoth_codec/
rlp.rs

1use crate::{
2    DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add, require_exact_consumption,
3    require_range_in_bounds,
4};
5
6const SHORT_STRING_OFFSET: u8 = 0x80;
7const LONG_STRING_OFFSET: u8 = 0xb7;
8const SHORT_LIST_OFFSET: u8 = 0xc0;
9const SHORT_STRING_LIMIT: usize = 55;
10const LENGTH_RADIX: usize = 256;
11
12/// Canonical RLP scalar form used by the decoder.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum RlpScalarForm {
15    /// A single byte in `0x00..=0x7f`, encoded as itself.
16    SingleByte,
17    /// A byte string with a one-byte RLP header.
18    ShortString,
19    /// A byte string with a length-of-length RLP header.
20    LongString,
21}
22
23/// Borrowed RLP scalar byte string.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct RlpScalar<'a> {
26    payload: &'a [u8],
27    encoded_len: usize,
28    header_len: usize,
29    form: RlpScalarForm,
30}
31
32impl<'a> RlpScalar<'a> {
33    /// Returns the decoded scalar payload bytes.
34    #[must_use]
35    pub const fn payload(self) -> &'a [u8] {
36        self.payload
37    }
38
39    /// Returns the total encoded item length in bytes.
40    #[must_use]
41    pub const fn encoded_len(self) -> usize {
42        self.encoded_len
43    }
44
45    /// Returns the RLP header length in bytes.
46    #[must_use]
47    pub const fn header_len(self) -> usize {
48        self.header_len
49    }
50
51    /// Returns the canonical scalar form that was decoded.
52    #[must_use]
53    pub const fn form(self) -> RlpScalarForm {
54        self.form
55    }
56
57    /// Returns true when the decoded payload is empty.
58    #[must_use]
59    pub const fn is_empty(self) -> bool {
60        self.payload.is_empty()
61    }
62}
63
64/// Decodes exactly one canonical RLP scalar byte string.
65pub fn decode_rlp_scalar<'a>(
66    input: &'a [u8],
67    limits: DecodeLimits,
68) -> Result<RlpScalar<'a>, DecodeError> {
69    let mut accumulator = limits.accumulator();
70    let scalar = decode_rlp_scalar_partial(input, &mut accumulator)?;
71    require_exact_consumption(scalar.encoded_len, input.len())?;
72    Ok(scalar)
73}
74
75/// Decodes one canonical RLP scalar byte string from the start of `input`.
76///
77/// Warning: this intentionally accepts trailing bytes. Use
78/// [`decode_rlp_scalar`] when the full input must be consumed.
79///
80/// This helper does not reject trailing bytes. It is intended for future nested
81/// decoders that need to consume one item while sharing cumulative budgets.
82pub fn decode_rlp_scalar_partial<'a>(
83    input: &'a [u8],
84    accumulator: &mut DecodeAccumulator,
85) -> Result<RlpScalar<'a>, DecodeError> {
86    accumulator.check_input_len(input.len())?;
87    accumulator.account_items(1)?;
88
89    let prefix = *input.first().ok_or(DecodeError::Malformed)?;
90    match prefix {
91        0x00..=0x7f => decode_single_byte(input),
92        SHORT_STRING_OFFSET..=LONG_STRING_OFFSET => decode_short_string(input, prefix),
93        0xb8..=0xbf => decode_long_string(input, prefix),
94        SHORT_LIST_OFFSET..=0xff => Err(DecodeError::UnexpectedList),
95    }
96}
97
98fn decode_single_byte(input: &[u8]) -> Result<RlpScalar<'_>, DecodeError> {
99    let payload = input.get(..1).ok_or(DecodeError::OffsetOutOfBounds)?;
100    Ok(RlpScalar {
101        payload,
102        encoded_len: 1,
103        header_len: 0,
104        form: RlpScalarForm::SingleByte,
105    })
106}
107
108fn decode_short_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
109    // The public dispatcher only calls this for prefixes in 0x80..=0xb7.
110    let payload_len = usize::from(prefix.saturating_sub(SHORT_STRING_OFFSET));
111    let payload = payload(input, 1, payload_len)?;
112    if payload_len == 1 && payload.first().is_some_and(|byte| *byte <= 0x7f) {
113        return Err(DecodeError::Malformed);
114    }
115    Ok(RlpScalar {
116        payload,
117        encoded_len: checked_len_add(1, payload_len)?,
118        header_len: 1,
119        form: RlpScalarForm::ShortString,
120    })
121}
122
123fn decode_long_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
124    // The public dispatcher only calls this for prefixes in 0xb8..=0xbf.
125    let len_of_len = usize::from(prefix.saturating_sub(LONG_STRING_OFFSET));
126    let payload_len = parse_payload_len(input, 1, len_of_len)?;
127    if payload_len <= SHORT_STRING_LIMIT {
128        return Err(DecodeError::Malformed);
129    }
130    let header_len = checked_len_add(1, len_of_len)?;
131    let payload = payload(input, header_len, payload_len)?;
132    Ok(RlpScalar {
133        payload,
134        encoded_len: checked_len_add(header_len, payload_len)?,
135        header_len,
136        form: RlpScalarForm::LongString,
137    })
138}
139
140fn parse_payload_len(input: &[u8], offset: usize, len: usize) -> Result<usize, DecodeError> {
141    let end = require_range_in_bounds(offset, len, input.len())?;
142    let bytes = input
143        .get(offset..end)
144        .ok_or(DecodeError::OffsetOutOfBounds)?;
145    if bytes.first().is_some_and(|byte| *byte == 0) {
146        return Err(DecodeError::Malformed);
147    }
148
149    let mut value = 0usize;
150    for byte in bytes {
151        value = value
152            .checked_mul(LENGTH_RADIX)
153            .ok_or(DecodeError::LengthOverflow)?;
154        value = value
155            .checked_add(usize::from(*byte))
156            .ok_or(DecodeError::LengthOverflow)?;
157    }
158    Ok(value)
159}
160
161fn payload(input: &[u8], offset: usize, len: usize) -> Result<&[u8], DecodeError> {
162    let end = require_range_in_bounds(offset, len, input.len())?;
163    input.get(offset..end).ok_or(DecodeError::OffsetOutOfBounds)
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    extern crate std;
170    use std::vec;
171
172    #[test]
173    fn decodes_single_byte_scalar() {
174        assert_eq!(
175            decode_rlp_scalar(&[0x7f], DecodeLimits::TEST_FIXTURE),
176            Ok(RlpScalar {
177                payload: &[0x7f],
178                encoded_len: 1,
179                header_len: 0,
180                form: RlpScalarForm::SingleByte,
181            })
182        );
183    }
184
185    #[test]
186    fn decodes_empty_string() {
187        assert_eq!(
188            decode_rlp_scalar(&[0x80], DecodeLimits::TEST_FIXTURE),
189            Ok(RlpScalar {
190                payload: &[],
191                encoded_len: 1,
192                header_len: 1,
193                form: RlpScalarForm::ShortString,
194            })
195        );
196    }
197
198    #[test]
199    fn decodes_short_string() {
200        assert_eq!(
201            decode_rlp_scalar(&[0x83, b'd', b'o', b'g'], DecodeLimits::TEST_FIXTURE),
202            Ok(RlpScalar {
203                payload: b"dog",
204                encoded_len: 4,
205                header_len: 1,
206                form: RlpScalarForm::ShortString,
207            })
208        );
209    }
210
211    #[test]
212    fn decodes_multi_byte_payload() {
213        assert_eq!(
214            decode_rlp_scalar(&[0x82, 0x04, 0x00], DecodeLimits::TEST_FIXTURE),
215            Ok(RlpScalar {
216                payload: &[0x04, 0x00],
217                encoded_len: 3,
218                header_len: 1,
219                form: RlpScalarForm::ShortString,
220            })
221        );
222    }
223
224    #[test]
225    fn decodes_official_scalar_examples() {
226        let lorem = b"Lorem ipsum dolor sit amet, consectetur adipisicing elit";
227        let mut long = vec![0xb8, 0x38];
228        long.extend_from_slice(lorem);
229
230        let cases: &[(&[u8], &[u8], RlpScalarForm)] = &[
231            (
232                &[0x83, b'd', b'o', b'g'],
233                b"dog",
234                RlpScalarForm::ShortString,
235            ),
236            (&[0x80], b"", RlpScalarForm::ShortString),
237            (&[0x00], &[0x00], RlpScalarForm::SingleByte),
238            (&[0x0f], &[0x0f], RlpScalarForm::SingleByte),
239            (
240                &[0x82, 0x04, 0x00],
241                &[0x04, 0x00],
242                RlpScalarForm::ShortString,
243            ),
244            (long.as_slice(), lorem, RlpScalarForm::LongString),
245        ];
246
247        for (input, expected_payload, expected_form) in cases {
248            assert!(matches!(
249                decode_rlp_scalar(input, DecodeLimits::TEST_FIXTURE),
250                Ok(scalar)
251                    if scalar.payload() == *expected_payload
252                        && scalar.encoded_len() == input.len()
253                        && scalar.form() == *expected_form
254            ));
255        }
256    }
257
258    #[test]
259    fn decodes_long_string() {
260        let mut input = vec![0xb8, 56];
261        input.extend_from_slice(&[b'a'; 56]);
262
263        assert!(matches!(
264            decode_rlp_scalar(&input, DecodeLimits::TEST_FIXTURE),
265            Ok(scalar)
266                if scalar.payload().len() == 56
267                    && scalar.encoded_len() == 58
268                    && scalar.header_len() == 2
269                    && scalar.form() == RlpScalarForm::LongString
270        ));
271    }
272
273    #[test]
274    fn partial_decoder_leaves_trailing_bytes_to_caller() {
275        let mut accumulator = DecodeLimits::TEST_FIXTURE.accumulator();
276
277        assert!(matches!(
278            decode_rlp_scalar_partial(&[0x83, b'd', b'o', b'g', 0x80], &mut accumulator),
279            Ok(scalar) if scalar.payload() == b"dog" && scalar.encoded_len() == 4
280        ));
281        assert_eq!(accumulator.total_items(), 1);
282    }
283
284    #[test]
285    fn exact_decoder_rejects_trailing_bytes() {
286        assert_eq!(
287            decode_rlp_scalar(&[0x83, b'd', b'o', b'g', 0x80], DecodeLimits::TEST_FIXTURE),
288            Err(DecodeError::TrailingBytes)
289        );
290    }
291
292    #[test]
293    fn rejects_empty_input() {
294        assert_eq!(
295            decode_rlp_scalar(&[], DecodeLimits::TEST_FIXTURE),
296            Err(DecodeError::Malformed)
297        );
298    }
299
300    #[test]
301    fn rejects_short_string_missing_payload() {
302        assert_eq!(
303            decode_rlp_scalar(&[0x82, 0x04], DecodeLimits::TEST_FIXTURE),
304            Err(DecodeError::OffsetOutOfBounds)
305        );
306    }
307
308    #[test]
309    fn rejects_noncanonical_single_byte_string() {
310        assert_eq!(
311            decode_rlp_scalar(&[0x81, 0x7f], DecodeLimits::TEST_FIXTURE),
312            Err(DecodeError::Malformed)
313        );
314    }
315
316    #[test]
317    fn rejects_long_string_missing_length_bytes() {
318        assert_eq!(
319            decode_rlp_scalar(&[0xb9, 0x01], DecodeLimits::TEST_FIXTURE),
320            Err(DecodeError::OffsetOutOfBounds)
321        );
322    }
323
324    #[test]
325    fn rejects_long_string_missing_payload() {
326        assert_eq!(
327            decode_rlp_scalar(&[0xb8, 56, b'a'], DecodeLimits::TEST_FIXTURE),
328            Err(DecodeError::OffsetOutOfBounds)
329        );
330    }
331
332    #[test]
333    fn rejects_long_string_with_leading_zero_length() {
334        let mut input = vec![0xb9, 0, 56];
335        input.extend_from_slice(&[b'a'; 56]);
336
337        assert_eq!(
338            decode_rlp_scalar(&input, DecodeLimits::TEST_FIXTURE),
339            Err(DecodeError::Malformed)
340        );
341    }
342
343    #[test]
344    fn rejects_long_string_length_overflow() {
345        let input = [0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
346
347        assert_eq!(
348            decode_rlp_scalar(&input, DecodeLimits::TEST_FIXTURE),
349            Err(DecodeError::LengthOverflow)
350        );
351    }
352
353    #[test]
354    fn rejects_long_string_used_for_short_payload() {
355        let mut input = vec![0xb8, 55];
356        input.extend_from_slice(&[b'a'; 55]);
357
358        assert_eq!(
359            decode_rlp_scalar(&input, DecodeLimits::TEST_FIXTURE),
360            Err(DecodeError::Malformed)
361        );
362    }
363
364    #[test]
365    fn rejects_list_prefix_for_scalar_decoder() {
366        assert_eq!(
367            decode_rlp_scalar(&[0xc0], DecodeLimits::TEST_FIXTURE),
368            Err(DecodeError::UnexpectedList)
369        );
370    }
371
372    #[test]
373    fn enforces_input_budget() {
374        let limits = DecodeLimits {
375            max_input_bytes: 1,
376            ..DecodeLimits::TEST_FIXTURE
377        };
378
379        assert_eq!(
380            decode_rlp_scalar(&[0x81, 0x80], limits),
381            Err(DecodeError::InputTooLarge)
382        );
383    }
384
385    #[test]
386    fn enforces_item_budget() {
387        let limits = DecodeLimits {
388            max_total_items: 0,
389            ..DecodeLimits::TEST_FIXTURE
390        };
391
392        assert_eq!(
393            decode_rlp_scalar(&[0x80], limits),
394            Err(DecodeError::ItemCountExceeded)
395        );
396    }
397}