Skip to main content

Module compression

Module compression 

Source
Available on crate feature compression only.
Expand description

Application-layer payload compression.

§Honest layer assessment: why this is NOT permessage-deflate

The right layer for WebSocket compression is the handshake extension (permessage-deflate, RFC 7692): it compresses at the frame boundary, shares context across frames, and is negotiated by the server echoing Sec-WebSocket-Extensions during the upgrade.

The underlying stack cannot do that today. As of the versions this crate builds against — tokio-tungstenite 0.29 (what axum 0.8 pulls) and tungstenite 0.29 — permessage-deflate is not implemented; tungstenite merely parses the Sec-WebSocket-Extensions header for handshake validation and offers no extension hook a library could wire a negotiation into. Shipping a hand-rolled RFC 7692 extension on top of a transport that owns the framing would mean re-implementing the context-takeover state machine against frame internals we do not control — the wrong layer and a correctness hazard.

So the compression feature provides an application-layer codec instead, and says so plainly:

  • Payloads are DEFLATE-compressed (RFC 1951, raw) via flate2 with the pure-Rust miniz_oxide backend — the crate’s #![forbid(unsafe_code)] posture is preserved end to end.
  • Compressed payloads travel inside ordinary binary frames with a 1-byte envelope (see FrameCompressor). Both endpoints must run ws-kit and opt in — browsers cannot use this: they only speak permessage-deflate. This is for Rust-to-Rust links (ws-kit server ↔ ws-kit client/proxy/worker).
  • “Negotiation” is out-of-band configuration (both sides build with the feature and construct a compressor). For integrators who signal the capability in a custom handshake header, extension_accepted matches the EXTENSION_NAME token case-insensitively in a comma-separated header value. When a tungstenite/axum handshake-level implementation lands upstream, CompressionConfig is what gets wired through — the config struct is deliberately transport-shaped.

§Security posture

  • Decompression-bomb guard: FrameCompressor::decompress refuses output larger than CompressionConfig::max_size (checked during inflation via Read::take, so a 10 GiB bomb never materializes in memory) and refuses identity payloads above the same cap.
  • Fail-closed envelopes: unknown flag bits are rejected, corrupt deflate streams error (WsError::Compression), and a text frame restored from non-UTF-8 bytes errors rather than lossy-coercing.
  • Compressed payloads ride Frame::Binary — the crate-wide rule that binary is never parsed as text/JSON applies to them too.

§Example

use ws_kit::codec::Frame;
use ws_kit::compression::{CompressionConfig, FrameCompressor};

let c = FrameCompressor::new(CompressionConfig::default());
let frame = Frame::from("a very repetitive payload ".repeat(50));
let wire = c.compress_frame(frame.clone()).unwrap();
// Wire is always a Binary frame with the envelope prefix:
assert!(wire.is_binary());
let restored = c.decompress_frame(wire).unwrap();
assert_eq!(restored, frame);

Structs§

CompressionConfig
Compression settings.
FrameCompressor
Encodes/decodes Frames into compressed wire form.

Constants§

EXTENSION_NAME
Capability token for out-of-band negotiation in a custom handshake header. Not permessage-deflate — see the module docs for why this is an application-layer codec.

Functions§

extension_accepted
True when a handshake header value (e.g. the integrator’s custom capability header) offers EXTENSION_NAME.
offer_extension
The value a client/server puts in its capability header to offer compression. Alias of EXTENSION_NAME for call-site readability.