compress_lib/message.rs
1//! Per-message compression for WebSocket permessage-deflate (RFC 7692).
2//!
3//! A message is compressed with a sync flush and sent without the trailing
4//! `00 00 FF FF`; the receiver appends those four bytes and inflates. With
5//! context takeover the window carries over between messages; without it
6//! every message stands alone (the safer default: secrets in one message
7//! cannot leak through the compressed size of the next, RFC 7692 section 8).
8//!
9//! This module does the codec part only. Negotiating
10//! `Sec-WebSocket-Extensions`, the RSV1 bit, and the per-message opt-out
11//! belong to the WebSocket layer.
12//!
13//! ```
14//! use compress_lib::message::{Compressor, Decompressor, MessageConfig};
15//!
16//! let config = MessageConfig::new().with_window_bits(12);
17//! let mut tx = Compressor::new(config)?;
18//! let mut rx = Decompressor::new(config)?;
19//! let mut wire = Vec::new();
20//! tx.compress_message(b"{\"hello\":\"world\"}", &mut wire)?;
21//! let mut text = Vec::new();
22//! rx.decompress_message(&wire, &mut text, 1 << 20)?;
23//! assert_eq!(text, b"{\"hello\":\"world\"}");
24//! # Ok::<(), compress_lib::Error>(())
25//! ```
26
27use alloc::vec::Vec;
28
29use crate::{
30 Error, ErrorKind, Level,
31 deflate::{Deflater, Flush, Inflater, MemoryConfig},
32};
33
34/// The four bytes a sync flush ends with, removed by the sender and put
35/// back by the receiver.
36const TAIL: [u8; 4] = [0x00, 0x00, 0xFF, 0xFF];
37
38/// Settings for one direction of a permessage-deflate connection.
39///
40/// | Setting | Default | RFC 7692 parameter |
41/// |---|---|---|
42/// | window bits | 15 | `server_max_window_bits` / `client_max_window_bits` |
43/// | context takeover | off | `server_no_context_takeover` / `client_no_context_takeover` absent |
44/// | level | 1 | (local choice) |
45///
46/// Idle connections: without takeover nothing carries over between
47/// messages, so an idle connection can drop its engines and build new ones
48/// when traffic returns (construction allocates the sizes below once, about
49/// a microsecond for a decompressor). With takeover the window is
50/// connection state and must be kept; [`Compressor::wipe`] and
51/// [`Decompressor::wipe`] zero it when the connection ends.
52///
53/// Memory per direction: without takeover a [`Decompressor`] holds only its
54/// decode tables (about 11 KiB, no window); with takeover it adds the window
55/// (`2^window_bits` bytes). A [`Compressor`] holds a deflater sized by the
56/// window bits (about 270 KiB at 15, under 100 KiB at 9).
57///
58/// # Examples
59///
60/// ```
61/// use compress_lib::{Level, message::MessageConfig};
62///
63/// let config = MessageConfig::new().with_window_bits(10).with_context_takeover(true).with_level(Level::DEFAULT);
64/// assert_eq!(config.window_bits(), 10);
65/// assert!(config.context_takeover());
66/// ```
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
68#[non_exhaustive]
69pub struct MessageConfig {
70 window_bits: u8,
71 context_takeover: bool,
72 level: Level,
73 memory: MemoryConfig,
74}
75
76impl MessageConfig {
77 /// Window bits 15, no context takeover, level 1.
78 ///
79 /// # Examples
80 ///
81 /// ```
82 /// use compress_lib::message::MessageConfig;
83 ///
84 /// assert_eq!(MessageConfig::new(), MessageConfig::default());
85 /// assert!(!MessageConfig::new().context_takeover());
86 /// ```
87 #[must_use]
88 pub const fn new() -> Self {
89 Self {
90 window_bits: 15,
91 context_takeover: false,
92 level: Level::FAST,
93 memory: MemoryConfig::new(),
94 }
95 }
96
97 /// Sets the window bits, 8 to 15 (checked when an engine is built).
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// assert_eq!(compress_lib::message::MessageConfig::new().with_window_bits(9).window_bits(), 9);
103 /// ```
104 #[must_use]
105 pub const fn with_window_bits(mut self, bits: u8) -> Self {
106 self.window_bits = bits;
107 self
108 }
109
110 /// Keeps the window from one message to the next.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// assert!(compress_lib::message::MessageConfig::new().with_context_takeover(true).context_takeover());
116 /// ```
117 #[must_use]
118 pub const fn with_context_takeover(mut self, on: bool) -> Self {
119 self.context_takeover = on;
120 self
121 }
122
123 /// Sets the compression level (the compressor only).
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use compress_lib::{Level, message::MessageConfig};
129 ///
130 /// assert_eq!(MessageConfig::new().with_level(Level::BEST).level(), Level::BEST);
131 /// ```
132 #[must_use]
133 pub const fn with_level(mut self, level: Level) -> Self {
134 self.level = level;
135 self
136 }
137
138 /// Sizes the compressor's hash table and symbol buffer (see
139 /// [`MemoryConfig`]); the decompressor is not affected.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// use compress_lib::{deflate::MemoryConfig, message::MessageConfig};
145 ///
146 /// let config = MessageConfig::new().with_memory(MemoryConfig::new().with_hash_bits(10));
147 /// assert_eq!(config.memory().hash_bits(), 10);
148 /// ```
149 #[must_use]
150 pub const fn with_memory(mut self, memory: MemoryConfig) -> Self {
151 self.memory = memory;
152 self
153 }
154
155 /// The compressor's memory settings.
156 ///
157 /// # Examples
158 ///
159 /// ```
160 /// assert_eq!(compress_lib::message::MessageConfig::new().memory().hash_bits(), 0);
161 /// ```
162 #[must_use]
163 pub const fn memory(&self) -> MemoryConfig {
164 self.memory
165 }
166
167 /// The window bits.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// assert_eq!(compress_lib::message::MessageConfig::new().window_bits(), 15);
173 /// ```
174 #[must_use]
175 pub const fn window_bits(&self) -> u8 {
176 self.window_bits
177 }
178
179 /// Whether the window carries over between messages.
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// assert!(!compress_lib::message::MessageConfig::new().context_takeover());
185 /// ```
186 #[must_use]
187 pub const fn context_takeover(&self) -> bool {
188 self.context_takeover
189 }
190
191 /// The compression level.
192 ///
193 /// # Examples
194 ///
195 /// ```
196 /// use compress_lib::{Level, message::MessageConfig};
197 ///
198 /// assert_eq!(MessageConfig::new().level(), Level::FAST);
199 /// ```
200 #[must_use]
201 pub const fn level(&self) -> Level {
202 self.level
203 }
204}
205
206impl Default for MessageConfig {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212/// Compresses messages for permessage-deflate.
213///
214/// # Examples
215///
216/// ```
217/// use compress_lib::message::{Compressor, MessageConfig};
218///
219/// let mut tx = Compressor::new(MessageConfig::new())?;
220/// let mut wire = Vec::new();
221/// tx.compress_message(b"Hello", &mut wire)?;
222/// assert_eq!(wire, [0xF2, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00]); // RFC 7692 7.2.3.1
223/// # Ok::<(), compress_lib::Error>(())
224/// ```
225#[derive(Debug)]
226pub struct Compressor {
227 deflater: Deflater,
228 takeover: bool,
229}
230
231impl Compressor {
232 /// A compressor for one direction of a connection.
233 ///
234 /// # Errors
235 ///
236 /// [`ErrorKind::InvalidWindowBits`] outside 8 to 15.
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// use compress_lib::message::{Compressor, MessageConfig};
242 ///
243 /// assert!(Compressor::new(MessageConfig::new().with_window_bits(8)).is_ok());
244 /// assert!(Compressor::new(MessageConfig::new().with_window_bits(16)).is_err());
245 /// ```
246 pub fn new(config: MessageConfig) -> Result<Self, Error> {
247 let deflater = Deflater::new(config.level)
248 .with_window_bits(config.window_bits)?
249 .with_memory(config.memory);
250 Ok(Self {
251 deflater,
252 takeover: config.context_takeover,
253 })
254 }
255
256 /// Compresses one message, appending the payload (without the trailing
257 /// `00 00 FF FF`) to `out`. Returns the payload length. An empty
258 /// message compresses to the single byte `00`.
259 ///
260 /// The payload can be longer than the message for incompressible data;
261 /// the WebSocket layer may then send the message uncompressed instead
262 /// (with takeover the compressor's window has advanced either way, so
263 /// call [`reset`](Compressor::reset) and tell the peer, or keep sending
264 /// compressed).
265 ///
266 /// # Errors
267 ///
268 /// [`ErrorKind::AllocationFailed`] when `out` cannot grow.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// use compress_lib::message::{Compressor, MessageConfig};
274 ///
275 /// let mut tx = Compressor::new(MessageConfig::new())?;
276 /// let mut wire = Vec::new();
277 /// assert_eq!(tx.compress_message(b"", &mut wire)?, 1);
278 /// assert_eq!(wire, [0x00]);
279 /// # Ok::<(), compress_lib::Error>(())
280 /// ```
281 pub fn compress_message(&mut self, message: &[u8], out: &mut Vec<u8>) -> Result<usize, Error> {
282 let start = out.len();
283 let result = self.deflater.compress_vec(message, out, Flush::Sync);
284 if let Err(e) = result {
285 out.truncate(start);
286 self.deflater.reset();
287 return Err(e);
288 }
289 // A sync flush always ends with the empty stored block's length
290 // bytes; take them off.
291 if out.len() >= start + TAIL.len() && out[out.len() - TAIL.len()..] == TAIL {
292 out.truncate(out.len() - TAIL.len());
293 }
294 if !self.takeover {
295 self.deflater.reset();
296 }
297 Ok(out.len() - start)
298 }
299
300 /// Zeroes the compressor's window, hash chains, and buffers (they hold
301 /// plaintext of earlier messages), keeping the allocations; see
302 /// [`Deflater::wipe`] for what "zeroes" can promise without `unsafe`.
303 ///
304 /// # Examples
305 ///
306 /// ```
307 /// use compress_lib::message::{Compressor, MessageConfig};
308 ///
309 /// let mut tx = Compressor::new(MessageConfig::new().with_context_takeover(true))?;
310 /// tx.compress_message(b"token=secret", &mut Vec::new())?;
311 /// tx.wipe();
312 /// # Ok::<(), compress_lib::Error>(())
313 /// ```
314 pub fn wipe(&mut self) {
315 self.deflater.wipe();
316 }
317
318 /// Forgets the window (for a connection that restarts its context).
319 ///
320 /// # Examples
321 ///
322 /// ```
323 /// use compress_lib::message::{Compressor, MessageConfig};
324 ///
325 /// let mut tx = Compressor::new(MessageConfig::new().with_context_takeover(true))?;
326 /// tx.reset();
327 /// # Ok::<(), compress_lib::Error>(())
328 /// ```
329 pub fn reset(&mut self) {
330 self.deflater.reset();
331 }
332
333 /// Heap bytes held.
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// use compress_lib::message::{Compressor, MessageConfig};
339 ///
340 /// let small = Compressor::new(MessageConfig::new().with_window_bits(9))?;
341 /// let big = Compressor::new(MessageConfig::new())?;
342 /// assert!(small.memory_in_use() < big.memory_in_use());
343 /// # Ok::<(), compress_lib::Error>(())
344 /// ```
345 #[must_use]
346 pub fn memory_in_use(&self) -> usize {
347 self.deflater.memory_in_use()
348 }
349}
350
351/// Decompresses permessage-deflate messages.
352///
353/// The output budget is checked per output chunk while inflating, never
354/// after, so a compressed bomb stops at the budget; the error is
355/// [`ErrorKind::OutputLimit`] (WebSocket close code 1009). Undecodable data
356/// is any other kind (close code 1007).
357///
358/// # Examples
359///
360/// ```
361/// use compress_lib::{ErrorKind, message::{Decompressor, MessageConfig}};
362///
363/// let mut rx = Decompressor::new(MessageConfig::new())?;
364/// let mut text = Vec::new();
365/// rx.decompress_message(&[0xF2, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00], &mut text, 1024)?;
366/// assert_eq!(text, b"Hello");
367/// let err = rx.decompress_message(&[0xF2, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00], &mut Vec::new(), 4).unwrap_err();
368/// assert_eq!(err.kind(), ErrorKind::OutputLimit);
369/// # Ok::<(), compress_lib::Error>(())
370/// ```
371#[derive(Debug)]
372pub struct Decompressor {
373 inflater: Inflater,
374 takeover: bool,
375 /// Where the current message starts in the caller's vector, while a
376 /// fragmented message is open.
377 open: Option<OpenMessage>,
378}
379
380#[derive(Clone, Copy, Debug)]
381struct OpenMessage {
382 start: usize,
383 produced: usize,
384 max: usize,
385}
386
387impl Decompressor {
388 /// A decompressor for one direction of a connection.
389 ///
390 /// # Errors
391 ///
392 /// [`ErrorKind::InvalidWindowBits`] outside 8 to 15.
393 ///
394 /// # Examples
395 ///
396 /// ```
397 /// use compress_lib::message::{Decompressor, MessageConfig};
398 ///
399 /// assert!(Decompressor::new(MessageConfig::new().with_window_bits(8)).is_ok());
400 /// ```
401 pub fn new(config: MessageConfig) -> Result<Self, Error> {
402 let inflater = if config.context_takeover {
403 Inflater::new().with_window_bits(config.window_bits)?
404 } else {
405 // Each message is decoded into one vector, which is its own
406 // history: no window is kept.
407 Inflater::without_history(crate::Limits::unlimited())
408 .with_window_bits(config.window_bits)?
409 };
410 Ok(Self {
411 inflater,
412 takeover: config.context_takeover,
413 open: None,
414 })
415 }
416
417 /// Decompresses one whole message payload, appending to `out`, with at
418 /// most `max` bytes of output. Returns the bytes appended.
419 ///
420 /// # Errors
421 ///
422 /// `OutputLimit` past `max`; `TrailingData` for nonzero bytes after a
423 /// final block; `UnexpectedEnd` when the message, with the
424 /// `00 00 FF FF` tail appended, does not end on a block boundary (a
425 /// block cut short, or a sender that left the tail in place); any
426 /// decoding error. After an error the decompressor is reset
427 /// (the connection should be failed anyway).
428 ///
429 /// # Examples
430 ///
431 /// ```
432 /// use compress_lib::message::{Decompressor, MessageConfig};
433 ///
434 /// let mut rx = Decompressor::new(MessageConfig::new())?;
435 /// let mut out = Vec::new();
436 /// assert_eq!(rx.decompress_message(&[0x00], &mut out, 16)?, 0); // empty message
437 /// # Ok::<(), compress_lib::Error>(())
438 /// ```
439 pub fn decompress_message(
440 &mut self,
441 payload: &[u8],
442 out: &mut Vec<u8>,
443 max: usize,
444 ) -> Result<usize, Error> {
445 self.decompress_fragment(payload, true, out, max)
446 }
447
448 /// Decompresses one frame of a message, appending to `out`; `last` marks
449 /// the final frame. Every fragment of a message must append to the same
450 /// vector, untouched in between: the vector is the message's history.
451 /// `max` caps the whole message. Returns the bytes appended by this
452 /// fragment.
453 ///
454 /// # Errors
455 ///
456 /// As [`decompress_message`](Decompressor::decompress_message), plus
457 /// `InvalidState` when `out` was changed between fragments.
458 ///
459 /// # Examples
460 ///
461 /// ```
462 /// use compress_lib::message::{Decompressor, MessageConfig};
463 ///
464 /// let payload = [0xF2, 0x48, 0xCD, 0xC9, 0xC9, 0x07, 0x00];
465 /// let mut rx = Decompressor::new(MessageConfig::new())?;
466 /// let mut out = Vec::new();
467 /// rx.decompress_fragment(&payload[..3], false, &mut out, 1024)?;
468 /// rx.decompress_fragment(&payload[3..], true, &mut out, 1024)?;
469 /// assert_eq!(out, b"Hello");
470 /// # Ok::<(), compress_lib::Error>(())
471 /// ```
472 pub fn decompress_fragment(
473 &mut self,
474 fragment: &[u8],
475 last: bool,
476 out: &mut Vec<u8>,
477 max: usize,
478 ) -> Result<usize, Error> {
479 let result = self.fragment(fragment, last, out, max);
480 if result.is_err() {
481 self.open = None;
482 self.inflater.reset();
483 }
484 result
485 }
486
487 fn fragment(
488 &mut self,
489 fragment: &[u8],
490 last: bool,
491 out: &mut Vec<u8>,
492 max: usize,
493 ) -> Result<usize, Error> {
494 let open = match self.open {
495 Some(m) => {
496 if out.len() != m.start + m.produced {
497 return Err(Error::config(ErrorKind::InvalidState));
498 }
499 m
500 }
501 None => OpenMessage {
502 start: out.len(),
503 produced: 0,
504 max,
505 },
506 };
507 let before = out.len();
508 let base = self.inflater.total_out() - open.produced as u64;
509 self.inflater
510 .set_max_output(base.saturating_add(open.max as u64));
511
512 let p = self
513 .inflater
514 .decompress_into(fragment, out, open.start, false)?;
515 // After a final block only zero padding may follow (RFC 7692's own
516 // example in section 7.2.3.4 ends with one such byte); anything else
517 // is refused.
518 if self.inflater.is_done() && fragment[p.consumed..].iter().any(|&b| b != 0) {
519 return Err(Error::new(
520 ErrorKind::TrailingData,
521 self.inflater.total_in(),
522 ));
523 }
524 if last && !self.inflater.is_done() {
525 let t = self
526 .inflater
527 .decompress_into(&TAIL, out, open.start, false)?;
528 // The tail is the empty stored block a sync flush ends with, so
529 // after it the message must sit on a block boundary (RFC 7692
530 // section 7.2.2). A block cut short (a stored block missing
531 // bytes, a Huffman block without its end code) would otherwise
532 // pass, and with context takeover corrupt the next message.
533 if t.consumed != TAIL.len() || !self.inflater.at_block_boundary() {
534 return Err(Error::new(
535 ErrorKind::UnexpectedEnd,
536 self.inflater.total_in(),
537 ));
538 }
539 }
540 let produced = open.produced + (out.len() - before);
541 if !last {
542 self.open = Some(OpenMessage { produced, ..open });
543 return Ok(out.len() - before);
544 }
545 self.open = None;
546 if self.takeover {
547 self.inflater.remember(&out[open.start..]);
548 if self.inflater.is_done() {
549 self.inflater.next_stream()?;
550 }
551 } else {
552 self.inflater.reset();
553 }
554 Ok(out.len() - before)
555 }
556
557 /// Zeroes the decompressor's window and decode tables and forgets any
558 /// open message, keeping the allocations; see [`Inflater::wipe`]. The
559 /// message vectors are the caller's to wipe.
560 ///
561 /// # Examples
562 ///
563 /// ```
564 /// use compress_lib::message::{Decompressor, MessageConfig};
565 ///
566 /// let mut rx = Decompressor::new(MessageConfig::new().with_context_takeover(true))?;
567 /// rx.wipe();
568 /// # Ok::<(), compress_lib::Error>(())
569 /// ```
570 pub fn wipe(&mut self) {
571 self.open = None;
572 self.inflater.wipe();
573 }
574
575 /// Forgets the window and any open message.
576 ///
577 /// # Examples
578 ///
579 /// ```
580 /// use compress_lib::message::{Decompressor, MessageConfig};
581 ///
582 /// let mut rx = Decompressor::new(MessageConfig::new())?;
583 /// rx.reset();
584 /// # Ok::<(), compress_lib::Error>(())
585 /// ```
586 pub fn reset(&mut self) {
587 self.open = None;
588 self.inflater.reset();
589 }
590
591 /// Heap bytes held: the decode tables, plus the window with takeover.
592 ///
593 /// # Examples
594 ///
595 /// ```
596 /// use compress_lib::message::{Decompressor, MessageConfig};
597 ///
598 /// let lean = Decompressor::new(MessageConfig::new())?;
599 /// let takeover = Decompressor::new(MessageConfig::new().with_context_takeover(true))?;
600 /// assert!(lean.memory_in_use() + 32 * 1024 <= takeover.memory_in_use());
601 /// # Ok::<(), compress_lib::Error>(())
602 /// ```
603 #[must_use]
604 pub fn memory_in_use(&self) -> usize {
605 self.inflater.memory_in_use()
606 }
607}