Skip to main content

decdn_protocol/
framing.rs

1//! Length-prefixed wire framing for deCDN ALPNs (ADR 013).
2//!
3//! Every message on every QUIC stream is `varint(len) || postcard_bytes[0..len]`.
4//! The varint uses postcard's native continuation-bit encoding (7 data bits per
5//! byte, MSB = continuation, little-endian, 1–5 bytes for `u32`).
6//!
7//! Receivers read the varint length, reject anything above the ALPN's frame
8//! ceiling before allocation, allocate exactly that many bytes, then deserialize
9//! the frame with [`decode_message`] (a thin wrapper over
10//! [`postcard::take_from_bytes`] that preserves the trailing remainder for
11//! future Tier-1 extension fields).
12//!
13//! Every read names its own ceiling, sized to the largest legitimate message
14//! that can arrive at that read (ADR 013 § Wire Framing). The ceilings live
15//! beside the messages they bound: [`crate::message::MAX_PROBE_FRAME`],
16//! [`crate::client::MAX_CLIENT_CONTROL_FRAME`], [`crate::client::MAX_CHUNK_FRAME`]
17//! and [`crate::dht::MAX_DHT_FRAME`]. There is no global ceiling, so no read can
18//! fall back to a bound far above what its ALPN carries.
19
20use serde::Serialize;
21use serde::de::DeserializeOwned;
22use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
23
24/// Errors produced by the framing helpers. The handler-layer mapping to QUIC
25/// application error codes lives in the node crate.
26#[derive(Debug, thiserror::Error)]
27pub enum FrameError {
28    /// The underlying stream failed while reading or writing the frame.
29    #[error("frame I/O error: {0}")]
30    Io(#[from] std::io::Error),
31    /// The frame is longer than the ceiling allows. A reader returns this for
32    /// a length prefix above its ceiling, before it buffers anything.
33    /// [`write_frame`] returns it for a payload too long for the `u32` length
34    /// prefix, with `max` set to `u32::MAX`.
35    #[error("frame length {len} exceeds the frame ceiling ({max})")]
36    TooLarge {
37        /// The announced (reader) or attempted (writer) payload length, in bytes.
38        len: u64,
39        /// The ceiling that applied, in bytes.
40        max: u32,
41    },
42    /// The length prefix is not a well-formed varint — a non-terminating
43    /// continuation run through all five bytes, or a fifth byte overflowing
44    /// `u32`. A short or empty buffer reads as truncated, not malformed.
45    #[error("malformed varint length prefix")]
46    Varint,
47    /// The frame's bytes are not a valid postcard encoding of the expected
48    /// message.
49    #[error("postcard decode error: {0}")]
50    Decode(#[from] postcard::Error),
51}
52
53/// Serialize `msg` to a postcard buffer suitable for passing to [`write_frame`].
54pub fn encode_message<M: Serialize>(msg: &M) -> Result<Vec<u8>, FrameError> {
55    Ok(postcard::to_allocvec(msg)?)
56}
57
58/// Decode a frame into a message and the unconsumed remainder. The remainder
59/// is reserved for Tier-1 extension fields (ADR 013 §Tier 1).
60pub fn decode_message<M: DeserializeOwned>(frame: &[u8]) -> Result<(M, &[u8]), FrameError> {
61    Ok(postcard::take_from_bytes::<M>(frame)?)
62}
63
64/// A top-level ALPN protocol enum (one per ALPN — [`crate::ProbeMessage`],
65/// [`crate::ClientMessage`], [`crate::DhtMessage`]). Serialized as the
66/// outermost postcard value in a frame, so its variant discriminant is the
67/// frame's leading varint.
68///
69/// Implementors expose their declared-variant count so the framing layer can
70/// tell an *unknown* discriminant (a variant this build does not know —
71/// ADR 013 `UNSUPPORTED_MESSAGE`, `0x01`) apart from a genuine parse fault
72/// (`MALFORMED_MESSAGE`, `0x03`) after a failed [`decode_message`].
73pub trait TopLevelEnum {
74    /// Number of declared variants. Postcard assigns discriminants
75    /// `0..VARIANT_COUNT` in declaration order, so any leading discriminant
76    /// `>= VARIANT_COUNT` names a variant this build does not know.
77    const VARIANT_COUNT: u32;
78}
79
80/// Classify a failed top-level-enum decode per ADR 013: return `true` iff the
81/// frame's leading discriminant names a variant at or beyond
82/// `M::VARIANT_COUNT` — i.e. an unknown/unsupported variant the receiver must
83/// answer with `UNSUPPORTED_MESSAGE` (`0x01`) rather than `MALFORMED_MESSAGE`
84/// (`0x03`).
85///
86/// A frame that does not begin with a well-formed varint (empty, a
87/// non-terminating continuation run, or a 5th byte that overflows `u32`) is a
88/// genuine parse fault, so this returns `false` and the caller keeps the
89/// `MALFORMED` classification. Callers should only consult this after
90/// [`decode_message`] has actually failed; a valid in-range discriminant whose
91/// inner payload is malformed also stays `MALFORMED` (this returns `false`).
92pub fn is_unknown_variant<M: TopLevelEnum>(frame: &[u8]) -> bool {
93    matches!(leading_varint_u32(frame), Some(d) if d >= M::VARIANT_COUNT)
94}
95
96/// Decode the leading postcard `u32` varint from `frame` (the top-level enum
97/// discriminant) without consuming the remainder. Returns `None` when the
98/// bytes do not begin with a well-formed varint — the synchronous mirror of
99/// [`read_varint_u32`]'s validation, over an in-memory slice.
100fn leading_varint_u32(frame: &[u8]) -> Option<u32> {
101    let mut result: u32 = 0;
102    for (i, &byte) in frame.iter().take(5).enumerate() {
103        let data = u32::from(byte & 0x7F);
104        // On the 5th byte (i == 4) only the low 4 bits are valid; reject overflow.
105        if i == 4 && byte & 0x70 != 0 {
106            return None;
107        }
108        let shift = u32::try_from(i).ok()?.saturating_mul(7);
109        let shifted = data.checked_shl(shift)?;
110        result |= shifted;
111        if byte & 0x80 == 0 {
112            return Some(result);
113        }
114    }
115    None
116}
117
118/// Try to locate one complete length-prefixed frame at the front of `buf`
119/// without consuming it, returning `(header_len, payload_len)` — the byte
120/// counts of the varint prefix and the payload it announces. Returns `Ok(None)`
121/// when `buf` does not yet hold a complete frame (the varint is truncated, or
122/// fewer than `payload_len` payload bytes have arrived).
123///
124/// This is the synchronous, non-consuming counterpart to [`read_frame`], for a
125/// caller that fills a byte buffer incrementally with **cancellation-safe**
126/// reads and must split frames off it after each top-up — e.g. the per-voucher
127/// reader's read-ahead (#1486), which reads ahead under a timeout that
128/// [`read_frame`] (built on `read_exact`) could not survive without losing
129/// partial bytes. The
130/// caller slices the payload as `buf[header_len..header_len + payload_len]`,
131/// decodes it with [`decode_message`], then drains `header_len + payload_len`
132/// bytes.
133///
134/// The announced length is validated against `max` — the read's frame ceiling —
135/// as soon as the varint is complete, before the caller waits for (or buffers)
136/// the payload. This mirrors [`read_frame`]'s pre-allocation denial-of-service
137/// bound.
138///
139/// # Errors
140///
141/// [`FrameError::Varint`] for a malformed length prefix (a non-terminating
142/// continuation run within the first 5 bytes, or a 5th byte overflowing `u32`);
143/// [`FrameError::TooLarge`] when the announced length exceeds `max`.
144pub fn parse_frame(buf: &[u8], max: u32) -> Result<Option<(usize, usize)>, FrameError> {
145    let Some((len, header_len)) = decode_varint_prefix(buf)? else {
146        return Ok(None);
147    };
148    if len > max {
149        return Err(FrameError::TooLarge {
150            len: u64::from(len),
151            max,
152        });
153    }
154    let payload_len = len as usize;
155    if buf.len().saturating_sub(header_len) < payload_len {
156        return Ok(None);
157    }
158    Ok(Some((header_len, payload_len)))
159}
160
161/// Decode a postcard `u32` varint from the front of `buf`, returning
162/// `(value, bytes_consumed)`, `Ok(None)` when the varint is truncated (fewer
163/// than its continuation bytes have arrived), or [`FrameError::Varint`] when the
164/// bytes present are already malformed. The synchronous mirror of
165/// [`read_varint_u32`].
166fn decode_varint_prefix(buf: &[u8]) -> Result<Option<(u32, usize)>, FrameError> {
167    let mut result: u32 = 0;
168    for (i, &byte) in buf.iter().take(5).enumerate() {
169        let data = u32::from(byte & 0x7F);
170        // On the 5th byte (i == 4) only the low 4 bits are valid; reject overflow.
171        if i == 4 && byte & 0x70 != 0 {
172            return Err(FrameError::Varint);
173        }
174        let shift = u32::try_from(i)
175            .map_err(|_| FrameError::Varint)?
176            .saturating_mul(7);
177        let shifted = data.checked_shl(shift).ok_or(FrameError::Varint)?;
178        result |= shifted;
179        if byte & 0x80 == 0 {
180            return Ok(Some((result, i + 1)));
181        }
182    }
183    // Ran out of buffer mid-varint (< 5 bytes, all continuations) → truncated;
184    // exactly 5 continuation bytes with the terminator missing is malformed.
185    if buf.len() >= 5 {
186        return Err(FrameError::Varint);
187    }
188    Ok(None)
189}
190
191/// Read one length-prefixed frame from an async reader.
192///
193/// The length prefix is validated against `max` *before* any payload
194/// allocation, so a peer cannot make the reader buffer more than `max` bytes.
195/// Pass the frame ceiling for the messages that can arrive at this read
196/// (ADR 013 § Wire Framing): a ceiling sized to the largest legitimate message
197/// keeps a stalled oversized frame from pinning memory.
198///
199/// # Errors
200///
201/// [`FrameError::TooLarge`] when the announced length exceeds `max`,
202/// [`FrameError::Varint`] for a malformed length prefix, and
203/// [`FrameError::Io`] when the stream fails or ends mid-frame.
204pub async fn read_frame<R: AsyncRead + Unpin>(r: &mut R, max: u32) -> Result<Vec<u8>, FrameError> {
205    let len = read_varint_u32(r).await?;
206    if len > max {
207        return Err(FrameError::TooLarge {
208            len: u64::from(len),
209            max,
210        });
211    }
212    let mut buf = vec![0u8; len as usize];
213    r.read_exact(&mut buf).await?;
214    Ok(buf)
215}
216
217/// Write one length-prefixed frame to an async writer.
218///
219/// The writer checks only that the length fits the `u32` prefix. The chunk
220/// encoders refuse a frame above [`crate::client::MAX_CHUNK_FRAME`]. Every other
221/// message is small by shape, and the worst-case encode tests beside each
222/// ceiling pin that it fits the read that receives it.
223///
224/// # Errors
225///
226/// [`FrameError::TooLarge`] when the payload length does not fit a `u32`, and
227/// [`FrameError::Io`] when the stream fails.
228pub async fn write_frame<W: AsyncWrite + Unpin>(
229    w: &mut W,
230    payload: &[u8],
231) -> Result<(), FrameError> {
232    let len = u32::try_from(payload.len()).map_err(|_| FrameError::TooLarge {
233        len: u64::try_from(payload.len()).unwrap_or(u64::MAX),
234        max: u32::MAX,
235    })?;
236    write_varint_u32(w, len).await?;
237    w.write_all(payload).await?;
238    Ok(())
239}
240
241async fn read_varint_u32<R: AsyncRead + Unpin>(r: &mut R) -> Result<u32, FrameError> {
242    let mut result: u32 = 0;
243    for i in 0u32..5 {
244        let mut b = [0u8; 1];
245        r.read_exact(&mut b).await?;
246        let byte = b[0];
247        let data = u32::from(byte & 0x7F);
248        let shift = i.saturating_mul(7);
249        // On the 5th byte (i=4) only the low 4 bits are valid; reject overflow.
250        if i == 4 && byte & 0x70 != 0 {
251            return Err(FrameError::Varint);
252        }
253        let shifted = data.checked_shl(shift).ok_or(FrameError::Varint)?;
254        result |= shifted;
255        if byte & 0x80 == 0 {
256            return Ok(result);
257        }
258    }
259    Err(FrameError::Varint)
260}
261
262/// Encode `value` as a postcard varint into the caller's stack buffer, returning
263/// the number of bytes written (1..=5).
264///
265/// A `u32` encodes to at most 5 varint bytes (`ceil(32/7) = 5`), which is why the
266/// buffer is a fixed `[u8; 5]` and why the count returned always fits it.
267///
268/// The `out.get_mut(idx)` guards exist to satisfy the `indexing_slicing` clippy lint.
269/// Each one stops the encode rather than dropping a byte and counting it anyway: the
270/// returned length is bytes *written*, never bytes intended, so no caller can be
271/// handed a count that outruns the bytes behind it.
272#[must_use]
273pub(crate) fn encode_varint_u32(mut value: u32, out: &mut [u8; 5]) -> usize {
274    let mut idx = 0usize;
275    loop {
276        let byte = (value & 0x7F) as u8;
277        value >>= 7;
278        let last = value == 0;
279        let Some(slot) = out.get_mut(idx) else {
280            break;
281        };
282        *slot = if last { byte } else { byte | 0x80 };
283        idx = idx.saturating_add(1);
284        if last {
285            break;
286        }
287    }
288    debug_assert!(idx <= 5, "u32 varint must fit in 5 bytes, idx={idx}");
289    idx
290}
291
292async fn write_varint_u32<W: AsyncWrite + Unpin>(w: &mut W, value: u32) -> std::io::Result<()> {
293    let mut buf = [0u8; 5];
294    let idx = encode_varint_u32(value, &mut buf);
295    // A length prefix that silently shortens to nothing is worse than a failed write:
296    // `write_frame` would put the payload on the wire behind it, the peer would read
297    // the payload's first byte as the length, and the stream — and the cumulative
298    // wire-byte count vouchers are paid against — would desync with no error on
299    // either side. Unreachable while `buf` is `[u8; 5]`; refuse rather than rely on it.
300    let Some(bytes) = buf.get(..idx) else {
301        return Err(std::io::Error::other(format!(
302            "varint length prefix wants {idx} bytes but only {} were encoded",
303            buf.len()
304        )));
305    };
306    w.write_all(bytes).await
307}
308
309#[cfg(test)]
310#[allow(
311    clippy::unwrap_used,
312    clippy::expect_used,
313    clippy::panic,
314    clippy::indexing_slicing
315)]
316mod proptests;
317
318#[cfg(test)]
319mod tests;