Skip to main content

oxiarc_snappy/
lib.rs

1//! Pure Rust Snappy compression implementation.
2//!
3//! This crate provides both the raw Snappy block format and the Snappy
4//! framed (streaming) format with CRC32C checksums.
5//!
6//! # Block Format
7//!
8//! The block format provides simple compress/decompress functions for
9//! in-memory data. This is the core Snappy algorithm.
10//!
11//! ```
12//! use oxiarc_snappy::{compress, decompress};
13//!
14//! let data = b"Hello, World! Hello, World!";
15//! let compressed = compress(data);
16//! let decompressed = decompress(&compressed).unwrap();
17//! assert_eq!(decompressed, data);
18//! ```
19//!
20//! # Framed Format (Streaming)
21//!
22//! The framed format provides streaming compression/decompression using
23//! `std::io::Write` and `std::io::Read` traits. Data is split into
24//! chunks of up to 64 KiB, each with a CRC32C checksum.
25//!
26//! ```
27//! use oxiarc_snappy::{FrameEncoder, FrameDecoder};
28//! use std::io::{Write, Read};
29//!
30//! // Compress
31//! let mut compressed = Vec::new();
32//! {
33//!     let mut encoder = FrameEncoder::new(&mut compressed);
34//!     encoder.write_all(b"Hello, streaming Snappy!").unwrap();
35//!     encoder.finish().unwrap();
36//! }
37//!
38//! // Decompress
39//! let mut decoder = FrameDecoder::new(&compressed[..]);
40//! let mut output = Vec::new();
41//! decoder.read_to_end(&mut output).unwrap();
42//! assert_eq!(output, b"Hello, streaming Snappy!");
43//! ```
44
45#[cfg(feature = "async-io")]
46pub mod async_snappy;
47pub mod compress;
48pub mod crc32c;
49pub mod decompress;
50pub mod error;
51pub mod frame;
52#[cfg(feature = "parallel")]
53pub mod frame_parallel;
54pub mod pool;
55
56// Re-export the main public API
57
58#[cfg(feature = "async-io")]
59pub use async_snappy::{
60    AsyncSnappyCompressor, AsyncSnappyDecompressor, compress_frame_async, decompress_frame_async,
61};
62pub use compress::compress;
63pub use compress::compress_block_with_dict;
64pub use compress::max_compress_len;
65pub use decompress::decompress;
66pub use decompress::decompress_block_with_dict;
67pub use decompress::get_decompress_len as decompress_len;
68pub use error::SnappyError;
69pub use frame::FrameDecoder;
70pub use frame::FrameEncoder;
71pub use frame::compress_frame_pooled;
72pub use frame::compress_frame_with_dict;
73pub use frame::decompress_frame_with_dict;
74#[cfg(feature = "parallel")]
75pub use frame_parallel::compress_parallel;
76pub use pool::PoolStats;
77pub use pool::SnappyPool;
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use std::io::{Read, Write};
83
84    #[test]
85    fn test_block_roundtrip_empty() {
86        let data: &[u8] = b"";
87        let compressed = compress(data);
88        let decompressed = decompress(&compressed).expect("should decompress");
89        assert_eq!(decompressed, data);
90    }
91
92    #[test]
93    fn test_block_roundtrip_hello() {
94        let data = b"Hello, World!";
95        let compressed = compress(data);
96        let decompressed = decompress(&compressed).expect("should decompress");
97        assert_eq!(decompressed, data);
98    }
99
100    #[test]
101    fn test_block_roundtrip_repeated() {
102        let data = vec![b'A'; 10_000];
103        let compressed = compress(&data);
104        // Repeated data should compress significantly
105        assert!(
106            compressed.len() < data.len() / 2,
107            "compressed {} vs original {}",
108            compressed.len(),
109            data.len()
110        );
111        let decompressed = decompress(&compressed).expect("should decompress");
112        assert_eq!(decompressed, data);
113    }
114
115    #[test]
116    fn test_block_roundtrip_pattern() {
117        let data = b"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
118        let compressed = compress(data);
119        let decompressed = decompress(&compressed).expect("should decompress");
120        assert_eq!(decompressed, data.as_slice());
121    }
122
123    #[test]
124    fn test_block_roundtrip_binary() {
125        let data: Vec<u8> = (0..=255).cycle().take(4096).collect();
126        let compressed = compress(&data);
127        let decompressed = decompress(&compressed).expect("should decompress");
128        assert_eq!(decompressed, data);
129    }
130
131    #[test]
132    fn test_block_roundtrip_single_byte() {
133        let data = [0x42];
134        let compressed = compress(&data);
135        let decompressed = decompress(&compressed).expect("should decompress");
136        assert_eq!(decompressed, data);
137    }
138
139    #[test]
140    fn test_block_roundtrip_two_bytes() {
141        let data = [0x42, 0x43];
142        let compressed = compress(&data);
143        let decompressed = decompress(&compressed).expect("should decompress");
144        assert_eq!(decompressed, data);
145    }
146
147    #[test]
148    fn test_block_roundtrip_three_bytes() {
149        let data = [0x42, 0x43, 0x44];
150        let compressed = compress(&data);
151        let decompressed = decompress(&compressed).expect("should decompress");
152        assert_eq!(decompressed, data);
153    }
154
155    #[test]
156    fn test_decompress_len() {
157        let data = vec![0u8; 12345];
158        let compressed = compress(&data);
159        let len = decompress_len(&compressed).expect("should decode length");
160        assert_eq!(len, 12345);
161    }
162
163    #[test]
164    fn test_max_compress_len_bounds() {
165        for size in [0, 1, 100, 1000, 65536, 1_000_000] {
166            let max_len = max_compress_len(size);
167            let data = vec![0xFFu8; size];
168            let compressed = compress(&data);
169            assert!(
170                compressed.len() <= max_len,
171                "compressed {} > max {} for input size {}",
172                compressed.len(),
173                max_len,
174                size
175            );
176        }
177    }
178
179    #[test]
180    fn test_decompress_invalid_data() {
181        // Random garbage should fail
182        let garbage = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
183        let result = decompress(&garbage);
184        assert!(result.is_err());
185    }
186
187    #[test]
188    fn test_frame_roundtrip_various_sizes() {
189        for size in [0, 1, 10, 100, 1000, 65535, 65536, 65537, 100_000] {
190            let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
191
192            let mut compressed = Vec::new();
193            {
194                let mut encoder = FrameEncoder::new(&mut compressed);
195                encoder
196                    .write_all(&data)
197                    .unwrap_or_else(|e| panic!("write failed for size {size}: {e}"));
198                encoder
199                    .finish()
200                    .unwrap_or_else(|e| panic!("finish failed for size {size}: {e}"));
201            }
202
203            let mut decoder = FrameDecoder::new(&compressed[..]);
204            let mut output = Vec::new();
205            decoder
206                .read_to_end(&mut output)
207                .unwrap_or_else(|e| panic!("read failed for size {size}: {e}"));
208
209            assert_eq!(output, data, "roundtrip mismatch for size {size}");
210        }
211    }
212
213    #[test]
214    fn test_frame_multi_write_roundtrip() {
215        let mut compressed = Vec::new();
216        {
217            let mut encoder = FrameEncoder::new(&mut compressed);
218            encoder
219                .write_all(b"Part 1. ")
220                .expect("write should succeed");
221            encoder
222                .write_all(b"Part 2. ")
223                .expect("write should succeed");
224            encoder.write_all(b"Part 3.").expect("write should succeed");
225            encoder.finish().expect("finish should succeed");
226        }
227
228        let mut decoder = FrameDecoder::new(&compressed[..]);
229        let mut output = String::new();
230        decoder
231            .read_to_string(&mut output)
232            .expect("read should succeed");
233
234        assert_eq!(output, "Part 1. Part 2. Part 3.");
235    }
236
237    #[test]
238    fn test_block_roundtrip_all_same_byte() {
239        // Run-length-like data
240        for byte_val in [0x00, 0x55, 0xAA, 0xFF] {
241            let data = vec![byte_val; 50_000];
242            let compressed = compress(&data);
243            let decompressed = decompress(&compressed).expect("should decompress");
244            assert_eq!(decompressed, data);
245        }
246    }
247
248    #[test]
249    fn test_block_roundtrip_lorem_ipsum() {
250        let data = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
251            Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
252            Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris \
253            nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in \
254            reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \
255            pariatur. Excepteur sint occaecat cupidatat non proident, sunt in \
256            culpa qui officia deserunt mollit anim id est laborum.";
257        let compressed = compress(data);
258        // Text should compress somewhat
259        assert!(compressed.len() < data.len());
260        let decompressed = decompress(&compressed).expect("should decompress");
261        assert_eq!(decompressed, data.as_slice());
262    }
263}