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