Skip to content

websocket: add opt-in bounded upgrades - #92

Open
Roasbeef wants to merge 2 commits into
rawhat:masterfrom
Roasbeef:websocket/bounded-ingress
Open

Roasbeef wants to merge 2 commits into
rawhat:masterfrom
Roasbeef:websocket/bounded-ingress

Conversation

@Roasbeef

@Roasbeef Roasbeef commented Sep 5, 2026

Copy link
Copy Markdown

In this PR, we add mist.websocket_with_options for applications that need per-connection WebSocket input limits. The callbacks match the existing mist.websocket API; callers opt in with WebsocketOptions(max_frame_bytes, max_message_bytes, CompressionDisabled). Both limits must be positive or the upgrade returns HTTP 400.

The decoder checks declared payload lengths before retaining or unmasking bodies. Fragmented messages share a cumulative limit across TCP reads, including when control frames interrupt them. Oversized input closes with code 1009. Detected framing violations or compressed frames close with code 1002; other protocol validation retains the existing decoder's behavior. Bounded connections do not negotiate compression.

Each input chunk is consumed one decoded frame or message at a time. The socket is rearmed once after the entire chunk, so multiple frames cannot each grant an extra socket read. The existing mist.websocket decoder and extension negotiation remain unchanged. No production Erlang is added.

The parser depends on gramps #11. This branch pins our reviewed gramps fork commit a37a8ae3fe2531375b49b865c155eb2987d4d22a so it can be evaluated independently. That git pin would become an upstream release dependency if the parser is accepted and released. We have merged both changes into our forks for a multi-session daemon and are offering them upstream for consideration.

These limits cover input retained by one connection's decoder. Applications must separately bound connection count, queued output, and data retained by their callbacks. They are not a total server memory bound or a remote-consumption acknowledgement.

All four new raw TCP regressions pass against the published dependency pin under a 120-second outer deadline. They cover an oversized incomplete header, cumulative fragments across separate reads, compression negotiation and rejection, actual socket closure, and ordinary text/fragment delivery. The full 22-test suite passed during development. The two latest pinned runs passed 21 tests and failed the existing http1_test.it_rejects_large_requests_test with Error(Other(Closed)). The identical failure reproduces on unchanged Mist 6be7897 (one 18/18 run, then 17/18); this proposal leaves that HTTP path unchanged. Formatting and whitespace checks pass.

Independent review found no blocking regression. Its documentation finding was addressed by narrowing the protocol-validation claim above.

In this commit, we expose per-connection frame and fragmented-message limits through websocket_with_options. The gramps decoder checks declared lengths before retaining payload bytes, including when a peer sends an oversized header without its body. Compression stays disabled for bounded connections so inflation cannot bypass the input limit.

The existing websocket API retains its decoder and negotiation behavior. Both paths rearm the socket once after processing an entire input chunk, rather than granting additional reads per frame. Raw TCP tests cover early rejection, fragments across reads, compression refusal, actual socket closure, and ordinary message delivery. The dependency is pinned to the reviewed gramps fork revision for evaluation.
In this commit, we regenerate the dependency manifest against the published gramps fork revision. This keeps the bounded decoder evaluation reproducible without a local filesystem dependency.
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