Skip to main content

melin_wire_protocol/
framing.rs

1//! I/O-free framing: split length-prefixed frames out of bytes the caller
2//! already holds.
3//!
4//! The wire format is a 4-byte little-endian payload length followed by
5//! that many payload bytes. [`BlockingFrameReader`] reads it straight off
6//! a `std::io::Read`; this module is for programs that own their I/O loop
7//! instead (io_uring completions, DPDK receive bursts, a non-blocking
8//! socket polled by hand) and only need to be told where the frames are
9//! in the bytes they have received so far.
10//!
11//! Two layers:
12//!
13//! - [`split_frame`] / [`split_frame_limited`]: stateless and zero-copy.
14//!   They look at the front of a caller-owned buffer and return the first
15//!   complete frame as a borrowed slice, `None` if more bytes are needed,
16//!   or an error if the length prefix declares more than the limit. The
17//!   caller keeps its own buffer, cursor and compaction policy, so a
18//!   receive loop on the hot path pays nothing beyond the bounds checks
19//!   it would write by hand.
20//! - [`FrameDecoder`]: an owning buffer built on the splitter, for a
21//!   caller that would rather push received chunks and pull frames out.
22//!
23//! The sending side has its counterpart for application requests: the
24//! body is encoded in place in the caller's send buffer, behind room for
25//! a header of [`REQUEST_HEADER_LEN`] bytes, and [`seal_request`] writes
26//! the length prefix, the protocol's tag and the request's ID in front of
27//! it once its length is known ([`request_body`] hands out the region to
28//! encode into, and [`frame_request`] does both around a closure). Frames
29//! built at successive offsets lie back to back, ready for one write. A
30//! codec crate that needs only framing depends on this crate alone, and a
31//! codec whose error is [`ProtocolError`] frames with `?`. On the
32//! receiving side, [`split_request`] is the one parser of a request
33//! frame's payload: its ID and its body.
34//!
35//! A receive loop over its own buffer:
36//!
37//! ```
38//! use melin_wire_protocol::framing::{split_frame, FrameTooLarge};
39//!
40//! fn on_receive(buf: &mut Vec<u8>, frames: &mut Vec<Vec<u8>>) -> Result<(), FrameTooLarge> {
41//!     let mut cursor = 0;
42//!     while let Some(frame) = split_frame(&buf[cursor..])? {
43//!         frames.push(frame.payload.to_vec());
44//!         cursor += frame.consumed();
45//!     }
46//!     // Keep the partial frame, if any, for the next receive.
47//!     buf.drain(..cursor);
48//!     Ok(())
49//! }
50//!
51//! let mut buf = vec![2, 0, 0, 0, b'h', b'i', 3, 0];
52//! let mut frames = Vec::new();
53//! on_receive(&mut buf, &mut frames)?;
54//! assert_eq!(frames, [b"hi".to_vec()]);
55//! assert_eq!(buf, [3, 0]);
56//!
57//! // A prefix declaring more than the limit fails before its payload arrives.
58//! buf.extend_from_slice(&[0xFF, 0xFF]);
59//! assert!(on_receive(&mut buf, &mut frames).is_err());
60//! # Ok::<(), FrameTooLarge>(())
61//! ```
62//!
63//! # Oversized frames
64//!
65//! A length above the limit is rejected as soon as the 4 prefix bytes are
66//! readable, before any payload arrives. A peer that sends a corrupt or
67//! hostile prefix is therefore refused immediately instead of leaving the
68//! receiver buffering towards a length it will never accept. Past such a
69//! prefix the byte stream has no trustworthy frame boundary left: the
70//! connection should be closed, not resynchronised.
71//!
72//! # Empty frames
73//!
74//! A zero length is a valid frame with an empty payload: it consumes the
75//! 4 prefix bytes and nothing else. Framing does not judge content; a
76//! protocol that has no use for an empty frame rejects it one layer up,
77//! where it already inspects the payload.
78//!
79//! [`BlockingFrameReader`]: crate::blocking::BlockingFrameReader
80
81use std::fmt;
82
83use crate::blocking::MAX_FRAME_SIZE;
84use crate::control::RequestId;
85use crate::control_codec::{REQUEST_ID_LEN, TAG_LEN, TAG_REQUEST, read_id};
86use crate::error::ProtocolError;
87
88/// Size of the length prefix in front of every frame's payload.
89pub const PREFIX_LEN: usize = 4;
90
91/// A complete frame found at the front of a buffer.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct Frame<'a> {
94    /// The payload, borrowed from the buffer, without its length prefix.
95    pub payload: &'a [u8],
96}
97
98impl Frame<'_> {
99    /// Bytes this frame occupies at the front of the buffer, prefix
100    /// included: advance the read cursor by this much to reach the next
101    /// frame.
102    #[inline]
103    pub fn consumed(&self) -> usize {
104        PREFIX_LEN + self.payload.len()
105    }
106}
107
108/// A length prefix declared more payload than the limit allows.
109///
110/// The framing of the stream it came from is no longer trustworthy; the
111/// connection should be dropped.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct FrameTooLarge {
114    /// The payload length the prefix declared. `u32` because that is the
115    /// prefix's width on the wire: the value is reported exactly as
116    /// received, whatever the platform's `usize`.
117    pub declared: u32,
118    /// The limit it exceeded. `usize` because it echoes the `max_payload`
119    /// the caller passed, which bounds a buffer length.
120    pub max: usize,
121}
122
123impl fmt::Display for FrameTooLarge {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(
126            f,
127            "frame too large: {} bytes (max {})",
128            self.declared, self.max
129        )
130    }
131}
132
133impl std::error::Error for FrameTooLarge {}
134
135impl From<FrameTooLarge> for std::io::Error {
136    /// An oversized frame is malformed input, the same kind
137    /// [`BlockingFrameReader`](crate::blocking::BlockingFrameReader)
138    /// reports for it.
139    fn from(e: FrameTooLarge) -> Self {
140        std::io::Error::new(std::io::ErrorKind::InvalidData, e)
141    }
142}
143
144/// Split the first frame off the front of `buf`, with the protocol's
145/// [`MAX_FRAME_SIZE`] as the payload limit.
146///
147/// See [`split_frame_limited`].
148#[inline]
149pub fn split_frame(buf: &[u8]) -> Result<Option<Frame<'_>>, FrameTooLarge> {
150    split_frame_limited(buf, MAX_FRAME_SIZE)
151}
152
153/// Split the first frame off the front of `buf`, refusing a payload
154/// longer than `max_payload` bytes.
155///
156/// - `Ok(Some(frame))`: a complete frame starts at `buf[0]`; the next one
157///   starts at `buf[frame.consumed()]`.
158/// - `Ok(None)`: `buf` holds less than one frame; keep its bytes and call
159///   again once more have arrived.
160/// - `Err(_)`: the prefix declares more than `max_payload`. Returned as
161///   soon as the 4 prefix bytes are present, however few payload bytes
162///   follow them.
163///
164/// Bytes after the first frame are not looked at. The function holds no
165/// state, so the caller can resume with any suffix of its buffer that
166/// starts on a frame boundary.
167#[inline]
168pub fn split_frame_limited(
169    buf: &[u8],
170    max_payload: usize,
171) -> Result<Option<Frame<'_>>, FrameTooLarge> {
172    let Some((prefix, rest)) = buf.split_first_chunk::<PREFIX_LEN>() else {
173        return Ok(None);
174    };
175    let declared = u32::from_le_bytes(*prefix);
176    // A length that does not fit `usize` exceeds any limit expressible in
177    // one; on every supported target the conversion is lossless and this
178    // folds into the plain comparison.
179    let len = match usize::try_from(declared) {
180        Ok(len) if len <= max_payload => len,
181        _ => {
182            return Err(FrameTooLarge {
183                declared,
184                max: max_payload,
185            });
186        }
187    };
188    Ok(rest.get(..len).map(|payload| Frame { payload }))
189}
190
191/// An owning frame buffer: push received bytes in, take complete frames
192/// out.
193///
194/// Built on [`split_frame_limited`]. Frames come out as raw payloads, so
195/// a caller can route them as it likes: handshake frames to the
196/// handshake, reply frames to whatever classifies them afterwards.
197///
198/// Consumed bytes are reclaimed lazily: [`next`](Self::next) only
199/// advances a cursor, and a later [`push`](Self::push) moves the
200/// unconsumed tail to the front before appending once the consumed prefix
201/// is at least as large as that tail. Draining a burst of frames then
202/// costs at most one move of the leftover partial frame, and a caller that
203/// interleaves pushes with single `next` calls still pays amortised
204/// constant work per byte.
205///
206/// # After an error
207///
208/// Once [`next`](Self::next) has reported [`FrameTooLarge`] the decoder is
209/// poisoned: the stream has no trustworthy frame boundary left. Every
210/// later `next` returns the same error, and `push` discards its input
211/// rather than buffer bytes that can never be framed. Drop the connection
212/// and the decoder with it.
213#[derive(Debug)]
214pub struct FrameDecoder {
215    /// Received bytes; `buf[start..]` is the part not yet returned as a
216    /// frame. A `Vec` because the bytes in flight are unbounded in
217    /// principle (one push may carry many frames) but steady in practice:
218    /// after the first few pushes its capacity settles and no further
219    /// allocation happens. A ring buffer would avoid the compaction move,
220    /// but could hand out a frame split across its wrap point, which a
221    /// borrowed `&[u8]` cannot represent.
222    buf: Vec<u8>,
223    /// Read cursor into `buf`: everything before it has been returned.
224    /// `usize` because it indexes `buf`.
225    start: usize,
226    /// Largest payload a frame may declare.
227    max_payload: usize,
228    /// The error that poisoned the decoder, if any. Stored so every later
229    /// call reports the original declared length.
230    poisoned: Option<FrameTooLarge>,
231}
232
233impl Default for FrameDecoder {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239impl FrameDecoder {
240    /// A decoder enforcing the protocol's [`MAX_FRAME_SIZE`].
241    pub fn new() -> Self {
242        Self::with_max_payload(MAX_FRAME_SIZE)
243    }
244
245    /// A decoder refusing any frame whose payload exceeds `max_payload`.
246    ///
247    /// Preallocates room for one maximal frame (capped at one
248    /// [`MAX_FRAME_SIZE`] frame, so a generous limit does not reserve
249    /// memory up front), sparing the first pushes a reallocation.
250    pub fn with_max_payload(max_payload: usize) -> Self {
251        Self {
252            buf: Vec::with_capacity(PREFIX_LEN + max_payload.min(MAX_FRAME_SIZE)),
253            start: 0,
254            max_payload,
255            poisoned: None,
256        }
257    }
258
259    /// The payload limit this decoder enforces.
260    pub fn max_payload(&self) -> usize {
261        self.max_payload
262    }
263
264    /// Whether a [`FrameTooLarge`] has poisoned the decoder.
265    pub fn is_poisoned(&self) -> bool {
266        self.poisoned.is_some()
267    }
268
269    /// Bytes received but not yet returned as a frame: a partial frame,
270    /// or complete frames still waiting for [`next`](Self::next).
271    pub fn pending(&self) -> &[u8] {
272        &self.buf[self.start..]
273    }
274
275    /// Append received bytes.
276    ///
277    /// Discarded once the decoder is poisoned.
278    pub fn push(&mut self, bytes: &[u8]) {
279        if self.poisoned.is_some() {
280            return;
281        }
282        let remaining = self.buf.len() - self.start;
283        if self.start > 0 && self.start >= remaining {
284            // Compact only once the consumed prefix is at least as large as
285            // the tail: every byte moved is matched by at least one byte
286            // reclaimed, so the cost stays amortised O(1) per byte even for
287            // a caller that interleaves single `next` calls with pushes,
288            // and the buffer never grows past twice its live bytes plus
289            // the push. A fully drained buffer moves nothing.
290            // `copy_within` + `truncate` is a single memmove of the tail;
291            // `drain(..start)` would do the same with more bookkeeping.
292            self.buf.copy_within(self.start.., 0);
293            self.buf.truncate(remaining);
294            self.start = 0;
295        }
296        self.buf.extend_from_slice(bytes);
297    }
298
299    /// The next complete frame's payload, or `None` until more bytes are
300    /// pushed. The slice stays valid until the next call on the decoder.
301    ///
302    /// Not an `Iterator`: the payload borrows the decoder, and `None`
303    /// means "not yet", not "never".
304    #[expect(clippy::should_implement_trait)] // cannot be `Iterator::next`, see above
305    pub fn next(&mut self) -> Result<Option<&[u8]>, FrameTooLarge> {
306        if let Some(e) = self.poisoned {
307            return Err(e);
308        }
309        match split_frame_limited(&self.buf[self.start..], self.max_payload) {
310            Ok(Some(frame)) => {
311                let payload_start = self.start + PREFIX_LEN;
312                self.start += frame.consumed();
313                Ok(Some(&self.buf[payload_start..self.start]))
314            }
315            Ok(None) => Ok(None),
316            Err(e) => {
317                self.poisoned = Some(e);
318                // The bytes can never be framed and `push` discards from
319                // here on, so free the allocation now rather than hold it
320                // until the decoder is dropped (`clear` would keep it).
321                self.buf = Vec::new();
322                self.start = 0;
323                Err(e)
324            }
325        }
326    }
327}
328
329// ---------------------------------------------------------------------------
330// Request frames
331// ---------------------------------------------------------------------------
332
333/// Bytes of a request frame's payload ahead of its body: the protocol's
334/// one-byte [`TAG_REQUEST`] and the request's eight-byte ID.
335pub const REQUEST_TAG_AND_ID_LEN: usize = TAG_LEN + REQUEST_ID_LEN;
336
337/// Bytes written ahead of a request body: the length prefix, the
338/// protocol's one-byte [`TAG_REQUEST`] and the request's eight-byte ID.
339pub const REQUEST_HEADER_LEN: usize = PREFIX_LEN + REQUEST_TAG_AND_ID_LEN;
340
341/// The widest request body one frame carries: the tag and the ID count
342/// toward [`MAX_FRAME_SIZE`], so a body may take the rest of it.
343pub const MAX_REQUEST_BODY: usize = MAX_FRAME_SIZE - REQUEST_TAG_AND_ID_LEN;
344
345// The prefix is written as a `u32`; every request payload is bounded by
346// `MAX_FRAME_SIZE`, so the conversion in `request_payload_len` is lossless.
347const _: () = assert!(MAX_FRAME_SIZE <= u32::MAX as usize);
348
349/// The bytes of a request frame's payload ahead of its body, for request
350/// `id`: [`TAG_REQUEST`], then the ID, little-endian. The one place they
351/// are laid out, for [`seal_request`] and for a sender that writes the
352/// header apart from the body (`melin-client`'s blocking connection,
353/// which spares itself a copy of the body that way).
354#[inline]
355pub fn request_tag_and_id(id: RequestId) -> [u8; REQUEST_TAG_AND_ID_LEN] {
356    let mut out = [0u8; REQUEST_TAG_AND_ID_LEN];
357    out[0] = TAG_REQUEST;
358    out[TAG_LEN..].copy_from_slice(&id.0.to_le_bytes());
359    out
360}
361
362/// Why a frame's payload is not a request: see [`split_request`].
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub enum NotARequest {
365    /// An empty payload: no tag at all.
366    Empty,
367    /// A tag other than [`TAG_REQUEST`].
368    Tag(u8),
369    /// A request tag with fewer bytes behind it than an ID: `len` is the
370    /// payload's length, tag included.
371    TooShort { len: usize },
372}
373
374impl fmt::Display for NotARequest {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            Self::Empty => f.write_str("an empty frame where a request was expected"),
378            Self::Tag(tag) => write!(
379                f,
380                "a frame with tag {tag:#04x} where a request was expected"
381            ),
382            Self::TooShort { len } => write!(
383                f,
384                "a {len}-byte request frame, too short for its {REQUEST_ID_LEN}-byte ID"
385            ),
386        }
387    }
388}
389
390impl std::error::Error for NotARequest {}
391
392/// Split a request frame's payload (after the length prefix) into its ID
393/// and its body: the one parser of a request, for a node's readers and for
394/// any program that reads requests off the wire (a proxy's trace, a
395/// gateway's codec). Anything but a [`TAG_REQUEST`] payload with room for
396/// the ID is [`NotARequest`]; the body may be empty.
397#[inline]
398pub fn split_request(payload: &[u8]) -> Result<(RequestId, &[u8]), NotARequest> {
399    match payload.first() {
400        None => Err(NotARequest::Empty),
401        Some(&TAG_REQUEST) => match read_id(payload) {
402            Some(id) => Ok((id, &payload[REQUEST_TAG_AND_ID_LEN..])),
403            None => Err(NotARequest::TooShort { len: payload.len() }),
404        },
405        Some(&tag) => Err(NotARequest::Tag(tag)),
406    }
407}
408
409/// A request could not be framed. Nothing was written to the header, so a
410/// caller that sends only what a successful call reported sends nothing
411/// that could desync the node's framing.
412#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub enum RequestFrameError {
414    /// The body, with the tag and the ID in front of it, would not fit in
415    /// one frame of [`MAX_FRAME_SIZE`] bytes. `len` is that payload length
416    /// (tag, ID and body), saturated at `usize::MAX` for an absurd body
417    /// length.
418    RequestTooLarge { len: usize },
419    /// The buffer cannot hold the frame: `needed` bytes, header included,
420    /// against the `available` length of the buffer. Either the buffer is
421    /// shorter than [`REQUEST_HEADER_LEN`], or the body runs past its end
422    /// (from an encoder, a bug: it reported more than it was handed).
423    BufferTooSmall { needed: usize, available: usize },
424}
425
426impl fmt::Display for RequestFrameError {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        match self {
429            Self::RequestTooLarge { len } => write!(
430                f,
431                "request too large: {len} bytes with its tag and ID, the frame limit is {MAX_FRAME_SIZE}"
432            ),
433            Self::BufferTooSmall { needed, available } => write!(
434                f,
435                "request buffer too small: the frame needs {needed} bytes, the buffer holds {available}"
436            ),
437        }
438    }
439}
440
441impl std::error::Error for RequestFrameError {}
442
443impl From<RequestFrameError> for ProtocolError {
444    /// For a codec whose error is [`ProtocolError`], so its request
445    /// encoder frames with `?`. An oversized request is
446    /// [`MessageTooLarge`](ProtocolError::MessageTooLarge) with the
447    /// payload length; a buffer too small is
448    /// [`Truncated`](ProtocolError::Truncated), what this crate's own
449    /// encoders report for a buffer that cannot hold their output.
450    fn from(e: RequestFrameError) -> Self {
451        match e {
452            RequestFrameError::RequestTooLarge { len } => ProtocolError::MessageTooLarge(len),
453            RequestFrameError::BufferTooSmall { .. } => ProtocolError::Truncated,
454        }
455    }
456}
457
458/// The payload length of a request frame carrying `body_len` bytes (the
459/// tag, the ID and the body), or [`RequestFrameError::RequestTooLarge`] if
460/// that exceeds [`MAX_FRAME_SIZE`]. The one place the request limit is
461/// decided, for a sender that writes the header apart from the body as
462/// much as for [`seal_request`].
463///
464/// `u32` because that is the length prefix's width on the wire: the value
465/// goes into it as it is.
466#[inline]
467pub fn request_payload_len(body_len: usize) -> Result<u32, RequestFrameError> {
468    // Saturating: a length near `usize::MAX` (an encoder's bug) is still
469    // reported as too large rather than wrapping into one that fits.
470    let len = REQUEST_TAG_AND_ID_LEN.saturating_add(body_len);
471    if len > MAX_FRAME_SIZE {
472        return Err(RequestFrameError::RequestTooLarge { len });
473    }
474    // Lossless: bounded by `MAX_FRAME_SIZE`, which fits a `u32` (asserted
475    // above).
476    Ok(len as u32)
477}
478
479/// The region of `buf` a request body is encoded into, ahead of
480/// [`seal_request`]: everything after the first [`REQUEST_HEADER_LEN`]
481/// bytes, capped at [`MAX_REQUEST_BODY`] bytes, so a codec that sizes its
482/// output by the slice it is given never writes a body the frame limit
483/// would refuse, even in a send buffer wider than one frame.
484///
485/// A `buf` shorter than the header is
486/// [`RequestFrameError::BufferTooSmall`].
487#[inline]
488pub fn request_body(buf: &mut [u8]) -> Result<&mut [u8], RequestFrameError> {
489    let available = buf.len();
490    match buf.get_mut(REQUEST_HEADER_LEN..) {
491        Some(body) => {
492            let region = body.len().min(MAX_REQUEST_BODY);
493            Ok(&mut body[..region])
494        }
495        None => Err(RequestFrameError::BufferTooSmall {
496            needed: REQUEST_HEADER_LEN,
497            available,
498        }),
499    }
500}
501
502/// Seal request `id`, whose `body_len`-byte body already sits at
503/// `buf[REQUEST_HEADER_LEN..]`: write the header (the length prefix, the
504/// protocol's tag and the ID) in front of it. Returns the frame's length,
505/// header included: the bytes to send are `buf[..n]`. The caller never
506/// needs the protocol's tag. The ID is the caller's to choose: the node
507/// echoes it on the request's final reply frame, and checks nothing about
508/// it.
509///
510/// - a `body_len` wider than [`MAX_REQUEST_BODY`] (one that takes the
511///   frame over [`MAX_FRAME_SIZE`]) is
512///   [`RequestTooLarge`](RequestFrameError::RequestTooLarge);
513/// - otherwise, a frame longer than `buf` (a `buf` shorter than the
514///   header, or a body past its end) is
515///   [`BufferTooSmall`](RequestFrameError::BufferTooSmall).
516///
517/// The frame limit is checked before the buffer, so a length that is both
518/// is reported as `RequestTooLarge`. On either error nothing is written.
519///
520/// Several requests batch into one buffer by framing each at the current
521/// end, in `buf[end..]`, and advancing `end` by what this returns; the
522/// frames lie back to back, ready for one write.
523///
524/// ```
525/// use melin_wire_protocol::control::RequestId;
526/// use melin_wire_protocol::framing::{FrameDecoder, request_body, seal_request, split_request};
527///
528/// let mut buf = [0u8; 64];
529/// let mut end = 0;
530/// for (id, body) in [(1, &b"first"[..]), (2, b"second")] {
531///     let region = request_body(&mut buf[end..])?;
532///     region[..body.len()].copy_from_slice(body);
533///     end += seal_request(&mut buf[end..], RequestId(id), body.len())?;
534/// }
535/// // `buf[..end]` is two request frames, back to back.
536/// let mut decoder = FrameDecoder::new();
537/// decoder.push(&buf[..end]);
538/// assert_eq!(split_request(decoder.next()?.unwrap())?, (RequestId(1), &b"first"[..]));
539/// assert_eq!(split_request(decoder.next()?.unwrap())?, (RequestId(2), &b"second"[..]));
540/// # Ok::<(), Box<dyn std::error::Error>>(())
541/// ```
542#[inline]
543pub fn seal_request(
544    buf: &mut [u8],
545    id: RequestId,
546    body_len: usize,
547) -> Result<usize, RequestFrameError> {
548    let payload_len = request_payload_len(body_len)?;
549    // Cannot overflow: `body_len` is at most `MAX_REQUEST_BODY` here.
550    let frame_len = REQUEST_HEADER_LEN + body_len;
551    let available = buf.len();
552    let Some(header) = buf
553        .get_mut(..frame_len)
554        .and_then(|frame| frame.first_chunk_mut::<REQUEST_HEADER_LEN>())
555    else {
556        return Err(RequestFrameError::BufferTooSmall {
557            needed: frame_len,
558            available,
559        });
560    };
561    header[..PREFIX_LEN].copy_from_slice(&payload_len.to_le_bytes());
562    header[PREFIX_LEN..].copy_from_slice(&request_tag_and_id(id));
563    Ok(frame_len)
564}
565
566/// Frame request `id` in place at the front of `buf`, the closure form of
567/// [`request_body`] and [`seal_request`]: `encode` writes the
568/// application's body into the region `request_body` returns and reports
569/// how many bytes it wrote, then the header goes in front of them.
570/// Returns the frame's length, header included.
571///
572/// `encode` may fail with its own error type, which needs a conversion
573/// from [`RequestFrameError`] for the failures decided here (a codec
574/// using [`ProtocolError`] has one):
575///
576/// - a `buf` shorter than [`REQUEST_HEADER_LEN`] is
577///   [`BufferTooSmall`](RequestFrameError::BufferTooSmall), and `encode`
578///   is not called;
579/// - a length from `encode` wider than [`MAX_REQUEST_BODY`] is
580///   [`RequestTooLarge`](RequestFrameError::RequestTooLarge);
581/// - otherwise, a length from `encode` past the region it was handed is
582///   `BufferTooSmall`.
583///
584/// The frame limit is checked before the region, so a length that is
585/// both is reported as `RequestTooLarge`. On any error the header is not
586/// written: `encode` may have written into the body region, but a caller
587/// that sends only what `frame_request` reported, and does not advance
588/// its cursor on an error, sends nothing that could desync the node's
589/// framing.
590///
591/// ```
592/// use melin_wire_protocol::control::RequestId;
593/// use melin_wire_protocol::error::ProtocolError;
594/// use melin_wire_protocol::framing::{REQUEST_HEADER_LEN, frame_request};
595///
596/// /// A codec's request encoder: the body, then the frame around it.
597/// fn encode_request(id: RequestId, body: &[u8], buf: &mut [u8]) -> Result<usize, ProtocolError> {
598///     frame_request(buf, id, |out| {
599///         let dst = out.get_mut(..body.len()).ok_or(ProtocolError::Truncated)?;
600///         dst.copy_from_slice(body);
601///         Ok(body.len())
602///     })
603/// }
604///
605/// let mut buf = [0u8; 24];
606/// assert_eq!(encode_request(RequestId(1), b"abc", &mut buf)?, REQUEST_HEADER_LEN + 3);
607/// assert!(matches!(
608///     encode_request(RequestId(2), &[0; 64], &mut buf),
609///     Err(ProtocolError::Truncated)
610/// ));
611/// # Ok::<(), ProtocolError>(())
612/// ```
613#[inline]
614pub fn frame_request<E: From<RequestFrameError>>(
615    buf: &mut [u8],
616    id: RequestId,
617    encode: impl FnOnce(&mut [u8]) -> Result<usize, E>,
618) -> Result<usize, E> {
619    let body_len = encode(request_body(buf)?)?;
620    Ok(seal_request(buf, id, body_len)?)
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626    use proptest::prelude::*;
627
628    /// Encode payloads as one length-prefixed stream.
629    fn encode(frames: &[Vec<u8>]) -> Vec<u8> {
630        let mut out = Vec::new();
631        for f in frames {
632            out.extend_from_slice(&(f.len() as u32).to_le_bytes());
633            out.extend_from_slice(f);
634        }
635        out
636    }
637
638    /// Every frame `split_frame_limited` finds in `buf`, walking a cursor.
639    fn split_all(buf: &[u8], max: usize) -> (Vec<Vec<u8>>, usize) {
640        let mut cursor = 0;
641        let mut out = Vec::new();
642        while let Some(frame) = split_frame_limited(&buf[cursor..], max).unwrap() {
643            out.push(frame.payload.to_vec());
644            cursor += frame.consumed();
645        }
646        (out, cursor)
647    }
648
649    /// Feed `stream` to a decoder in chunks ending at each of `cuts`,
650    /// draining after every push.
651    fn decode_chunked(stream: &[u8], cuts: &[usize], max: usize) -> Vec<Vec<u8>> {
652        let mut dec = FrameDecoder::with_max_payload(max);
653        let mut out = Vec::new();
654        let mut prev = 0;
655        for &cut in cuts.iter().chain(std::iter::once(&stream.len())) {
656            dec.push(&stream[prev..cut]);
657            prev = cut;
658            while let Some(p) = dec.next().unwrap() {
659                out.push(p.to_vec());
660            }
661        }
662        assert!(dec.pending().is_empty());
663        out
664    }
665
666    // --- split_frame ---
667
668    #[test]
669    fn empty_and_partial_prefix_need_more() {
670        assert_eq!(split_frame(&[]), Ok(None));
671        assert_eq!(split_frame(&[3]), Ok(None));
672        assert_eq!(split_frame(&[3, 0, 0]), Ok(None));
673    }
674
675    #[test]
676    fn prefix_without_payload_needs_more() {
677        assert_eq!(split_frame(&[3, 0, 0, 0]), Ok(None));
678        assert_eq!(split_frame(&[3, 0, 0, 0, 0xAA, 0xBB]), Ok(None));
679    }
680
681    #[test]
682    fn complete_frame_ignores_trailing_bytes() {
683        let buf = [3, 0, 0, 0, 0xAA, 0xBB, 0xCC, 0xFF, 0xFF];
684        let frame = split_frame(&buf).unwrap().unwrap();
685        assert_eq!(frame.payload, &[0xAA, 0xBB, 0xCC]);
686        assert_eq!(frame.consumed(), 7);
687    }
688
689    #[test]
690    fn zero_length_frame_is_an_empty_payload() {
691        let buf = [0, 0, 0, 0, 9];
692        let frame = split_frame(&buf).unwrap().unwrap();
693        assert!(frame.payload.is_empty());
694        assert_eq!(frame.consumed(), PREFIX_LEN);
695    }
696
697    #[test]
698    fn exact_limit_succeeds() {
699        let stream = encode(&[vec![7u8; MAX_FRAME_SIZE]]);
700        let frame = split_frame(&stream).unwrap().unwrap();
701        assert_eq!(frame.payload.len(), MAX_FRAME_SIZE);
702        assert_eq!(frame.consumed(), stream.len());
703    }
704
705    #[test]
706    fn oversized_fails_on_the_prefix_alone() {
707        let declared = MAX_FRAME_SIZE as u32 + 1;
708        assert_eq!(
709            split_frame(&declared.to_le_bytes()),
710            Err(FrameTooLarge {
711                declared,
712                max: MAX_FRAME_SIZE
713            })
714        );
715    }
716
717    #[test]
718    fn max_u32_prefix_reports_the_declared_length() {
719        assert_eq!(
720            split_frame(&[0xFF; 4]),
721            Err(FrameTooLarge {
722                declared: u32::MAX,
723                max: MAX_FRAME_SIZE
724            })
725        );
726    }
727
728    #[test]
729    fn custom_limit_is_respected() {
730        let at = encode(&[vec![1u8; 256]]);
731        assert_eq!(
732            split_frame_limited(&at, 256)
733                .unwrap()
734                .unwrap()
735                .payload
736                .len(),
737            256
738        );
739        let over = encode(&[vec![1u8; 257]]);
740        assert_eq!(
741            split_frame_limited(&over[..PREFIX_LEN], 256),
742            Err(FrameTooLarge {
743                declared: 257,
744                max: 256
745            })
746        );
747        // The same frame is fine under the default limit.
748        assert!(split_frame(&over).unwrap().is_some());
749    }
750
751    #[test]
752    fn zero_limit_admits_only_empty_frames() {
753        assert!(split_frame_limited(&[0, 0, 0, 0], 0).unwrap().is_some());
754        assert_eq!(
755            split_frame_limited(&[1, 0, 0, 0], 0),
756            Err(FrameTooLarge {
757                declared: 1,
758                max: 0
759            })
760        );
761    }
762
763    #[test]
764    fn limit_above_u32_admits_every_prefix() {
765        assert_eq!(split_frame_limited(&[0xFF; 4], usize::MAX), Ok(None));
766    }
767
768    #[test]
769    fn error_converts_to_invalid_data() {
770        let e: std::io::Error = FrameTooLarge {
771            declared: 2000,
772            max: 1024,
773        }
774        .into();
775        assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
776        assert_eq!(e.to_string(), "frame too large: 2000 bytes (max 1024)");
777    }
778
779    /// Every prefix of a valid stream splits into a prefix of its frames
780    /// and stops exactly at the last frame boundary it contains.
781    #[test]
782    fn every_truncation_yields_whole_frames_only() {
783        let frames = vec![vec![], vec![1], vec![2, 3, 4], vec![], vec![5; 40]];
784        let stream = encode(&frames);
785        for end in 0..=stream.len() {
786            let (got, consumed) = split_all(&stream[..end], MAX_FRAME_SIZE);
787            assert_eq!(got, frames[..got.len()], "end {end}");
788            assert_eq!(consumed, encode(&got).len(), "end {end}");
789            // Whatever is left is less than the next frame.
790            if got.len() < frames.len() {
791                assert!(end - consumed < PREFIX_LEN + frames[got.len()].len());
792            }
793        }
794    }
795
796    // --- FrameDecoder ---
797
798    #[test]
799    fn decoder_splits_at_every_boundary() {
800        let frames = vec![vec![], vec![1], vec![2, 3, 4], vec![], vec![5; 40]];
801        let stream = encode(&frames);
802        for cut in 0..=stream.len() {
803            assert_eq!(decode_chunked(&stream, &[cut], MAX_FRAME_SIZE), frames);
804        }
805        for a in 0..=stream.len() {
806            for b in a..=stream.len() {
807                assert_eq!(decode_chunked(&stream, &[a, b], MAX_FRAME_SIZE), frames);
808            }
809        }
810    }
811
812    #[test]
813    fn decoder_byte_at_a_time() {
814        let frames = vec![vec![9; 3], vec![], vec![8; MAX_FRAME_SIZE]];
815        let stream = encode(&frames);
816        let cuts: Vec<usize> = (1..stream.len()).collect();
817        assert_eq!(decode_chunked(&stream, &cuts, MAX_FRAME_SIZE), frames);
818    }
819
820    #[test]
821    fn decoder_keeps_pipelined_bytes_pending() {
822        let mut dec = FrameDecoder::new();
823        dec.push(&encode(&[vec![1, 2]]));
824        dec.push(&[5, 0, 0, 0, 7]);
825        assert_eq!(dec.next().unwrap(), Some(&[1u8, 2][..]));
826        assert_eq!(dec.next().unwrap(), None);
827        assert_eq!(dec.pending(), &[5, 0, 0, 0, 7]);
828        // Compaction on push keeps the partial frame intact.
829        dec.push(&[8, 9, 10, 11]);
830        assert_eq!(dec.next().unwrap(), Some(&[7u8, 8, 9, 10, 11][..]));
831        assert!(dec.pending().is_empty());
832    }
833
834    #[test]
835    fn decoder_interleaved_push_and_single_next() {
836        // One `next` per push leaves complete frames pending across pushes:
837        // the deferred compaction must neither lose nor reorder them, and
838        // must keep the buffer bounded by twice its live bytes plus a push.
839        let frames: Vec<Vec<u8>> = (0..64u8).map(|i| vec![i; usize::from(i % 7)]).collect();
840        let stream = encode(&frames);
841        let mut dec = FrameDecoder::new();
842        let mut out = Vec::new();
843        for chunk in stream.chunks(5) {
844            let live_before = dec.pending().len();
845            dec.push(chunk);
846            assert!(dec.buf.len() <= 2 * live_before + chunk.len());
847            if let Some(p) = dec.next().unwrap() {
848                out.push(p.to_vec());
849            }
850        }
851        while let Some(p) = dec.next().unwrap() {
852            out.push(p.to_vec());
853        }
854        assert_eq!(out, frames);
855        assert!(dec.pending().is_empty());
856    }
857
858    #[test]
859    fn decoder_poisons_on_oversized_prefix() {
860        let mut dec = FrameDecoder::with_max_payload(256);
861        assert_eq!(dec.max_payload(), 256);
862        dec.push(&encode(&[vec![1]]));
863        dec.push(&257u32.to_le_bytes());
864        // The good frame ahead of it still comes out.
865        assert_eq!(dec.next().unwrap(), Some(&[1u8][..]));
866        let err = FrameTooLarge {
867            declared: 257,
868            max: 256,
869        };
870        assert_eq!(dec.next(), Err(err));
871        assert!(dec.is_poisoned());
872        assert!(dec.pending().is_empty());
873        // Later input is discarded and the original error repeats.
874        dec.push(&encode(&[vec![2]]));
875        assert!(dec.pending().is_empty());
876        assert_eq!(dec.next(), Err(err));
877    }
878
879    #[test]
880    fn decoder_starts_empty() {
881        let mut dec = FrameDecoder::default();
882        assert_eq!(dec.max_payload(), MAX_FRAME_SIZE);
883        assert!(!dec.is_poisoned());
884        assert_eq!(dec.next(), Ok(None));
885        dec.push(&[]);
886        assert_eq!(dec.next(), Ok(None));
887    }
888
889    // --- request frames ---
890
891    /// The ID the tests frame under unless they say otherwise: every byte
892    /// distinct, so a byte out of place shows.
893    const ID: RequestId = RequestId(0x0807_0605_0403_0201);
894
895    /// Request `id`'s frame carrying `body`, length prefix included, built
896    /// by hand.
897    fn request_frame_with(id: RequestId, body: &[u8]) -> Vec<u8> {
898        let mut frame = ((TAG_LEN + REQUEST_ID_LEN + body.len()) as u32)
899            .to_le_bytes()
900            .to_vec();
901        frame.push(TAG_REQUEST);
902        frame.extend_from_slice(&id.0.to_le_bytes());
903        frame.extend_from_slice(body);
904        frame
905    }
906
907    /// [`request_frame_with`] under [`ID`].
908    fn app_frame(body: &[u8]) -> Vec<u8> {
909        request_frame_with(ID, body)
910    }
911
912    /// `frame_request` with an encoder that copies `body`, under the
913    /// module's own error type, as request [`ID`].
914    fn frame_body(buf: &mut [u8], body: &[u8]) -> Result<usize, RequestFrameError> {
915        frame_body_as(buf, ID, body)
916    }
917
918    /// [`frame_body`] under `id`.
919    fn frame_body_as(
920        buf: &mut [u8],
921        id: RequestId,
922        body: &[u8],
923    ) -> Result<usize, RequestFrameError> {
924        frame_request(buf, id, |out| {
925            let Some(dst) = out.get_mut(..body.len()) else {
926                return Err(RequestFrameError::BufferTooSmall {
927                    needed: body.len(),
928                    available: out.len(),
929                });
930            };
931            dst.copy_from_slice(body);
932            Ok(body.len())
933        })
934    }
935
936    #[test]
937    fn request_constants_follow_the_frame_limit() {
938        assert_eq!(REQUEST_HEADER_LEN, PREFIX_LEN + TAG_LEN + REQUEST_ID_LEN);
939        assert_eq!(REQUEST_HEADER_LEN, 13);
940        // The ID comes out of the body: the frame limit stays.
941        assert_eq!(MAX_FRAME_SIZE, 1024);
942        assert_eq!(MAX_REQUEST_BODY, MAX_FRAME_SIZE - TAG_LEN - REQUEST_ID_LEN);
943        assert_eq!(MAX_REQUEST_BODY, 1015);
944        assert_eq!(
945            request_payload_len(MAX_REQUEST_BODY),
946            Ok(MAX_FRAME_SIZE as u32)
947        );
948        assert_eq!(
949            request_payload_len(MAX_REQUEST_BODY + 1),
950            Err(RequestFrameError::RequestTooLarge {
951                len: MAX_FRAME_SIZE + 1
952            })
953        );
954        assert_eq!(
955            request_payload_len(usize::MAX),
956            Err(RequestFrameError::RequestTooLarge { len: usize::MAX })
957        );
958    }
959
960    #[test]
961    fn request_body_is_the_rest_capped_at_the_widest_body() {
962        let mut buf = [0u8; 32];
963        assert_eq!(
964            request_body(&mut buf).unwrap().len(),
965            32 - REQUEST_HEADER_LEN
966        );
967        let mut wide = vec![0u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
968        assert_eq!(request_body(&mut wide).unwrap().len(), MAX_REQUEST_BODY);
969        let mut header_only = [0u8; REQUEST_HEADER_LEN];
970        assert!(request_body(&mut header_only).unwrap().is_empty());
971        for len in 0..REQUEST_HEADER_LEN {
972            assert_eq!(
973                request_body(&mut vec![0u8; len]),
974                Err(RequestFrameError::BufferTooSmall {
975                    needed: REQUEST_HEADER_LEN,
976                    available: len
977                })
978            );
979        }
980    }
981
982    #[test]
983    fn seal_request_writes_the_header_and_nothing_else() {
984        let mut buf = [0xEE; 32];
985        buf[REQUEST_HEADER_LEN..REQUEST_HEADER_LEN + 3].copy_from_slice(b"abc");
986        let n = seal_request(&mut buf, ID, 3).unwrap();
987        assert_eq!(n, REQUEST_HEADER_LEN + 3);
988        assert_eq!(&buf[..n], app_frame(b"abc").as_slice());
989        // The tag, then the ID, little-endian.
990        assert_eq!(buf[PREFIX_LEN], TAG_REQUEST);
991        assert_eq!(
992            &buf[PREFIX_LEN + 1..REQUEST_HEADER_LEN],
993            &ID.0.to_le_bytes()
994        );
995        assert!(buf[n..].iter().all(|&b| b == 0xEE));
996
997        // An empty body is a header-only frame, even in a header-sized
998        // buffer.
999        let mut buf = [0u8; REQUEST_HEADER_LEN];
1000        assert_eq!(seal_request(&mut buf, ID, 0), Ok(REQUEST_HEADER_LEN));
1001        assert_eq!(buf.as_slice(), app_frame(b"").as_slice());
1002
1003        // Any ID, zero included, goes on the wire as it is.
1004        for id in [0, 1, u64::MAX] {
1005            let mut buf = [0u8; REQUEST_HEADER_LEN];
1006            seal_request(&mut buf, RequestId(id), 0).unwrap();
1007            assert_eq!(buf.as_slice(), request_frame_with(RequestId(id), b""));
1008        }
1009    }
1010
1011    #[test]
1012    fn the_tag_and_id_are_laid_out_once() {
1013        let header = request_tag_and_id(ID);
1014        assert_eq!(header.len(), REQUEST_TAG_AND_ID_LEN);
1015        assert_eq!(header, request_frame_with(ID, b"")[PREFIX_LEN..]);
1016    }
1017
1018    #[test]
1019    fn split_request_reads_the_id_and_the_body() {
1020        let frame = app_frame(b"body");
1021        assert_eq!(split_request(&frame[PREFIX_LEN..]), Ok((ID, &b"body"[..])));
1022        // A request with no body.
1023        let empty = app_frame(b"");
1024        assert_eq!(split_request(&empty[PREFIX_LEN..]), Ok((ID, &b""[..])));
1025        // An empty payload.
1026        assert_eq!(split_request(&[]), Err(NotARequest::Empty));
1027        // A request too short for its ID, cut anywhere inside it.
1028        for len in 1..REQUEST_TAG_AND_ID_LEN {
1029            assert_eq!(
1030                split_request(&empty[PREFIX_LEN..PREFIX_LEN + len]),
1031                Err(NotARequest::TooShort { len })
1032            );
1033        }
1034        // Every other tag, whatever follows it.
1035        for tag in (0..=u8::MAX).filter(|&t| t != TAG_REQUEST) {
1036            let payload = [&[tag][..], &[0x11; 16]].concat();
1037            assert_eq!(split_request(&payload), Err(NotARequest::Tag(tag)));
1038            assert_eq!(split_request(&[tag]), Err(NotARequest::Tag(tag)));
1039        }
1040        // Each says what it saw.
1041        assert!(NotARequest::Empty.to_string().contains("empty"));
1042        assert!(NotARequest::Tag(0x09).to_string().contains("0x09"));
1043        assert!(
1044            NotARequest::TooShort { len: 4 }
1045                .to_string()
1046                .contains("4-byte")
1047        );
1048    }
1049
1050    #[test]
1051    fn seal_request_refuses_without_writing() {
1052        // A body past the buffer's end.
1053        let mut buf = [0x77u8; REQUEST_HEADER_LEN + 4];
1054        assert_eq!(
1055            seal_request(&mut buf, ID, 5),
1056            Err(RequestFrameError::BufferTooSmall {
1057                needed: REQUEST_HEADER_LEN + 5,
1058                available: REQUEST_HEADER_LEN + 4
1059            })
1060        );
1061        // No room for the header at all.
1062        for len in 0..REQUEST_HEADER_LEN {
1063            let mut short = vec![0x77u8; len];
1064            assert_eq!(
1065                seal_request(&mut short, ID, 0),
1066                Err(RequestFrameError::BufferTooSmall {
1067                    needed: REQUEST_HEADER_LEN,
1068                    available: len
1069                })
1070            );
1071            assert!(short.iter().all(|&b| b == 0x77));
1072        }
1073        // Over the frame limit, in a buffer wide enough to hold it: the
1074        // limit is checked first, and before the buffer when both fail.
1075        let mut wide = vec![0x77u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
1076        assert_eq!(
1077            seal_request(&mut wide, ID, MAX_REQUEST_BODY + 1),
1078            Err(RequestFrameError::RequestTooLarge {
1079                len: MAX_FRAME_SIZE + 1
1080            })
1081        );
1082        assert_eq!(
1083            seal_request(&mut buf, ID, usize::MAX),
1084            Err(RequestFrameError::RequestTooLarge { len: usize::MAX })
1085        );
1086        assert!(wide.iter().all(|&b| b == 0x77));
1087        assert!(buf.iter().all(|&b| b == 0x77));
1088    }
1089
1090    #[test]
1091    fn frame_request_writes_the_header_in_front_of_the_body() {
1092        let mut buf = [0xEE; 32];
1093        let n = frame_body(&mut buf, b"abc").unwrap();
1094        assert_eq!(n, REQUEST_HEADER_LEN + 3);
1095        assert_eq!(&buf[..n], app_frame(b"abc").as_slice());
1096        // Nothing past the frame is touched.
1097        assert!(buf[n..].iter().all(|&b| b == 0xEE));
1098
1099        // An empty body is a header-only frame, and the encoder is handed
1100        // everything after the header.
1101        let n = frame_request(&mut buf, ID, |out| {
1102            assert_eq!(out.len(), 32 - REQUEST_HEADER_LEN);
1103            Ok::<_, RequestFrameError>(0)
1104        })
1105        .unwrap();
1106        assert_eq!(&buf[..n], app_frame(b"").as_slice());
1107    }
1108
1109    #[test]
1110    fn frame_request_takes_the_widest_body_and_refuses_one_more() {
1111        let mut buf = vec![0u8; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
1112
1113        let widest = vec![0xAB; MAX_REQUEST_BODY];
1114        let n = frame_body(&mut buf, &widest).unwrap();
1115        assert_eq!(n, PREFIX_LEN + MAX_FRAME_SIZE);
1116        let frame = split_frame(&buf[..n]).unwrap().unwrap();
1117        assert_eq!(frame.consumed(), n);
1118        assert_eq!(split_request(frame.payload), Ok((ID, widest.as_slice())));
1119
1120        // One byte over: refused, and no header is written. The encoder is
1121        // handed exactly the widest body, though the buffer is wider, so a
1122        // codec sizing itself by its slice stays in bounds.
1123        let mut buf = vec![0x5C; REQUEST_HEADER_LEN + MAX_FRAME_SIZE + 16];
1124        let err = frame_request(&mut buf, ID, |out| {
1125            assert_eq!(out.len(), MAX_REQUEST_BODY);
1126            Ok::<_, RequestFrameError>(MAX_REQUEST_BODY + 1)
1127        })
1128        .unwrap_err();
1129        assert_eq!(
1130            err,
1131            RequestFrameError::RequestTooLarge {
1132                len: MAX_FRAME_SIZE + 1
1133            }
1134        );
1135        assert!(buf[..REQUEST_HEADER_LEN].iter().all(|&b| b == 0x5C));
1136
1137        // An encoder reporting an absurd length is refused, not wrapped;
1138        // the frame limit is checked before the region, even when the
1139        // buffer is too short as well.
1140        let err =
1141            frame_request(&mut buf, ID, |_| Ok::<_, RequestFrameError>(usize::MAX)).unwrap_err();
1142        assert_eq!(err, RequestFrameError::RequestTooLarge { len: usize::MAX });
1143        let mut small = [0u8; 16];
1144        let err = frame_request(&mut small, ID, |_| {
1145            Ok::<_, RequestFrameError>(MAX_REQUEST_BODY + 1)
1146        })
1147        .unwrap_err();
1148        assert!(
1149            matches!(err, RequestFrameError::RequestTooLarge { .. }),
1150            "{err:?}"
1151        );
1152    }
1153
1154    #[test]
1155    fn frame_request_refuses_a_buffer_too_small() {
1156        // No room for the header: the encoder is never called.
1157        for len in 0..REQUEST_HEADER_LEN {
1158            let mut buf = vec![0u8; len];
1159            let err = frame_request(&mut buf, ID, |_| -> Result<usize, RequestFrameError> {
1160                panic!("the encoder must not run without room for the header")
1161            })
1162            .unwrap_err();
1163            assert_eq!(
1164                err,
1165                RequestFrameError::BufferTooSmall {
1166                    needed: REQUEST_HEADER_LEN,
1167                    available: len
1168                }
1169            );
1170            assert!(err.to_string().contains("too small"), "{err}");
1171        }
1172
1173        // Exactly the header: room for an empty body only.
1174        let mut buf = [0u8; REQUEST_HEADER_LEN];
1175        assert_eq!(frame_body(&mut buf, b"").unwrap(), REQUEST_HEADER_LEN);
1176
1177        // An encoder claiming more than it was handed: refused, header
1178        // left unwritten.
1179        let mut buf = [0x77u8; REQUEST_HEADER_LEN + 4];
1180        let err = frame_request(&mut buf, ID, |_| Ok::<_, RequestFrameError>(5)).unwrap_err();
1181        assert_eq!(
1182            err,
1183            RequestFrameError::BufferTooSmall {
1184                needed: REQUEST_HEADER_LEN + 5,
1185                available: REQUEST_HEADER_LEN + 4
1186            }
1187        );
1188        assert!(buf[..REQUEST_HEADER_LEN].iter().all(|&b| b == 0x77));
1189    }
1190
1191    #[test]
1192    fn frame_request_passes_the_encoders_error_through() {
1193        #[derive(Debug, PartialEq)]
1194        enum CodecError {
1195            Mine,
1196            Framing(RequestFrameError),
1197        }
1198        impl From<RequestFrameError> for CodecError {
1199            fn from(e: RequestFrameError) -> Self {
1200                CodecError::Framing(e)
1201            }
1202        }
1203        let mut buf = [0x33u8; 24];
1204        assert_eq!(
1205            frame_request(&mut buf, ID, |_| Err(CodecError::Mine)),
1206            Err(CodecError::Mine)
1207        );
1208        assert!(buf.iter().all(|&b| b == 0x33));
1209        // The failures decided by `frame_request` arrive converted.
1210        assert_eq!(
1211            frame_request(&mut buf[..2], ID, |_| Ok::<_, CodecError>(0)),
1212            Err(CodecError::Framing(RequestFrameError::BufferTooSmall {
1213                needed: REQUEST_HEADER_LEN,
1214                available: 2
1215            }))
1216        );
1217    }
1218
1219    /// A codec's request encoder whose error is `ProtocolError`: framing
1220    /// failures convert with `?`, with no conversion of the codec's own.
1221    fn encode_request_as_codec(body: &[u8], buf: &mut [u8]) -> Result<usize, ProtocolError> {
1222        let region = request_body(buf)?;
1223        let dst = region
1224            .get_mut(..body.len())
1225            .ok_or(ProtocolError::InvalidField("body"))?;
1226        dst.copy_from_slice(body);
1227        Ok(seal_request(buf, ID, body.len())?)
1228    }
1229
1230    #[test]
1231    fn a_protocol_error_codec_frames_with_question_mark() {
1232        let mut buf = [0u8; 24];
1233        assert_eq!(
1234            encode_request_as_codec(b"abc", &mut buf).unwrap(),
1235            REQUEST_HEADER_LEN + 3
1236        );
1237        assert_eq!(&buf[..REQUEST_HEADER_LEN + 3], app_frame(b"abc").as_slice());
1238        assert!(matches!(
1239            encode_request_as_codec(b"", &mut buf[..2]),
1240            Err(ProtocolError::Truncated)
1241        ));
1242        // The closure form under the same error type.
1243        let err = frame_request(&mut [0u8; 16], ID, |_| {
1244            Ok::<_, ProtocolError>(MAX_REQUEST_BODY + 1)
1245        })
1246        .unwrap_err();
1247        assert!(
1248            matches!(err, ProtocolError::MessageTooLarge(len) if len == MAX_FRAME_SIZE + 1),
1249            "{err:?}"
1250        );
1251        assert!(matches!(
1252            ProtocolError::from(RequestFrameError::BufferTooSmall {
1253                needed: 9,
1254                available: 8
1255            }),
1256            ProtocolError::Truncated
1257        ));
1258    }
1259
1260    #[test]
1261    fn request_frame_errors_say_what_failed() {
1262        assert_eq!(
1263            RequestFrameError::RequestTooLarge { len: 2000 }.to_string(),
1264            format!(
1265                "request too large: 2000 bytes with its tag and ID, the frame limit is {MAX_FRAME_SIZE}"
1266            )
1267        );
1268        assert_eq!(
1269            RequestFrameError::BufferTooSmall {
1270                needed: 9,
1271                available: 8
1272            }
1273            .to_string(),
1274            "request buffer too small: the frame needs 9 bytes, the buffer holds 8"
1275        );
1276    }
1277
1278    #[test]
1279    fn frame_request_frames_at_an_offset_and_batches_back_to_back() {
1280        let bodies: [&[u8]; 4] = [b"one", b"", &[TAG_REQUEST, 0x00], b"four!"];
1281        let mut buf = [0xEE; 128];
1282        // Start part-way in, as after bytes already queued for sending.
1283        let start = 7;
1284        let mut end = start;
1285        let mut expected = Vec::new();
1286        for (n, body) in bodies.into_iter().enumerate() {
1287            let id = RequestId(n as u64 + 1);
1288            end += frame_body_as(&mut buf[end..], id, body).unwrap();
1289            expected.extend(request_frame_with(id, body));
1290        }
1291        assert!(buf[..start].iter().all(|&b| b == 0xEE));
1292        assert!(buf[end..].iter().all(|&b| b == 0xEE));
1293        assert_eq!(&buf[start..end], expected.as_slice());
1294    }
1295
1296    // --- properties ---
1297
1298    fn arb_frames(max: usize) -> impl Strategy<Value = Vec<Vec<u8>>> {
1299        prop::collection::vec(prop::collection::vec(any::<u8>(), 0..=max), 0..12)
1300    }
1301
1302    proptest! {
1303        /// Any chunking of a valid stream yields the same frames, in order,
1304        /// with nothing left over.
1305        #[test]
1306        fn any_chunking_yields_the_same_frames(
1307            frames in arb_frames(64),
1308            raw_cuts in prop::collection::vec(any::<prop::sample::Index>(), 0..16),
1309        ) {
1310            let stream = encode(&frames);
1311            let mut cuts: Vec<usize> =
1312                raw_cuts.iter().map(|i| i.index(stream.len() + 1)).collect();
1313            cuts.sort_unstable();
1314            prop_assert_eq!(decode_chunked(&stream, &cuts, 64), frames.clone());
1315            prop_assert_eq!(split_all(&stream, 64), (frames, stream.len()));
1316        }
1317
1318        /// An oversized prefix is refused with only its 4 bytes present,
1319        /// wherever it falls in the stream and however the stream is cut.
1320        #[test]
1321        fn oversized_prefix_fails_on_arrival(
1322            frames in arb_frames(32),
1323            max in 0usize..64,
1324            excess in 1u32..1_000_000,
1325            cut in any::<prop::sample::Index>(),
1326        ) {
1327            let good: Vec<Vec<u8>> =
1328                frames.into_iter().map(|mut f| { f.truncate(max); f }).collect();
1329            let declared = max as u32 + excess;
1330            let mut stream = encode(&good);
1331            stream.extend_from_slice(&declared.to_le_bytes());
1332            let cut = cut.index(stream.len() + 1);
1333
1334            let mut dec = FrameDecoder::with_max_payload(max);
1335            let mut got = Vec::new();
1336            let mut result = Ok(());
1337            for chunk in [&stream[..cut], &stream[cut..]] {
1338                dec.push(chunk);
1339                loop {
1340                    match dec.next() {
1341                        Ok(Some(p)) => got.push(p.to_vec()),
1342                        Ok(None) => break,
1343                        Err(e) => { result = Err(e); break; }
1344                    }
1345                }
1346            }
1347            prop_assert_eq!(got, good);
1348            prop_assert_eq!(result, Err(FrameTooLarge { declared, max }));
1349        }
1350
1351        /// Any run of requests that fit, under any IDs, framed back to back
1352        /// from any offset, decodes to the same IDs and bodies however the
1353        /// bytes arrive.
1354        #[test]
1355        fn framed_requests_round_trip_through_the_decoder(
1356            requests in prop::collection::vec(
1357                (any::<u64>(), prop::collection::vec(any::<u8>(), 0..=64)),
1358                0..8,
1359            ),
1360            offset in 0usize..16,
1361            chunk in 1usize..32,
1362        ) {
1363            let mut buf = vec![0u8; offset + requests.len() * (REQUEST_HEADER_LEN + 64)];
1364            let mut end = offset;
1365            for (id, body) in &requests {
1366                end += frame_body_as(&mut buf[end..], RequestId(*id), body).unwrap();
1367            }
1368            let mut decoder = FrameDecoder::new();
1369            let mut got = Vec::new();
1370            for piece in buf[offset..end].chunks(chunk) {
1371                decoder.push(piece);
1372                while let Some(payload) = decoder.next().unwrap() {
1373                    let (id, body) = split_request(payload).unwrap();
1374                    got.push((id.0, body.to_vec()));
1375                }
1376            }
1377            prop_assert_eq!(got, requests);
1378            prop_assert!(decoder.pending().is_empty());
1379        }
1380    }
1381}