Skip to content

blackbull.server.permessage_deflate

blackbull.server.permessage_deflate

permessage-deflate (RFC 7692) negotiation and per-message codec.

This module is small on purpose. It does two things:

  1. Handshake: parse a peer's Sec-WebSocket-Extensions offer, decide whether to accept permessage-deflate, and produce the response-side Sec-WebSocket-Extensions header value.

  2. Per-connection state: hold the streaming inflate/deflate state plus the *_no_context_takeover flags so :class:WebSocketRecipient can decompress inbound messages and :class:WebSocketSender can compress outbound ones.

The actual wire-level use of RSV1 happens in :mod:ws_codec and :mod:recipient; this module just owns the policy + state.

DeflateParams dataclass

Per-connection permessage-deflate parameters as negotiated.

InboundDecompressor

Streaming decompressor for inbound permessage-deflate messages.

The peer (client) is doing the compression here, so the no_context_takeover flag we care about is client_no_context_takeover. When set, the inflater is recreated for every message; otherwise the same inflater is reused so the sliding-window context carries forward across messages.

decompress(payload, *, max_length=None)

Inflate one whole compressed message. Raises zlib.error on bad data.

With max_length set, the inflated output is bounded by zlib itself rather than measured after the fact: a bomb is refused without ever being built. Asking for max_length + 1 is what makes "exactly at the bound" and "one byte over" distinguishable in a single call — zlib stops at the byte it was given, so a return of exactly max_length bytes is ambiguous on its own.

Raises :class:MessageTooLarge when the peer's message inflates past the bound. unconsumed_tail is the other half of that check: zlib stops early when it hits the limit, so leftover input means there was more to come even if the output landed on the boundary.

OutboundCompressor

Streaming compressor for outbound permessage-deflate messages.

Symmetric to :class:InboundDecompressor: the no_context_takeover flag that matters here is server_no_context_takeover (we are the server), deciding whether to reset the deflater between messages.

compress(payload)

Compress one whole message; strip the trailing 0x00 0x00 0xff 0xff per RFC 7692 §7.2.1.

negotiate(offer_header)

Decide whether to accept permessage-deflate based on the client's offer.

Returns (params, response_value) where:

  • params is the :class:DeflateParams to install on the connection (or None to decline).
  • response_value is the bytes to put after Sec-WebSocket-Extensions: in the 101 response (or None when declining).

Policy: accept context-takeover by default on both sides (better compression). Honour server_no_context_takeover and client_no_context_takeover when the client asks for them. Ignore unknown parameter names — that's what RFC 7692 §5.1 calls for.

offer_header is the raw bytes of the client's Sec-WebSocket-Extensions header (or None when absent). The client may send multiple comma-separated offers; we pick the first one that names permessage-deflate and that we can satisfy.