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