Skip to main content

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}