Skip to main content

eth_valkyoth_codec/rlp/
list.rs

1use crate::{
2    DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add, require_exact_consumption,
3    require_range_in_bounds,
4};
5
6use super::{
7    LONG_LIST_OFFSET, RlpScalar, RlpScalarForm, SHORT_LIST_OFFSET, SHORT_STRING_LIMIT,
8    decode_long_string, decode_short_string, parse_payload_len, payload,
9};
10
11/// Hard cap on RLP list traversal depth regardless of the active decode limits.
12///
13/// Inputs nested deeper than this return [`DecodeError::NestingTooDeep`] even
14/// when [`DecodeLimits::max_nesting_depth`] is higher.
15pub const MAX_RLP_LIST_TRAVERSAL_DEPTH: usize = 128;
16
17/// Canonical RLP list form used by the decoder.
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum RlpListForm {
20    /// A list with a one-byte RLP header.
21    ShortList,
22    /// A list with a length-of-length RLP header.
23    LongList,
24}
25
26/// Borrowed RLP list payload.
27#[derive(Clone, Copy, Debug)]
28pub struct RlpList<'a> {
29    payload: &'a [u8],
30    encoded_len: usize,
31    header_len: usize,
32    item_count: usize,
33    form: RlpListForm,
34    limits: DecodeLimits,
35}
36
37impl<'a> RlpList<'a> {
38    /// Returns the concatenated encoded child items.
39    #[must_use]
40    pub const fn payload(self) -> &'a [u8] {
41        self.payload
42    }
43
44    /// Returns the total encoded list length in bytes.
45    #[must_use]
46    pub const fn encoded_len(self) -> usize {
47        self.encoded_len
48    }
49
50    /// Returns the RLP header length in bytes.
51    #[must_use]
52    pub const fn header_len(self) -> usize {
53        self.header_len
54    }
55
56    /// Returns the number of immediate child items.
57    #[must_use]
58    pub const fn item_count(self) -> usize {
59        self.item_count
60    }
61
62    /// Returns the canonical list form that was decoded.
63    #[must_use]
64    pub const fn form(self) -> RlpListForm {
65        self.form
66    }
67
68    /// Returns true when the list has no child items.
69    #[must_use]
70    pub const fn is_empty(self) -> bool {
71        self.item_count == 0
72    }
73
74    /// Returns an iterator over the immediate child items in this list.
75    #[must_use]
76    pub const fn items(self) -> RlpListItems<'a> {
77        RlpListItems {
78            input: self.payload,
79            cursor: 0,
80            remaining: self.item_count,
81            limits: self.limits,
82        }
83    }
84}
85
86impl PartialEq for RlpList<'_> {
87    fn eq(&self, other: &Self) -> bool {
88        self.payload == other.payload
89            && self.encoded_len == other.encoded_len
90            && self.header_len == other.header_len
91            && self.item_count == other.item_count
92            && self.form == other.form
93    }
94}
95
96impl Eq for RlpList<'_> {}
97
98/// Borrowed RLP item yielded by [`RlpListItems`].
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub enum RlpItem<'a> {
101    /// A scalar byte string item.
102    Scalar(RlpScalar<'a>),
103    /// A list item.
104    List(RlpList<'a>),
105}
106
107impl<'a> RlpItem<'a> {
108    /// Returns the total encoded item length in bytes.
109    #[must_use]
110    pub const fn encoded_len(self) -> usize {
111        match self {
112            Self::Scalar(scalar) => scalar.encoded_len(),
113            Self::List(list) => list.encoded_len(),
114        }
115    }
116
117    /// Returns the RLP header length in bytes.
118    #[must_use]
119    pub const fn header_len(self) -> usize {
120        match self {
121            Self::Scalar(scalar) => scalar.header_len(),
122            Self::List(list) => list.header_len(),
123        }
124    }
125
126    /// Returns true when this item is a scalar byte string.
127    #[must_use]
128    pub const fn is_scalar(self) -> bool {
129        matches!(self, Self::Scalar(_))
130    }
131
132    /// Returns true when this item is a list.
133    #[must_use]
134    pub const fn is_list(self) -> bool {
135        matches!(self, Self::List(_))
136    }
137
138    /// Returns the scalar item, if this item is a scalar byte string.
139    #[must_use]
140    pub const fn as_scalar(&self) -> Option<RlpScalar<'a>> {
141        match self {
142            Self::Scalar(scalar) => Some(*scalar),
143            Self::List(_) => None,
144        }
145    }
146
147    /// Returns the list item, if this item is a list.
148    #[must_use]
149    pub const fn as_list(&self) -> Option<RlpList<'a>> {
150        match self {
151            Self::Scalar(_) => None,
152            Self::List(list) => Some(*list),
153        }
154    }
155}
156
157/// Iterator over immediate child items in a decoded RLP list.
158#[derive(Clone, Debug)]
159pub struct RlpListItems<'a> {
160    input: &'a [u8],
161    cursor: usize,
162    remaining: usize,
163    limits: DecodeLimits,
164}
165
166impl<'a> RlpListItems<'a> {
167    /// Returns the number of child items not yielded yet.
168    #[must_use]
169    pub const fn remaining(&self) -> usize {
170        self.remaining
171    }
172}
173
174impl<'a> Iterator for RlpListItems<'a> {
175    type Item = Result<RlpItem<'a>, DecodeError>;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        if self.remaining == 0 {
179            return None;
180        }
181
182        match parse_item(self.input, self.cursor, self.input.len())
183            .and_then(|item| item.into_rlp_item(self.input, self.cursor, self.limits))
184        {
185            Ok((item, next_cursor)) => {
186                self.cursor = next_cursor;
187                self.remaining = self.remaining.saturating_sub(1);
188                Some(Ok(item))
189            }
190            Err(error) => {
191                self.remaining = 0;
192                Some(Err(error))
193            }
194        }
195    }
196
197    fn size_hint(&self) -> (usize, Option<usize>) {
198        (self.remaining, Some(self.remaining))
199    }
200}
201
202impl ExactSizeIterator for RlpListItems<'_> {}
203
204impl core::iter::FusedIterator for RlpListItems<'_> {}
205
206/// Decodes exactly one canonical RLP list.
207pub fn decode_rlp_list<'a>(
208    input: &'a [u8],
209    limits: DecodeLimits,
210) -> Result<RlpList<'a>, DecodeError> {
211    let mut accumulator = limits.accumulator();
212    let list = decode_rlp_list_partial(input, &mut accumulator)?;
213    require_exact_consumption(list.encoded_len, input.len())?;
214    Ok(list)
215}
216
217/// Decodes one canonical RLP list from the start of `input`.
218///
219/// Warning: this intentionally accepts trailing bytes. Use [`decode_rlp_list`]
220/// when the full input must be consumed.
221///
222/// The input-length budget check applies to the full `input` slice, not only
223/// the consumed list bytes. Callers that decode from a larger outer buffer must
224/// pre-slice before calling this helper.
225pub fn decode_rlp_list_partial<'a>(
226    input: &'a [u8],
227    accumulator: &mut DecodeAccumulator,
228) -> Result<RlpList<'a>, DecodeError> {
229    accumulator.check_input_len(input.len())?;
230    accumulator.account_items(1)?;
231    accumulator.check_nesting_depth(1)?;
232
233    let prefix = *input.first().ok_or(DecodeError::Malformed)?;
234    let list = match prefix {
235        SHORT_LIST_OFFSET..=LONG_LIST_OFFSET => {
236            decode_short_list(input, prefix, accumulator.limits())?
237        }
238        0xf8..=0xff => decode_long_list(input, prefix, accumulator.limits())?,
239        0x00..=0xbf => return Err(DecodeError::UnexpectedScalar),
240    };
241    let item_count = validate_list_payload(list.payload, accumulator)?;
242    accumulator.check_list_count(item_count)?;
243    Ok(RlpList { item_count, ..list })
244}
245
246fn decode_short_list(
247    input: &[u8],
248    prefix: u8,
249    limits: DecodeLimits,
250) -> Result<RlpList<'_>, DecodeError> {
251    let payload_len = usize::from(prefix.saturating_sub(SHORT_LIST_OFFSET));
252    let payload = payload(input, 1, payload_len)?;
253    Ok(RlpList {
254        payload,
255        encoded_len: checked_len_add(1, payload_len)?,
256        header_len: 1,
257        item_count: 0,
258        form: RlpListForm::ShortList,
259        limits,
260    })
261}
262
263fn decode_long_list(
264    input: &[u8],
265    prefix: u8,
266    limits: DecodeLimits,
267) -> Result<RlpList<'_>, DecodeError> {
268    let len_of_len = usize::from(prefix.saturating_sub(LONG_LIST_OFFSET));
269    let payload_len = parse_payload_len(input, 1, len_of_len)?;
270    if payload_len <= SHORT_STRING_LIMIT {
271        return Err(DecodeError::Malformed);
272    }
273    let header_len = checked_len_add(1, len_of_len)?;
274    let payload = payload(input, header_len, payload_len)?;
275    Ok(RlpList {
276        payload,
277        encoded_len: checked_len_add(header_len, payload_len)?,
278        header_len,
279        item_count: 0,
280        form: RlpListForm::LongList,
281        limits,
282    })
283}
284
285#[derive(Clone, Copy)]
286struct ListFrame {
287    end: usize,
288    count: usize,
289}
290
291fn validate_list_payload(
292    input: &[u8],
293    accumulator: &mut DecodeAccumulator,
294) -> Result<usize, DecodeError> {
295    let mut stack = [ListFrame { end: 0, count: 0 }; MAX_RLP_LIST_TRAVERSAL_DEPTH];
296    let root = stack.get_mut(0).ok_or(DecodeError::NestingTooDeep)?;
297    *root = ListFrame {
298        end: input.len(),
299        count: 0,
300    };
301    let mut depth = 1usize;
302    let mut cursor = 0usize;
303
304    loop {
305        let frame_index = depth.checked_sub(1).ok_or(DecodeError::LengthOverflow)?;
306        let frame = stack
307            .get_mut(frame_index)
308            .ok_or(DecodeError::NestingTooDeep)?;
309
310        if cursor == frame.end {
311            let finished_count = frame.count;
312            accumulator.check_list_count(finished_count)?;
313            depth = depth.checked_sub(1).ok_or(DecodeError::LengthOverflow)?;
314            if depth == 0 {
315                return Ok(finished_count);
316            }
317            continue;
318        }
319        if cursor > frame.end {
320            return Err(DecodeError::OffsetOutOfBounds);
321        }
322
323        frame.count = checked_len_add(frame.count, 1)?;
324        accumulator.check_list_count(frame.count)?;
325
326        let item = parse_item(input, cursor, frame.end)?;
327        accumulator.account_items(1)?;
328        if matches!(item.kind, ParsedItemKind::List(_)) {
329            let next_depth = checked_len_add(depth, 1)?;
330            accumulator.check_nesting_depth(next_depth)?;
331            if next_depth > MAX_RLP_LIST_TRAVERSAL_DEPTH {
332                return Err(DecodeError::NestingTooDeep);
333            }
334            let child_index = next_depth
335                .checked_sub(1)
336                .ok_or(DecodeError::LengthOverflow)?;
337            let child = stack
338                .get_mut(child_index)
339                .ok_or(DecodeError::NestingTooDeep)?;
340            *child = ListFrame {
341                end: item.payload_end,
342                count: 0,
343            };
344            depth = next_depth;
345            cursor = item.payload_start;
346        } else {
347            cursor = item.item_end;
348        }
349    }
350}
351
352struct ParsedItem {
353    kind: ParsedItemKind,
354    header_len: usize,
355    payload_start: usize,
356    payload_end: usize,
357    item_end: usize,
358}
359
360impl ParsedItem {
361    fn into_rlp_item<'a>(
362        self,
363        input: &'a [u8],
364        offset: usize,
365        limits: DecodeLimits,
366    ) -> Result<(RlpItem<'a>, usize), DecodeError> {
367        let payload = input
368            .get(self.payload_start..self.payload_end)
369            .ok_or(DecodeError::OffsetOutOfBounds)?;
370        let encoded_len = self
371            .item_end
372            .checked_sub(offset)
373            .ok_or(DecodeError::LengthOverflow)?;
374        let item = match self.kind {
375            ParsedItemKind::Scalar(form) => RlpItem::Scalar(RlpScalar {
376                payload,
377                encoded_len,
378                header_len: self.header_len,
379                form,
380            }),
381            ParsedItemKind::List(form) => RlpItem::List(RlpList {
382                payload,
383                encoded_len,
384                header_len: self.header_len,
385                item_count: count_immediate_items(payload, limits)?,
386                form,
387                limits,
388            }),
389        };
390        Ok((item, self.item_end))
391    }
392}
393
394#[derive(Clone, Copy)]
395enum ParsedItemKind {
396    Scalar(RlpScalarForm),
397    List(RlpListForm),
398}
399
400fn parse_item(
401    input: &[u8],
402    offset: usize,
403    container_end: usize,
404) -> Result<ParsedItem, DecodeError> {
405    let local = input
406        .get(offset..container_end)
407        .ok_or(DecodeError::OffsetOutOfBounds)?;
408    let prefix = *local.first().ok_or(DecodeError::Malformed)?;
409    let (kind, header_len, payload_len) = match prefix {
410        0x00..=0x7f => (ParsedItemKind::Scalar(RlpScalarForm::SingleByte), 0, 1),
411        super::SHORT_STRING_OFFSET..=super::LONG_STRING_OFFSET => {
412            let scalar = decode_short_string(local, prefix)?;
413            (
414                ParsedItemKind::Scalar(scalar.form()),
415                scalar.header_len(),
416                scalar.payload().len(),
417            )
418        }
419        0xb8..=0xbf => {
420            let scalar = decode_long_string(local, prefix)?;
421            (
422                ParsedItemKind::Scalar(scalar.form()),
423                scalar.header_len(),
424                scalar.payload().len(),
425            )
426        }
427        SHORT_LIST_OFFSET..=LONG_LIST_OFFSET => {
428            let payload_len = usize::from(prefix.saturating_sub(SHORT_LIST_OFFSET));
429            payload(local, 1, payload_len)?;
430            (ParsedItemKind::List(RlpListForm::ShortList), 1, payload_len)
431        }
432        0xf8..=0xff => {
433            let len_of_len = usize::from(prefix.saturating_sub(LONG_LIST_OFFSET));
434            let payload_len = parse_payload_len(local, 1, len_of_len)?;
435            if payload_len <= SHORT_STRING_LIMIT {
436                return Err(DecodeError::Malformed);
437            }
438            let header_len = checked_len_add(1, len_of_len)?;
439            payload(local, header_len, payload_len)?;
440            (
441                ParsedItemKind::List(RlpListForm::LongList),
442                header_len,
443                payload_len,
444            )
445        }
446    };
447    let payload_start = checked_len_add(offset, header_len)?;
448    let payload_end = checked_len_add(payload_start, payload_len)?;
449    let item_end = if header_len == 0 {
450        checked_len_add(offset, 1)?
451    } else {
452        payload_end
453    };
454    require_range_in_bounds(offset, item_end.saturating_sub(offset), container_end)?;
455    Ok(ParsedItem {
456        kind,
457        header_len,
458        payload_start,
459        payload_end,
460        item_end,
461    })
462}
463
464fn count_immediate_items(input: &[u8], limits: DecodeLimits) -> Result<usize, DecodeError> {
465    // Iteration-phase recounting is bounded independently from the
466    // decode-phase accumulator used by partial streaming callers.
467    let mut accumulator = limits.accumulator();
468    accumulator.check_input_len(input.len())?;
469    let mut count = 0usize;
470    let mut cursor = 0usize;
471    while cursor < input.len() {
472        let item = parse_item(input, cursor, input.len())?;
473        count = checked_len_add(count, 1)?;
474        accumulator.account_items(1)?;
475        accumulator.check_list_count(count)?;
476        cursor = item.item_end;
477    }
478    if cursor == input.len() {
479        Ok(count)
480    } else {
481        Err(DecodeError::OffsetOutOfBounds)
482    }
483}