Expand description
I/O-free framing: split length-prefixed frames out of bytes the caller already holds.
The wire format is a 4-byte little-endian payload length followed by
that many payload bytes. BlockingFrameReader reads it straight off
a std::io::Read; this module is for programs that own their I/O loop
instead (io_uring completions, DPDK receive bursts, a non-blocking
socket polled by hand) and only need to be told where the frames are
in the bytes they have received so far.
Two layers:
split_frame/split_frame_limited: stateless and zero-copy. They look at the front of a caller-owned buffer and return the first complete frame as a borrowed slice,Noneif more bytes are needed, or an error if the length prefix declares more than the limit. The caller keeps its own buffer, cursor and compaction policy, so a receive loop on the hot path pays nothing beyond the bounds checks it would write by hand.FrameDecoder: an owning buffer built on the splitter, for a caller that would rather push received chunks and pull frames out.
The sending side has its counterpart for application requests: the
body is encoded in place in the caller’s send buffer, behind room for
a header of REQUEST_HEADER_LEN bytes, and seal_request writes
the length prefix, the protocol’s tag and the request’s ID in front of
it once its length is known (request_body hands out the region to
encode into, and frame_request does both around a closure). Frames
built at successive offsets lie back to back, ready for one write. A
codec crate that needs only framing depends on this crate alone, and a
codec whose error is ProtocolError frames with ?. On the
receiving side, split_request is the one parser of a request
frame’s payload: its ID and its body.
A receive loop over its own buffer:
use melin_wire_protocol::framing::{split_frame, FrameTooLarge};
fn on_receive(buf: &mut Vec<u8>, frames: &mut Vec<Vec<u8>>) -> Result<(), FrameTooLarge> {
let mut cursor = 0;
while let Some(frame) = split_frame(&buf[cursor..])? {
frames.push(frame.payload.to_vec());
cursor += frame.consumed();
}
// Keep the partial frame, if any, for the next receive.
buf.drain(..cursor);
Ok(())
}
let mut buf = vec![2, 0, 0, 0, b'h', b'i', 3, 0];
let mut frames = Vec::new();
on_receive(&mut buf, &mut frames)?;
assert_eq!(frames, [b"hi".to_vec()]);
assert_eq!(buf, [3, 0]);
// A prefix declaring more than the limit fails before its payload arrives.
buf.extend_from_slice(&[0xFF, 0xFF]);
assert!(on_receive(&mut buf, &mut frames).is_err());§Oversized frames
A length above the limit is rejected as soon as the 4 prefix bytes are readable, before any payload arrives. A peer that sends a corrupt or hostile prefix is therefore refused immediately instead of leaving the receiver buffering towards a length it will never accept. Past such a prefix the byte stream has no trustworthy frame boundary left: the connection should be closed, not resynchronised.
§Empty frames
A zero length is a valid frame with an empty payload: it consumes the 4 prefix bytes and nothing else. Framing does not judge content; a protocol that has no use for an empty frame rejects it one layer up, where it already inspects the payload.
Structs§
- Frame
- A complete frame found at the front of a buffer.
- Frame
Decoder - An owning frame buffer: push received bytes in, take complete frames out.
- Frame
TooLarge - A length prefix declared more payload than the limit allows.
Enums§
- NotA
Request - Why a frame’s payload is not a request: see
split_request. - Request
Frame Error - A request could not be framed. Nothing was written to the header, so a caller that sends only what a successful call reported sends nothing that could desync the node’s framing.
Constants§
- MAX_
REQUEST_ BODY - The widest request body one frame carries: the tag and the ID count
toward
MAX_FRAME_SIZE, so a body may take the rest of it. - PREFIX_
LEN - Size of the length prefix in front of every frame’s payload.
- REQUEST_
HEADER_ LEN - Bytes written ahead of a request body: the length prefix, the
protocol’s one-byte
TAG_REQUESTand the request’s eight-byte ID. - REQUEST_
TAG_ AND_ ID_ LEN - Bytes of a request frame’s payload ahead of its body: the protocol’s
one-byte
TAG_REQUESTand the request’s eight-byte ID.
Functions§
- frame_
request - Frame request
idin place at the front ofbuf, the closure form ofrequest_bodyandseal_request:encodewrites the application’s body into the regionrequest_bodyreturns and reports how many bytes it wrote, then the header goes in front of them. Returns the frame’s length, header included. - request_
body - The region of
bufa request body is encoded into, ahead ofseal_request: everything after the firstREQUEST_HEADER_LENbytes, capped atMAX_REQUEST_BODYbytes, so a codec that sizes its output by the slice it is given never writes a body the frame limit would refuse, even in a send buffer wider than one frame. - request_
payload_ len - The payload length of a request frame carrying
body_lenbytes (the tag, the ID and the body), orRequestFrameError::RequestTooLargeif that exceedsMAX_FRAME_SIZE. The one place the request limit is decided, for a sender that writes the header apart from the body as much as forseal_request. - request_
tag_ and_ id - The bytes of a request frame’s payload ahead of its body, for request
id:TAG_REQUEST, then the ID, little-endian. The one place they are laid out, forseal_requestand for a sender that writes the header apart from the body (melin-client’s blocking connection, which spares itself a copy of the body that way). - seal_
request - Seal request
id, whosebody_len-byte body already sits atbuf[REQUEST_HEADER_LEN..]: write the header (the length prefix, the protocol’s tag and the ID) in front of it. Returns the frame’s length, header included: the bytes to send arebuf[..n]. The caller never needs the protocol’s tag. The ID is the caller’s to choose: the node echoes it on the request’s final reply frame, and checks nothing about it. - split_
frame - Split the first frame off the front of
buf, with the protocol’sMAX_FRAME_SIZEas the payload limit. - split_
frame_ limited - Split the first frame off the front of
buf, refusing a payload longer thanmax_payloadbytes. - split_
request - Split a request frame’s payload (after the length prefix) into its ID
and its body: the one parser of a request, for a node’s readers and for
any program that reads requests off the wire (a proxy’s trace, a
gateway’s codec). Anything but a
TAG_REQUESTpayload with room for the ID isNotARequest; the body may be empty.