Skip to content

websocket: add bounded incremental decoding - #11

Open
Roasbeef wants to merge 1 commit into
rawhat:masterfrom
Roasbeef:websocket/bounded-decoder
Open

Roasbeef wants to merge 1 commit into
rawhat:masterfrom
Roasbeef:websocket/bounded-decoder

Conversation

@Roasbeef

@Roasbeef Roasbeef commented Sep 5, 2026 •

Copy link
Copy Markdown

In this PR, we add an opt-in incremental WebSocket decoder that checks a frame's declared payload length before retaining or unmasking its body. A peer can advertise an oversized payload and then stop sending, so an application-level check after decoding cannot enforce this bound.

The decoder carries fragmented-message state across input boundaries and counts continuation payloads against one message limit, including when control frames interrupt the message. It returns one completed frame or message together with the unconsumed input. Compression is rejected before inflation.

Both limits are caller-configured and must be positive. The existing decoder API remains unchanged, and no Erlang code is added. These are per-connection input bounds, not limits on connection count, socket output, or application-retained data. Broader protocol validation retains the existing behavior.

We built this for a long-lived daemon hosting multiple agent sessions, where checking a completed application message is too late to bound parser memory. Mist #92 exposes the opt-in decoder through its upgrade API. We have merged the change into our fork for that integration and are offering it here for consideration; upstream acceptance is not a prerequisite for our deployment.

All 18 tests pass locally under a 120-second outer deadline. Coverage includes incomplete oversized headers, exact extended-length boundaries, cumulative fragments across TCP reads, interleaved control frames, compressed input, and every byte split of a masked message. An isolated negative control that raises both configured limits fails four assertions, including rejection before a payload arrives. Independent review found no blocking regression.

In this commit, we add an incremental decoder that checks a frame's declared payload length before retaining or unmasking its body. Fragment state survives input boundaries and counts toward one message budget, including when control frames interrupt it.

The decoder returns one completed message and unconsumed input instead of collecting an arbitrary batch. Compression is refused before inflation; the existing decoder remains unchanged for existing callers. Tests cover incomplete oversized headers, exact extended-length boundaries, fragmentation across chunks, control frames, and every split of a masked message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant