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.8" }`, 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.8.0`, alpha: the consumer paths (WebSocket permessage-deflate, HTTP
86//! content codings, streamed responses) exercised end to end, and
87//! interoperation with C zlib checked both ways in CI. The beta series and
88//! the 1.0 audit follow; the complete planned API, each item with its
89//! version, is `docs/API.md` in the
90//! [repository](https://github.com/jamesgober/compress-lib).
91
92#![doc(html_root_url = "https://docs.rs/compress-lib")]
93#![cfg_attr(docsrs, feature(doc_cfg))]
94#![cfg_attr(not(feature = "std"), no_std)]
95#![forbid(unsafe_code)]
96#![deny(missing_docs)]
97#![cfg_attr(
98 not(test),
99 deny(
100 clippy::unwrap_used,
101 clippy::expect_used,
102 clippy::panic,
103 clippy::todo,
104 clippy::unimplemented,
105 clippy::unreachable,
106 clippy::dbg_macro
107 )
108)]
109
110extern crate alloc;
111
112/// Crate version string, populated by Cargo at build time.
113pub const VERSION: &str = env!("CARGO_PKG_VERSION");
114
115pub mod checksum;
116pub mod deflate;
117pub mod gzip;
118#[cfg(feature = "std")]
119#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
120pub mod io;
121pub mod message;
122pub mod zlib;
123
124mod error;
125mod format;
126mod inflate;
127mod level;
128mod limits;
129mod stream;
130
131pub use error::{Error, ErrorKind};
132pub use format::{Decoder, Encoder, Format, decompress, decompress_with};
133pub use level::Level;
134pub use limits::Limits;
135pub use stream::{Progress, Status};
136
137/// Overwrites every element of `v` with the default value, spare capacity
138/// included, keeping its length, and passes it through
139/// [`core::hint::black_box`] so the writes are not removed as dead stores.
140/// Without `unsafe` (volatile writes) this is a strong hint to the compiler,
141/// not a guarantee; the documentation of every `wipe` says so.
142pub(crate) fn wipe_vec<T: Copy + Default>(v: &mut alloc::vec::Vec<T>) {
143 let len = v.len();
144 v.resize(v.capacity(), T::default());
145 v.fill(T::default());
146 core::hint::black_box(&*v);
147 v.truncate(len);
148}
149
150#[cfg(test)]
151mod tests {
152 use super::VERSION;
153
154 #[test]
155 fn test_version_matches_manifest() {
156 assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
157 let core = VERSION.split(['-', '+']).next().unwrap_or_default();
158 assert_eq!(core.split('.').count(), 3);
159 assert!(core.split('.').all(|part| part.parse::<u64>().is_ok()));
160 }
161}