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