noflate/lib.rs
1#![no_std]
2#![warn(missing_docs)]
3#![forbid(unsafe_code)]
4
5//! A zero-dependency DEFLATE (RFC 1951), gzip (RFC 1952), and zlib
6//! (RFC 1950) encoder and decoder.
7//!
8//! - `no_std` (requires only `alloc`)
9//! - No `unsafe` code
10//! - Sans-io: callers drive encoding and decoding via `feed` / `output` /
11//! `advance`, with no I/O performed by the library itself
12//! - WebSocket `permessage-deflate` (RFC 7692) support via
13//! [`deflate::Encoder::sync_flush`] and [`deflate::Encoder::reset_history`]
14//!
15//! # Examples
16//!
17//! One-shot compression and decompression:
18//!
19//! ```
20//! # fn main() -> noflate::Result<()> {
21//! let input = b"Hello, DEFLATE!";
22//! let compressed = noflate::deflate::compress(input)?;
23//! let decompressed = noflate::deflate::decompress(&compressed)?;
24//! assert_eq!(decompressed, input);
25//! # Ok(())
26//! # }
27//! ```
28//!
29//! Streaming encoder:
30//!
31//! ```
32//! # fn main() -> noflate::Result<()> {
33//! let mut encoder = noflate::deflate::Encoder::new();
34//! encoder.feed(b"Hello, ")?;
35//! encoder.feed(b"world!")?;
36//! encoder.finish()?;
37//! let compressed = encoder.output().to_vec();
38//! encoder.advance(compressed.len());
39//! assert_eq!(
40//! noflate::deflate::decompress(&compressed)?,
41//! b"Hello, world!",
42//! );
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! Streaming decoder:
48//!
49//! ```
50//! # fn main() -> noflate::Result<()> {
51//! # let compressed = noflate::deflate::compress(b"hello")?;
52//! let mut decoder = noflate::deflate::Decoder::new();
53//! decoder.feed(&compressed)?;
54//! let out = decoder.output().to_vec();
55//! decoder.advance(out.len());
56//! assert!(decoder.is_finished());
57//! assert_eq!(out, b"hello");
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! The [`gzip`] and [`zlib`] modules provide the same API shape for their
63//! respective container formats.
64//!
65//! The crate performs no I/O itself, but the sans-io API plugs into
66//! `std::io::Write` and `std::io::Read` with a small adapter: drain
67//! [`deflate::Encoder::output`] into any `Write` sink, and top up
68//! [`deflate::Decoder::feed`] from any `Read` source. See
69//! `examples/io_bridge.rs` in the repository for a runnable
70//! `DeflateWriter` / `DeflateReader` pair; the same pattern works
71//! verbatim for the [`gzip`] and [`zlib`] streaming types.
72//!
73//! [`Format::detect`] identifies the format of a compressed stream:
74//!
75//! ```
76//! # fn main() -> noflate::Result<()> {
77//! let data = noflate::gzip::compress(b"hello")?;
78//! assert_eq!(noflate::Format::detect(&data), Some(noflate::Format::Gzip));
79//! # Ok(())
80//! # }
81//! ```
82
83extern crate alloc;
84
85#[cfg(test)]
86extern crate std;
87
88mod adler32;
89mod bit;
90mod buf;
91mod crc32;
92mod decode;
93pub mod deflate;
94mod encode;
95mod error;
96pub mod gzip;
97mod huffman;
98mod lz77;
99mod symbol;
100pub mod zlib;
101
102pub use error::{Error, Result};
103
104/// The detected compression format of a byte stream.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub enum Format {
107 /// Raw DEFLATE (RFC 1951).
108 Deflate,
109 /// ZLIB container (RFC 1950).
110 Zlib,
111 /// GZIP container (RFC 1952).
112 Gzip,
113}
114
115impl Format {
116 /// Detect the compression format from the first bytes of `data`.
117 ///
118 /// Returns `None` if `data` is shorter than 2 bytes.
119 ///
120 /// Detection order:
121 /// 1. **Gzip** — magic bytes `0x1F 0x8B`.
122 /// 2. **Zlib** — CM=8, CINFO≤7, and the FCHECK checksum passes.
123 /// 3. **Deflate** — fallback for anything else.
124 pub fn detect(data: &[u8]) -> Option<Format> {
125 if data.len() < 2 {
126 return None;
127 }
128
129 if data[0] == 0x1F && data[1] == 0x8B {
130 return Some(Format::Gzip);
131 }
132
133 let cmf = data[0];
134 let flg = data[1];
135 if (cmf & 0x0F) == 8 && (cmf >> 4) <= 7 && (u16::from(cmf) * 256 + u16::from(flg)) % 31 == 0
136 {
137 return Some(Format::Zlib);
138 }
139
140 Some(Format::Deflate)
141 }
142}