Skip to main content

eth_valkyoth_codec/
rlp.rs

1mod list;
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7    DecodeAccumulator, DecodeError, DecodeLimits, checked_len_add, require_exact_consumption,
8    require_range_in_bounds,
9};
10
11pub use list::{
12    MAX_RLP_LIST_TRAVERSAL_DEPTH, RlpItem, RlpList, RlpListForm, RlpListItems, decode_rlp_list,
13    decode_rlp_list_partial,
14};
15
16pub(super) const SHORT_STRING_OFFSET: u8 = 0x80;
17/// Base for computing `len_of_len` in long-string encoding.
18///
19/// This is the last short-string prefix, not the first long-string prefix.
20pub(super) const LONG_STRING_OFFSET: u8 = 0xb7;
21pub(super) const SHORT_LIST_OFFSET: u8 = 0xc0;
22/// Base for computing `len_of_len` in long-list encoding.
23///
24/// This is the last short-list prefix, not the first long-list prefix.
25pub(super) const LONG_LIST_OFFSET: u8 = 0xf7;
26pub(super) const SHORT_STRING_LIMIT: usize = 55;
27pub(super) const LENGTH_RADIX: usize = 256;
28
29/// Canonical RLP scalar form used by the decoder.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum RlpScalarForm {
32    /// A single byte in `0x00..=0x7f`, encoded as itself.
33    SingleByte,
34    /// A byte string with a one-byte RLP header.
35    ShortString,
36    /// A byte string with a length-of-length RLP header.
37    LongString,
38}
39
40/// Borrowed RLP scalar byte string.
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct RlpScalar<'a> {
43    payload: &'a [u8],
44    encoded_len: usize,
45    header_len: usize,
46    form: RlpScalarForm,
47}
48
49impl<'a> RlpScalar<'a> {
50    /// Returns the decoded scalar payload bytes.
51    #[must_use]
52    pub const fn payload(self) -> &'a [u8] {
53        self.payload
54    }
55
56    /// Returns the total encoded item length in bytes.
57    #[must_use]
58    pub const fn encoded_len(self) -> usize {
59        self.encoded_len
60    }
61
62    /// Returns the RLP header length in bytes.
63    #[must_use]
64    pub const fn header_len(self) -> usize {
65        self.header_len
66    }
67
68    /// Returns the canonical scalar form that was decoded.
69    #[must_use]
70    pub const fn form(self) -> RlpScalarForm {
71        self.form
72    }
73
74    /// Returns true when the decoded payload is empty.
75    #[must_use]
76    pub const fn is_empty(self) -> bool {
77        self.payload.is_empty()
78    }
79}
80
81/// Decodes exactly one canonical RLP scalar byte string.
82pub fn decode_rlp_scalar<'a>(
83    input: &'a [u8],
84    limits: DecodeLimits,
85) -> Result<RlpScalar<'a>, DecodeError> {
86    let mut accumulator = limits.accumulator();
87    let scalar = decode_rlp_scalar_partial(input, &mut accumulator)?;
88    require_exact_consumption(scalar.encoded_len, input.len())?;
89    Ok(scalar)
90}
91
92/// Decodes one canonical RLP scalar byte string from the start of `input`.
93///
94/// Warning: this intentionally accepts trailing bytes. Use
95/// [`decode_rlp_scalar`] when the full input must be consumed.
96///
97/// The input-length budget check applies to the full `input` slice, not only
98/// the consumed scalar bytes. Callers that decode from a larger outer buffer
99/// must pre-slice before calling this helper.
100///
101/// This helper does not reject trailing bytes. It is intended for nested
102/// decoders that need to consume one item while sharing cumulative budgets.
103pub fn decode_rlp_scalar_partial<'a>(
104    input: &'a [u8],
105    accumulator: &mut DecodeAccumulator,
106) -> Result<RlpScalar<'a>, DecodeError> {
107    accumulator.check_input_len(input.len())?;
108    accumulator.account_items(1)?;
109
110    let prefix = *input.first().ok_or(DecodeError::Malformed)?;
111    match prefix {
112        0x00..=0x7f => decode_single_byte(input),
113        SHORT_STRING_OFFSET..=LONG_STRING_OFFSET => decode_short_string(input, prefix),
114        0xb8..=0xbf => decode_long_string(input, prefix),
115        SHORT_LIST_OFFSET..=0xff => Err(DecodeError::UnexpectedList),
116    }
117}
118
119fn decode_single_byte(input: &[u8]) -> Result<RlpScalar<'_>, DecodeError> {
120    let payload = input.get(..1).ok_or(DecodeError::OffsetOutOfBounds)?;
121    Ok(RlpScalar {
122        payload,
123        encoded_len: 1,
124        header_len: 0,
125        form: RlpScalarForm::SingleByte,
126    })
127}
128
129pub(super) fn decode_short_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
130    let payload_len = usize::from(prefix.saturating_sub(SHORT_STRING_OFFSET));
131    let payload = payload(input, 1, payload_len)?;
132    if payload_len == 1 && payload.first().is_some_and(|byte| *byte <= 0x7f) {
133        return Err(DecodeError::Malformed);
134    }
135    Ok(RlpScalar {
136        payload,
137        encoded_len: checked_len_add(1, payload_len)?,
138        header_len: 1,
139        form: RlpScalarForm::ShortString,
140    })
141}
142
143pub(super) fn decode_long_string(input: &[u8], prefix: u8) -> Result<RlpScalar<'_>, DecodeError> {
144    let len_of_len = usize::from(prefix.saturating_sub(LONG_STRING_OFFSET));
145    let payload_len = parse_payload_len(input, 1, len_of_len)?;
146    if payload_len <= SHORT_STRING_LIMIT {
147        return Err(DecodeError::Malformed);
148    }
149    let header_len = checked_len_add(1, len_of_len)?;
150    let payload = payload(input, header_len, payload_len)?;
151    Ok(RlpScalar {
152        payload,
153        encoded_len: checked_len_add(header_len, payload_len)?,
154        header_len,
155        form: RlpScalarForm::LongString,
156    })
157}
158
159pub(super) fn parse_payload_len(
160    input: &[u8],
161    offset: usize,
162    len: usize,
163) -> Result<usize, DecodeError> {
164    let end = require_range_in_bounds(offset, len, input.len())?;
165    let bytes = input
166        .get(offset..end)
167        .ok_or(DecodeError::OffsetOutOfBounds)?;
168    if bytes.first().is_some_and(|byte| *byte == 0) {
169        return Err(DecodeError::Malformed);
170    }
171
172    let mut value = 0usize;
173    for byte in bytes {
174        value = value
175            .checked_mul(LENGTH_RADIX)
176            .ok_or(DecodeError::LengthOverflow)?;
177        value = value
178            .checked_add(usize::from(*byte))
179            .ok_or(DecodeError::LengthOverflow)?;
180    }
181    Ok(value)
182}
183
184pub(super) fn payload(input: &[u8], offset: usize, len: usize) -> Result<&[u8], DecodeError> {
185    let end = require_range_in_bounds(offset, len, input.len())?;
186    input.get(offset..end).ok_or(DecodeError::OffsetOutOfBounds)
187}