Skip to main content

netget/server/devp2p/
rlp.rs

1//! Recursive Length Prefix (RLP), Ethereum's serialisation, with the bounds a peer cannot move.
2//!
3//! RLP is recursive by construction — a list holds items that may be lists — and one byte
4//! (`0xc1`…) opens a level, so a decoder without a depth counter is a stack overflow a peer can
5//! buy for a few kilobytes. Every decode here carries `depth` and refuses past
6//! [`MAX_RLP_DEPTH`]; the deepest structure devp2p actually sends (an uncle header inside a block
7//! body inside a `BlockBodies` response) is five levels deep. The item count is bounded too
8//! ([`MAX_RLP_ITEMS`]): one byte is one item, and each item costs a `Vec` slot here.
9//!
10//! Decoding is **canonical-only**, as go-ethereum's is: a single byte below `0x80` written with a
11//! length prefix, a length with leading zeros, or a long form for a payload of 55 bytes or fewer
12//! is refused. That is not pedantry — it is what makes `encode(decode(x)) == x`, which is what
13//! lets a block hash computed from a decoded header be the hash of the bytes the peer sent.
14use anyhow::{bail, ensure, Context, Result};
15
16/// The deepest nesting accepted. devp2p's deepest real structure is five levels.
17pub const MAX_RLP_DEPTH: usize = 16;
18/// The most items (strings and lists) one decode may produce.
19pub const MAX_RLP_ITEMS: usize = 200_000;
20
21/// One RLP item.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum Rlp {
24    Bytes(Vec<u8>),
25    List(Vec<Rlp>),
26}
27
28impl Rlp {
29    pub fn empty_list() -> Rlp {
30        Rlp::List(Vec::new())
31    }
32
33    /// An unsigned integer: big-endian, no leading zeros, zero as the empty string.
34    pub fn uint(n: u64) -> Rlp {
35        Rlp::Bytes(trim_leading_zeros(&n.to_be_bytes()).to_vec())
36    }
37
38    /// An unsigned big integer given as big-endian bytes (leading zeros dropped).
39    pub fn big_uint(be: &[u8]) -> Rlp {
40        Rlp::Bytes(trim_leading_zeros(be).to_vec())
41    }
42
43    pub fn bytes(b: &[u8]) -> Rlp {
44        Rlp::Bytes(b.to_vec())
45    }
46
47    pub fn string(s: &str) -> Rlp {
48        Rlp::Bytes(s.as_bytes().to_vec())
49    }
50
51    pub fn as_bytes(&self) -> Result<&[u8]> {
52        match self {
53            Rlp::Bytes(b) => Ok(b),
54            Rlp::List(_) => bail!("expected a byte string, found a list"),
55        }
56    }
57
58    pub fn as_list(&self) -> Result<&[Rlp]> {
59        match self {
60            Rlp::List(l) => Ok(l),
61            Rlp::Bytes(_) => bail!("expected a list, found a byte string"),
62        }
63    }
64
65    /// A canonical unsigned integer of at most eight bytes.
66    pub fn as_u64(&self) -> Result<u64> {
67        let b = self.as_bytes()?;
68        ensure!(
69            b.len() <= 8,
70            "integer of {} bytes is above 64 bits",
71            b.len()
72        );
73        ensure!(
74            b.first() != Some(&0),
75            "non-canonical integer (leading zero byte)"
76        );
77        Ok(b.iter().fold(0u64, |n, d| (n << 8) | u64::from(*d)))
78    }
79
80    /// A canonical unsigned big integer of at most 32 bytes, as its minimal big-endian bytes.
81    pub fn as_big_uint(&self) -> Result<&[u8]> {
82        let b = self.as_bytes()?;
83        ensure!(
84            b.len() <= 32,
85            "integer of {} bytes is above 256 bits",
86            b.len()
87        );
88        ensure!(
89            b.first() != Some(&0),
90            "non-canonical integer (leading zero byte)"
91        );
92        Ok(b)
93    }
94
95    /// A boolean: RLP has none, so devp2p writes 0 (empty string) or 1.
96    pub fn as_bool(&self) -> Result<bool> {
97        match self.as_u64()? {
98            0 => Ok(false),
99            1 => Ok(true),
100            n => bail!("boolean {n} is neither 0 nor 1"),
101        }
102    }
103
104    /// A byte string of exactly `N` bytes.
105    pub fn as_fixed<const N: usize>(&self) -> Result<[u8; N]> {
106        let b = self.as_bytes()?;
107        b.try_into()
108            .map_err(|_| anyhow::anyhow!("expected {N} bytes, found {}", b.len()))
109    }
110
111    /// Encode this item.
112    pub fn encode(&self) -> Vec<u8> {
113        let mut out = Vec::new();
114        self.encode_into(&mut out);
115        out
116    }
117
118    fn encode_into(&self, out: &mut Vec<u8>) {
119        match self {
120            Rlp::Bytes(b) => {
121                if b.len() == 1 && b[0] < 0x80 {
122                    out.push(b[0]);
123                } else {
124                    put_length(out, 0x80, b.len());
125                    out.extend_from_slice(b);
126                }
127            }
128            Rlp::List(items) => {
129                let mut payload = Vec::new();
130                for item in items {
131                    item.encode_into(&mut payload);
132                }
133                put_length(out, 0xc0, payload.len());
134                out.extend_from_slice(&payload);
135            }
136        }
137    }
138}
139
140fn trim_leading_zeros(b: &[u8]) -> &[u8] {
141    let start = b.iter().position(|d| *d != 0).unwrap_or(b.len());
142    &b[start..]
143}
144
145fn put_length(out: &mut Vec<u8>, offset: u8, len: usize) {
146    if len <= 55 {
147        out.push(offset + len as u8);
148    } else {
149        let be = (len as u64).to_be_bytes();
150        let be = trim_leading_zeros(&be);
151        out.push(offset + 55 + be.len() as u8);
152        out.extend_from_slice(be);
153    }
154}
155
156/// Decode exactly one item that spans the whole input.
157pub fn decode(input: &[u8]) -> Result<Rlp> {
158    let (item, used) = decode_prefix(input)?;
159    ensure!(
160        used == input.len(),
161        "{} bytes trail the RLP item",
162        input.len() - used
163    );
164    Ok(item)
165}
166
167/// Decode one item from the start of `input`; returns it and the bytes it used. Trailing bytes
168/// are the caller's business (the RLPx handshake pads its messages).
169pub fn decode_prefix(input: &[u8]) -> Result<(Rlp, usize)> {
170    let mut items = 0usize;
171    decode_at(input, 0, &mut items)
172}
173
174/// Where an item's payload is, read from its prefix: (is_list, payload start, payload length).
175fn header(input: &[u8]) -> Result<(bool, usize, usize)> {
176    let b = *input.first().context("RLP item is empty")?;
177    let (list, start, len) = match b {
178        0x00..=0x7f => return Ok((false, 0, 1)),
179        0x80..=0xb7 => (false, 1, usize::from(b - 0x80)),
180        0xc0..=0xf7 => (true, 1, usize::from(b - 0xc0)),
181        0xb8..=0xbf | 0xf8..=0xff => {
182            let list = b >= 0xf8;
183            let len_of_len = usize::from(b - if list { 0xf7 } else { 0xb7 });
184            let len_bytes = input
185                .get(1..1 + len_of_len)
186                .context("RLP length runs past the input")?;
187            ensure!(len_bytes[0] != 0, "non-canonical RLP length (leading zero)");
188            ensure!(
189                len_of_len <= 8,
190                "RLP length of {len_of_len} bytes is impossible"
191            );
192            let len = len_bytes.iter().fold(0u64, |n, d| (n << 8) | u64::from(*d));
193            ensure!(
194                len > 55,
195                "non-canonical RLP length: long form for {len} bytes"
196            );
197            let len = usize::try_from(len).context("RLP length above usize")?;
198            (list, 1 + len_of_len, len)
199        }
200    };
201    let end = start.checked_add(len).context("RLP length overflows")?;
202    ensure!(
203        end <= input.len(),
204        "RLP item declares {len} bytes, {} remain",
205        input.len() - start.min(input.len())
206    );
207    if !list && start == 1 && len == 1 {
208        ensure!(
209            input[1] >= 0x80,
210            "non-canonical RLP: single byte {:#04x} with a length prefix",
211            input[1]
212        );
213    }
214    Ok((list, start, len))
215}
216
217fn decode_at(input: &[u8], depth: usize, items: &mut usize) -> Result<(Rlp, usize)> {
218    *items += 1;
219    ensure!(
220        *items <= MAX_RLP_ITEMS,
221        "RLP holds more than {MAX_RLP_ITEMS} items"
222    );
223    let (list, start, len) = header(input)?;
224    let payload = &input[start..start + len];
225    if !list {
226        if start == 0 {
227            return Ok((Rlp::Bytes(vec![input[0]]), 1));
228        }
229        return Ok((Rlp::Bytes(payload.to_vec()), start + len));
230    }
231    ensure!(
232        depth < MAX_RLP_DEPTH,
233        "RLP nesting deeper than {MAX_RLP_DEPTH} levels"
234    );
235    let mut out = Vec::new();
236    let mut pos = 0;
237    while pos < payload.len() {
238        let (item, used) = decode_at(&payload[pos..], depth + 1, items)?;
239        out.push(item);
240        pos += used;
241    }
242    Ok((Rlp::List(out), start + len))
243}
244
245/// Split a devp2p frame into its message code (an RLP integer) and the payload after it.
246pub fn split_code(frame: &[u8]) -> Result<(u64, &[u8])> {
247    let (list, start, len) = header(frame)?;
248    ensure!(!list, "message code is a list");
249    let code_bytes = if start == 0 {
250        &frame[..1]
251    } else {
252        &frame[start..start + len]
253    };
254    let code = Rlp::Bytes(code_bytes.to_vec())
255        .as_u64()
256        .context("message code")?;
257    let used = if start == 0 { 1 } else { start + len };
258    Ok((code, &frame[used..]))
259}