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}