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}