HTTP/2 frames
http2-codec is Cruft's HTTP/2 frame serializer and deserializer, the layer that turns bytes into typed frames and frames back into bytes. It bounds-checks every frame it parses but holds no connection state, so frame-size limits and flow control live one layer up in http2-conn, not here.
HTTP/2 is a binary protocol; you cannot eyeball it the way you can HTTP/1. Every message on the wire is a binary frame: a 9-octet header (24-bit length, 8-bit type, 8-bit flags, 31-bit stream id) followed by a payload. Something has to read those 9 octets, decide whether a whole frame has arrived yet, and hand back a typed value. In Node that layer is h2's frame codec, buried under the stream state machine.
Cruft splits that job into its own crate. This page is about that crate, and only that crate: the part that turns bytes into frames and frames back into bytes.
Alpha, do not deploy.http2-codec(craterusty-http2-codec, Cruft 0.0.10) is a stand-in for the frame layer of the crates.ioh2crate. It has not been externally audited and has near-zero interop or conformance testing. The bounds-checks below are exercised by the crate's own tests; the safety limits HTTP/2 actually needs live one layer up inhttp2-conn, not here. Do not put it in front of adversarial input in production.
Framing is one layer of the protocol
h2 bundles the whole HTTP/2 library into one thing: it manages streams, flow control, priorities, HPACK, the lot. http2-codec does exactly one layer of that: it is a pure frame serializer/deserializer. It has no connection, no streams, no state, no sockets, no timers. Its whole surface is functions like encode_frame, decode_frame_header, take_frame, encode_settings_frame, encode_window_update, encode_goaway, encode_data, encode_headers, plus the matching decoders. Give it a byte slice, get a Frame (a FrameHeader plus a payload: Vec<u8>). Give it a Frame's parts, get bytes. That is the contract.
The reason this matters: the security questions that count (can someone send a 16 MB frame and blow up memory? can a client send unlimited DATA past the receive window?) are not answered here. They cannot be. This crate does not know what the connection agreed to. It has no window to exceed. Those checks live one layer up, in http2-conn.
What it does, and what the tests cover
Frame header codec is exact and bounds-checked. decode_frame_header returns None on a buffer shorter than 9 octets, masks off the reserved bit on the stream id (& 0x7FFF_FFFF), and reconstructs the 24-bit length. encode_frame_header is its inverse. Round-trip and reserved-bit masking are covered by a header test over a stream id of 0x7FFF_FFFF after the R bit is set.
take_frame will not hand you a partial frame. It reads the declared length, computes total = 9 + length, and returns None if fewer than total bytes are buffered. The streaming/incremental split (feed two frames concatenated, get them one at a time) is tested.
Padding stripping is underflow-safe. strip_padding (used by data_payload) and header_block_fragment both read the Pad Length octet and return None rather than panicking when the declared padding exceeds the remaining payload. A DATA-with-padding round-trip is tested, as is a padded HEADERS fragment.
SETTINGS length is validated on the checked path. decode_settings_payload_checked returns None when the payload length is not a multiple of 6, the codec-side FRAME_SIZE_ERROR signal. The older decode_settings_payload still exists and silently truncates via chunks_exact(6); callers that care about the RFC rule must use the _checked variant.
The per-frame-type encoders round-trip. SETTINGS, SETTINGS ACK, WINDOW_UPDATE, PING, PING ACK, RST_STREAM, GOAWAY, DATA, and HEADERS all have encode functions and their decoders are exercised by the in-file tests. window_update_increment and decode_goaway return None on wrong-length payloads.
Everything else you would want from an HTTP/2 library is not here. There is no HPACK (that is http2-hpack), no stream state machine, no flow-control accounting, no negotiation, no priority tree, no CONTINUATION reassembly. The RFC 9113 error-code constants (ERR_ENHANCE_YOUR_CALM, ERR_FLOW_CONTROL_ERROR, and so on) are defined but this crate never raises them: it is the connection layer that decides to send a GOAWAY with one of them.
The codec trusts the length field
Here is the one distinction that would bite a caller who stopped reading at "it's bounds-checked." take_frame trusts the 24-bit length header. A 24-bit length can declare up to ~16 MB. take_frame itself does not compare that against any negotiated SETTINGS_MAX_FRAME_SIZE: the DEFAULT_MAX_FRAME_SIZE constant (16_384) is present but this function does not enforce it. Feed raw socket bytes straight into take_frame and buffer until it returns Some, and an attacker declaring 16 MB frames could grow the read buffer unboundedly.
That is not a bug in this crate, because bounding inbound frame size is not this crate's job. It is the job of whoever owns the connection buffer. In Cruft that owner is http2-conn.
Where the frame-size and flow-control defense lives
Inbound frame-size enforcement, receive-side flow control, WINDOW_UPDATE validation, SETTINGS range checks, and stream-ID rules are all deferred to the connection layer, and they are implemented there now. The fix is split across the two crates the way the layering demands:
http2-conn(the connection layer) rejects oversized partial frames before buffering the full payload, tracks inbound connection and per-stream receive windows for DATA, rejects zero and overflowingWINDOW_UPDATE, validates SETTINGS stream/ACK shape plusENABLE_PUSH/INITIAL_WINDOW_SIZE/MAX_FRAME_SIZEranges, and rejects DATA reuse on a closed stream.http2-codec(this crate) contributeddecode_settings_payload_checked, so a non-multiple-of-six SETTINGS payload produces a frame-size failure instead of silent truncation.
Focused tests cover partial oversized frames, malformed SETTINGS, WINDOW_UPDATE zero and overflow, receive-window exhaustion, and closed-stream DATA reuse, and they pass across both crates while the runtime still builds. Default hostile-input tests also cover HPACK expansion, dynamic-table-size-update refusal, rapid reset, concurrent-stream flood, CONTINUATION flood, and oversized inbound frame rejection. h2spec conformance, a fuzz target, and a differential against nghttp2 remain a stated high-value follow-on, not a closed item.
Limitations
The maturity baseline is h2, which has years of production traffic, an interop history against nghttp2/curl/browsers, and a fuzzing corpus. http2-codec has none of that. Concretely:
- It is a codec; the defenses live in
http2-conn. Every DoS-relevant limit (frame size, flow control, stream caps, header-list size) is enforced byhttp2-conn, not here. Reading this crate in isolation says nothing about whether Cruft's HTTP/2 server is safe to expose: readhttp2-connfor that. It trusts the length field and leaves frame-size, flow-control, and stream-state defense tohttp2-conn, where those checks now exist but still lack h2spec/fuzz/differential proof. - Two SETTINGS decoders, one silent on malformed input.
decode_settings_payloadtruncates malformed input silently; onlydecode_settings_payload_checkedrefuses it. A caller wiring the wrong one reopens part of the frame-size gap at their own call site. - Test coverage is thin and self-generated. The in-file tests are happy-path round-trips over well-formed input the crate itself produced. There is no h2spec conformance lane, no fuzz target, and no differential against
h2at the frame level. The hostile-input coverage lives in the connection crate's tests, not here. - No claim of interop. Nothing in-tree shows a real HTTP/2 client (curl, a browser, nghttp2) can talk to frames this crate produces. Assume unproven until a differential exists.