Skip to main content

compress_lib/
lib.rs

1//! # compress_lib
2//!
3//! DEFLATE (RFC 1951), zlib (RFC 1950), and gzip (RFC 1952) for Rust
4//! services, with the per-message rules of WebSocket permessage-deflate
5//! (RFC 7692).
6//!
7//! The engine is sans-IO: the caller pushes input and pulls output through
8//! buffers it owns. Memory is fixed when an engine is built and never grows
9//! with input, and every decoder enforces an absolute output cap while it
10//! decodes, so a decompression bomb costs at most the cap. There is no
11//! `unsafe` code and no third-party runtime dependency.
12//!
13//! ## Tier 1
14//!
15//! Add it with Cargo's rename, `compress = { package = "compress-lib", version
16//! = "0.7" }`, and code reads `compress::`. One call each way; decoding is
17//! capped at 64 MiB of output by default:
18//!
19//! ```
20//! use compress_lib as compress;
21//!
22//! let packed = compress::gzip::compress(b"hello, hello, hello");
23//! let body = compress::gzip::decompress(&packed)?;
24//! assert_eq!(body, b"hello, hello, hello");
25//!
26//! let z = compress::zlib::compress(b"zlib, which HTTP calls deflate");
27//! let raw = compress::deflate::compress_with(b"raw DEFLATE", compress::Level::FAST);
28//! assert_eq!(compress::decompress(&z)?, b"zlib, which HTTP calls deflate"); // gzip or zlib, detected
29//!
30//! let small = compress::Limits::new().with_max_output(1 << 20);
31//! assert_eq!(compress::deflate::decompress_with(&raw, small)?, b"raw DEFLATE");
32//! # Ok::<(), compress_lib::Error>(())
33//! ```
34//!
35//! ## WebSocket permessage-deflate
36//!
37//! [`message`] has the RFC 7692 rules: the sync-flush tail removed and put
38//! back, context takeover on or off, window bits 8 to 15, and the output
39//! budget checked while inflating.
40//!
41//! ```
42//! use compress_lib::message::{Compressor, Decompressor, MessageConfig};
43//!
44//! let config = MessageConfig::new().with_window_bits(10);
45//! let mut tx = Compressor::new(config)?;
46//! let mut rx = Decompressor::new(config)?;
47//! let mut wire = Vec::new();
48//! tx.compress_message(b"{\"op\":\"ping\"}", &mut wire)?;
49//! let mut text = Vec::new();
50//! rx.decompress_message(&wire, &mut text, 1 << 20)?;
51//! assert_eq!(text, b"{\"op\":\"ping\"}");
52//! # Ok::<(), compress_lib::Error>(())
53//! ```
54//!
55//! ## Streaming
56//!
57//! [`deflate::Inflater`] and [`deflate::Deflater`] take buffers you own and
58//! say where they stopped ([`Status`]); memory is fixed when they are built,
59//! and every decoder limit is checked before output is written.
60//!
61//! ```
62//! use compress_lib::deflate::{Deflater, Flush, Inflater, Status};
63//!
64//! let mut deflater = Deflater::default();
65//! let mut packed = [0u8; 256];
66//! let p = deflater.compress(b"streamed", &mut packed, Flush::Finish)?;
67//! assert_eq!(p.status, Status::Done);
68//!
69//! let mut inflater = Inflater::new().with_window_bits(12)?;
70//! let mut out = [0u8; 64];
71//! let q = inflater.finish(&packed[..p.produced], &mut out)?;
72//! assert_eq!(&out[..q.produced], b"streamed");
73//! # Ok::<(), compress_lib::Error>(())
74//! ```
75//!
76//! [`Decoder`] and [`Encoder`] take a [`Format`] chosen at run time (an HTTP
77//! layer picking by `Content-Encoding`); with the `std` feature, [`io`] wraps
78//! any of them around `Read` and `Write`.
79//!
80//! Every failure is an [`Error`] with a matchable [`ErrorKind`] and a stable
81//! code for logs and metrics. [`checksum`] has CRC-32 and Adler-32.
82//!
83//! ## Status
84//!
85//! `0.7.0`, hardened: an attack test per threat, a 4-hour soak, and
86//! `docs/SECURITY.md` (the threat model, each defence with its cost) in the
87//! [repository](https://github.com/jamesgober/compress-lib). Consumer runs
88//! and the 1.0 audit follow; the complete planned API, each item with its
89//! version, is `docs/API.md` there.
90
91#![doc(html_root_url = "https://docs.rs/compress-lib")]
92#![cfg_attr(docsrs, feature(doc_cfg))]
93#![cfg_attr(not(feature = "std"), no_std)]
94#![forbid(unsafe_code)]
95#![deny(missing_docs)]
96#![cfg_attr(
97    not(test),
98    deny(
99        clippy::unwrap_used,
100        clippy::expect_used,
101        clippy::panic,
102        clippy::todo,
103        clippy::unimplemented,
104        clippy::unreachable,
105        clippy::dbg_macro
106    )
107)]
108
109extern crate alloc;
110
111/// Crate version string, populated by Cargo at build time.
112pub const VERSION: &str = env!("CARGO_PKG_VERSION");
113
114pub mod checksum;
115pub mod deflate;
116pub mod gzip;
117#[cfg(feature = "std")]
118#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
119pub mod io;
120pub mod message;
121pub mod zlib;
122
123mod error;
124mod format;
125mod inflate;
126mod level;
127mod limits;
128mod stream;
129
130pub use error::{Error, ErrorKind};
131pub use format::{Decoder, Encoder, Format, decompress, decompress_with};
132pub use level::Level;
133pub use limits::Limits;
134pub use stream::{Progress, Status};
135
136/// Overwrites every element of `v` with the default value, spare capacity
137/// included, keeping its length, and passes it through
138/// [`core::hint::black_box`] so the writes are not removed as dead stores.
139/// Without `unsafe` (volatile writes) this is a strong hint to the compiler,
140/// not a guarantee; the documentation of every `wipe` says so.
141pub(crate) fn wipe_vec<T: Copy + Default>(v: &mut alloc::vec::Vec<T>) {
142    let len = v.len();
143    v.resize(v.capacity(), T::default());
144    v.fill(T::default());
145    core::hint::black_box(&*v);
146    v.truncate(len);
147}
148
149#[cfg(test)]
150mod tests {
151    use super::VERSION;
152
153    #[test]
154    fn test_version_matches_manifest() {
155        assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
156        let core = VERSION.split(['-', '+']).next().unwrap_or_default();
157        assert_eq!(core.split('.').count(), 3);
158        assert!(core.split('.').all(|part| part.parse::<u64>().is_ok()));
159    }
160}