libdeflater/lib.rs
1//! Rust bindings to [`libdeflate`], a DEFLATE-based buffer
2//! compression/decompression library that works with raw DEFLATE,
3//! zlib, and gzip data.
4//!
5//! **Warning**: Libdeflate is targeted at *specialized*
6//! performance-sensitive use-cases where developers have a good
7//! understanding of their input/output data. Developers looking for a
8//! general-purpose DEFLATE library should use something like
9//! [`flate2`], which can handle a much wider range of inputs (network
10//! streams, large files, etc.).
11//!
12//! [`libdeflate`]: https://github.com/ebiggers/libdeflate
13//! [`flate2`]: https://github.com/alexcrichton/flate2-rs
14//!
15//! # Decompression
16//!
17//! [`Decompressor::new`] can be used to construct a [`Decompressor`],
18//! which can decompress:
19//!
20//! - DEFLATE data ([`deflate_decompress`])
21//! - zlib data ([`zlib_decompress`])
22//! - gzip data ([`gzip_decompress`])
23//!
24//! **Note**: `libdeflate` requires that the input *and* output
25//! buffers are pre-allocated before decompressing. Because of this,
26//! you will at least need to know the upper bound on how large the
27//! compressed data will decompress to; otherwise, a `decompress_*`
28//! function call will return `DecompressionError::InsufficientSpace`
29//!
30//! [`Decompressor::new`]: struct.Decompressor.html#method.new
31//! [`Decompressor`]: struct.Decompressor.html
32//! [`deflate_decompress`]: struct.Decompressor.html#method.deflate_decompress
33//! [`zlib_decompress`]: struct.Decompressor.html#method.zlib_decompress
34//! [`gzip_decompress`]: struct.Decompressor.html#method.gzip_decompress
35//! [`DecompressionError::InsufficientSpace`]: enum.DecompressionError.html
36//!
37//! # Compression
38//!
39//! `Compressor::new` can be used to construct a [`Compressor`], which
40//! can compress data into the following formats:
41//!
42//! - DEFLATE ([`deflate_compress`])
43//! - zlib ([`zlib_compress`])
44//! - gzip ([`gzip_compress`])
45//!
46//! Because buffers must be allocated up-front, developers need to
47//! supply these functions with output buffers that are big enough to
48//! fit the compressed data. The maximum size of the compressed data
49//! can be found with the associated `*_bound` methods:
50//!
51//! - [`deflate_compress_bound`]
52//! - [`zlib_compress_bound`]
53//! - [`gzip_compress_bound`]
54//!
55//! [`Compressor::new`]: struct.Compressor.html#method.new
56//! [`Compressor`]: struct.Compressor.html
57//! [`deflate_compress`]: struct.Compressor.html#method.deflate_compress
58//! [`zlib_compress`]: struct.Compressor.html#method.zlib_compress
59//! [`gzip_compress`]: struct.Compressor.html#method.gzip_compress
60//! [`deflate_compress_bound`]: struct.Compressor.html#method.deflate_compress_bound
61//! [`zlib_compress_bound`]: struct.Compressor.html#method.zlib_compress_bound
62//! [`gzip_compress_bound`]: struct.Compressor.html#method.gzip_compress_bound
63
64use std::error::Error;
65use std::fmt;
66use std::ptr::NonNull;
67use libdeflate_sys::{libdeflate_decompressor,
68 libdeflate_free_decompressor,
69 libdeflate_gzip_decompress,
70 libdeflate_zlib_decompress,
71 libdeflate_deflate_decompress,
72 libdeflate_result,
73 libdeflate_result_LIBDEFLATE_SUCCESS,
74 libdeflate_result_LIBDEFLATE_BAD_DATA,
75 libdeflate_result_LIBDEFLATE_INSUFFICIENT_SPACE,
76 libdeflate_compressor,
77 libdeflate_deflate_compress_bound,
78 libdeflate_deflate_compress,
79 libdeflate_zlib_compress_bound,
80 libdeflate_zlib_compress,
81 libdeflate_gzip_compress_bound,
82 libdeflate_gzip_compress,
83 libdeflate_free_compressor,
84 libdeflate_crc32,
85 libdeflate_adler32};
86
87#[cfg(feature = "use_rust_alloc")]
88mod malloc_wrapper;
89
90unsafe fn alloc_compressor(compression_level: std::os::raw::c_int) -> *mut libdeflate_compressor {
91 #[cfg(feature = "use_rust_alloc")]
92 { libdeflate_sys::libdeflate_alloc_compressor_ex(compression_level, &malloc_wrapper::OPTIONS) }
93 #[cfg(not(feature = "use_rust_alloc"))]
94 { libdeflate_sys::libdeflate_alloc_compressor(compression_level) }
95}
96
97unsafe fn alloc_decompressor() -> *mut libdeflate_decompressor {
98 #[cfg(feature = "use_rust_alloc")]
99 { libdeflate_sys::libdeflate_alloc_decompressor_ex(&malloc_wrapper::OPTIONS) }
100 #[cfg(not(feature = "use_rust_alloc"))]
101 { libdeflate_sys::libdeflate_alloc_decompressor() }
102}
103
104/// A `libdeflate` decompressor that can inflate DEFLATE, zlib, or
105/// gzip data.
106pub struct Decompressor {
107 p: NonNull<libdeflate_decompressor>,
108}
109unsafe impl Send for Decompressor {}
110
111/// An error that may be returned by one of the
112/// [`Decompressor`](struct.Decompressor.html)'s `decompress_*`
113/// methods when a decompression cannot be performed.
114#[derive(Debug, PartialEq)]
115pub enum DecompressionError {
116 /// The provided data is invalid in some way. For example, the
117 /// checksum in the data revealed possible corruption, magic
118 /// numbers in the data do not match expectations, etc.
119 BadData,
120
121 /// The provided output buffer is not large enough to accomodate
122 /// the decompressed data.
123 InsufficientSpace,
124}
125
126impl fmt::Display for DecompressionError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match &self {
129 DecompressionError::BadData => write!(f, "the data provided to a libdeflater *_decompress function call was invalid in some way (e.g. bad magic numbers, bad checksum)"),
130 DecompressionError::InsufficientSpace => write!(f, "a buffer provided to a libdeflater *_decompress function call was too small to accommodate the decompressed data")
131 }
132 }
133}
134
135impl Error for DecompressionError {}
136
137/// A result returned by decompression methods
138type DecompressionResult<T> = std::result::Result<T, DecompressionError>;
139
140impl Default for Decompressor {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[allow(non_upper_case_globals)]
147impl Decompressor {
148
149 /// Returns a newly constructed instance of a `Decompressor`.
150 pub fn new() -> Decompressor {
151 unsafe {
152 let ptr = alloc_decompressor();
153 if let Some(ptr) = NonNull::new(ptr) {
154 Decompressor{ p: ptr }
155 } else {
156 panic!("libdeflate_alloc_decompressor returned NULL: out of memory");
157 }
158 }
159 }
160
161 /// Decompresses `gz_data` (a buffer containing
162 /// [`gzip`](https://tools.ietf.org/html/rfc1952) data) and writes
163 /// the decompressed data into `out`. Returns the number of
164 /// decompressed bytes written into `out`, or an error (see
165 /// [`DecompressionError`](enum.DecompressionError.html) for error
166 /// cases).
167 pub fn gzip_decompress(&mut self,
168 gz_data: &[u8],
169 out: &mut [u8]) -> DecompressionResult<usize> {
170 unsafe {
171 let mut out_nbytes = 0;
172 let in_ptr = gz_data.as_ptr() as *const std::ffi::c_void;
173 let out_ptr = out.as_mut_ptr() as *mut std::ffi::c_void;
174 let ret: libdeflate_result =
175 libdeflate_gzip_decompress(self.p.as_ptr(),
176 in_ptr,
177 gz_data.len(),
178 out_ptr,
179 out.len(),
180 &mut out_nbytes);
181 match ret {
182 libdeflate_result_LIBDEFLATE_SUCCESS => {
183 Ok(out_nbytes)
184 },
185 libdeflate_result_LIBDEFLATE_BAD_DATA => {
186 Err(DecompressionError::BadData)
187 },
188 libdeflate_result_LIBDEFLATE_INSUFFICIENT_SPACE => {
189 Err(DecompressionError::InsufficientSpace)
190 },
191 _ => {
192 panic!("libdeflate_gzip_decompress returned an unknown error type: this is an internal bug that **must** be fixed");
193 }
194 }
195 }
196 }
197
198 /// Decompresses `zlib_data` (a buffer containing
199 /// [`zlib`](https://www.ietf.org/rfc/rfc1950.txt) data) and
200 /// writes the decompressed data to `out`. Returns the number of
201 /// decompressed bytes written into `out`, or an error (see
202 /// [`DecompressionError`](enum.DecompressionError.html) for error
203 /// cases).
204 pub fn zlib_decompress(&mut self,
205 zlib_data: &[u8],
206 out: &mut [u8]) -> DecompressionResult<usize> {
207 unsafe {
208 let mut out_nbytes = 0;
209 let in_ptr = zlib_data.as_ptr() as *const std::ffi::c_void;
210 let out_ptr = out.as_mut_ptr() as *mut std::ffi::c_void;
211 let ret: libdeflate_result =
212 libdeflate_zlib_decompress(self.p.as_ptr(),
213 in_ptr,
214 zlib_data.len(),
215 out_ptr,
216 out.len(),
217 &mut out_nbytes);
218
219 match ret {
220 libdeflate_result_LIBDEFLATE_SUCCESS => {
221 Ok(out_nbytes)
222 },
223 libdeflate_result_LIBDEFLATE_BAD_DATA => {
224 Err(DecompressionError::BadData)
225 },
226 libdeflate_result_LIBDEFLATE_INSUFFICIENT_SPACE => {
227 Err(DecompressionError::InsufficientSpace)
228 },
229 _ => {
230 panic!("libdeflate_zlib_decompress returned an unknown error type: this is an internal bug that **must** be fixed");
231 }
232 }
233 }
234 }
235
236 /// Decompresses `deflate_data` (a buffer containing
237 /// [`deflate`](https://tools.ietf.org/html/rfc1951) data) and
238 /// writes the decompressed data to `out`. Returns the number of
239 /// decompressed bytes written into `out`, or an error (see
240 /// [`DecompressionError`](enum.DecompressionError.html) for error
241 /// cases).
242 pub fn deflate_decompress(&mut self,
243 deflate_data: &[u8],
244 out: &mut [u8]) -> DecompressionResult<usize> {
245 unsafe {
246 let mut out_nbytes = 0;
247 let in_ptr = deflate_data.as_ptr() as *const std::ffi::c_void;
248 let out_ptr = out.as_mut_ptr() as *mut std::ffi::c_void;
249 let ret: libdeflate_result =
250 libdeflate_deflate_decompress(self.p.as_ptr(),
251 in_ptr,
252 deflate_data.len(),
253 out_ptr,
254 out.len(),
255 &mut out_nbytes);
256
257 match ret {
258 libdeflate_result_LIBDEFLATE_SUCCESS => {
259 Ok(out_nbytes)
260 },
261 libdeflate_result_LIBDEFLATE_BAD_DATA => {
262 Err(DecompressionError::BadData)
263 },
264 libdeflate_result_LIBDEFLATE_INSUFFICIENT_SPACE => {
265 Err(DecompressionError::InsufficientSpace)
266 },
267 _ => {
268 panic!("libdeflate_deflate_decompress returned an unknown error type: this is an internal bug that **must** be fixed");
269 }
270 }
271 }
272 }
273}
274
275impl Drop for Decompressor {
276 fn drop(&mut self) {
277 unsafe {
278 libdeflate_free_decompressor(self.p.as_ptr());
279 }
280 }
281}
282
283/// Raw numeric values of compression levels that are accepted by libdeflate
284const MIN_COMPRESSION_LVL: i32 = 0;
285const DEFAULT_COMPRESSION_LVL : i32 = 6;
286const MAX_COMPRESSION_LVL: i32 = 12;
287
288/// Compression level used by a [`Compressor`](struct.Compressor.html)
289/// instance.
290#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
291pub struct CompressionLvl(i32);
292
293/// Errors that can be returned when attempting to create a
294/// [`CompressionLvl`](enum.CompressionLvl.html) from a numeric value.
295#[derive(Copy, Clone, PartialEq, Eq, Debug)]
296pub enum CompressionLvlError {
297 InvalidValue,
298}
299
300/// A result that is returned when trying to create a
301/// [`CompressionLvl`](enum.CompressionLvl.html) from a numeric value.
302type CompressionLevelResult = Result<CompressionLvl, CompressionLvlError>;
303
304impl CompressionLvl {
305 /// Try to create a valid
306 /// [`CompressionLvl`](enum.CompressionLvl.html) from a numeric
307 /// value.
308 ///
309 /// If `level` is a valid custom compression level for libdeflate,
310 /// returns a `Result::Ok(CompressionLvl)`. Otherwise, returns
311 /// `Result::Error(error)`.
312 ///
313 /// Valid compression levels for libdeflate, at time of writing,
314 /// are 1-12.
315 pub const fn new(level: i32) -> CompressionLevelResult {
316 if MIN_COMPRESSION_LVL <= level && level <= MAX_COMPRESSION_LVL {
317 Ok(CompressionLvl(level))
318 } else {
319 Err(CompressionLvlError::InvalidValue)
320 }
321 }
322
323 /// Returns the fastest compression level. This compression level
324 /// offers the highest performance but lowest compression ratio.
325 pub const fn fastest() -> CompressionLvl {
326 CompressionLvl(MIN_COMPRESSION_LVL)
327 }
328
329 /// Returns the best compression level, in terms of compression
330 /// ratio. This compression level offers the best compression
331 /// ratio but lowest performance.
332 pub const fn best() -> CompressionLvl {
333 CompressionLvl(MAX_COMPRESSION_LVL)
334 }
335
336 /// Returns an iterator that emits all compression levels
337 /// supported by `libdeflate` in ascending order.
338 pub const fn iter() -> CompressionLvlIter {
339 CompressionLvlIter(MIN_COMPRESSION_LVL)
340 }
341}
342
343impl Default for CompressionLvl {
344 /// Returns the default compression level reccomended by
345 /// libdeflate.
346 fn default() -> CompressionLvl {
347 CompressionLvl(DEFAULT_COMPRESSION_LVL)
348 }
349}
350
351/// An iterator over the
352/// [`CompressionLvl`](struct.CompressionLvl.html)s supported by the
353/// [`Compressor`](struct.Compressor.html).
354pub struct CompressionLvlIter(i32);
355
356impl Iterator for CompressionLvlIter {
357 type Item = CompressionLvl;
358
359 fn next(&mut self) -> Option<Self::Item> {
360 if self.0 <= MAX_COMPRESSION_LVL {
361 let ret = Some(CompressionLvl(self.0));
362 self.0 += 1;
363 ret
364 } else {
365 None
366 }
367 }
368}
369
370impl From<CompressionLvl> for i32 {
371 fn from(level: CompressionLvl) -> Self {
372 level.0
373 }
374}
375
376impl From<&CompressionLvl> for i32 {
377 fn from(level: &CompressionLvl) -> Self {
378 level.0
379 }
380}
381
382/// An error that may be returned when calling one of the
383/// [`Compressor`](struct.Compressor.html)'s `compress_*` methods.
384#[derive(Debug, PartialEq)]
385pub enum CompressionError {
386 InsufficientSpace,
387}
388
389impl fmt::Display for CompressionError {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 match &self {
392 CompressionError::InsufficientSpace => write!(f, "the output buffer provided to a libdeflater *_compress function call was too small for the input data"),
393 }
394 }
395}
396
397impl Error for CompressionError {}
398
399type CompressionResult<T> = std::result::Result<T, CompressionError>;
400
401/// A `libdeflate` compressor that can compress arbitrary data into
402/// DEFLATE, zlib, or gzip formats.
403pub struct Compressor {
404 p: NonNull<libdeflate_compressor>,
405}
406unsafe impl Send for Compressor {}
407
408impl Default for Compressor {
409 fn default() -> Self {
410 Self::new(CompressionLvl::default())
411 }
412}
413
414impl Compressor {
415
416 /// Returns a newly constructed `Compressor` that compresses data
417 /// with the supplied
418 /// [`CompressionLvl`](struct.CompressionLvl.html)
419 pub fn new(lvl: CompressionLvl) -> Compressor {
420 unsafe {
421 let ptr = alloc_compressor(lvl.0 as _);
422 if let Some(ptr) = NonNull::new(ptr) {
423 Compressor{ p: ptr }
424 } else {
425 panic!("libdeflate_alloc_compressor returned NULL: out of memory");
426 }
427 }
428 }
429
430 /// Returns the maximum number of bytes required to encode
431 /// `n_bytes` as [`deflate`](https://tools.ietf.org/html/rfc1951)
432 /// data. This is a hard upper-bound that assumes the worst
433 /// possible compression ratio (i.e. assumes the data cannot be
434 /// compressed), format overhead, etc.
435 pub fn deflate_compress_bound(&mut self, n_bytes: usize) -> usize {
436 unsafe {
437 libdeflate_deflate_compress_bound(self.p.as_ptr(), n_bytes)
438 }
439 }
440
441 /// Compresses `in_raw_data` as
442 /// [`deflate`](https://tools.ietf.org/html/rfc1951) data, writing
443 /// the data into `out_deflate_data`. Returns the number of bytes
444 /// written into `out_deflate_data`.
445 pub fn deflate_compress(&mut self,
446 in_raw_data: &[u8],
447 out_deflate_data: &mut [u8]) -> CompressionResult<usize> {
448 unsafe {
449 let in_ptr = in_raw_data.as_ptr() as *const std::ffi::c_void;
450 let out_ptr = out_deflate_data.as_mut_ptr() as *mut std::ffi::c_void;
451
452 let sz = libdeflate_deflate_compress(self.p.as_ptr(),
453 in_ptr,
454 in_raw_data.len(),
455 out_ptr,
456 out_deflate_data.len());
457
458 if sz != 0 {
459 Ok(sz)
460 } else {
461 Err(CompressionError::InsufficientSpace)
462 }
463 }
464 }
465
466 /// Returns the maximum number of bytes required to encode
467 /// `n_bytes` as [`zlib`](https://www.ietf.org/rfc/rfc1950.txt)
468 /// data. This is a hard upper-bound that assumes the worst
469 /// possible compression ratio (i.e. assumes the data cannot be
470 /// compressed), format overhead, etc.
471 pub fn zlib_compress_bound(&mut self, n_bytes: usize) -> usize {
472 unsafe {
473 libdeflate_zlib_compress_bound(self.p.as_ptr(), n_bytes)
474 }
475 }
476
477 /// Compresses `in_raw_data` as
478 /// [`zlib`](https://www.ietf.org/rfc/rfc1950.txt) data, writing
479 /// the data into `out_zlib_data`. Returns the number of bytes
480 /// written into `out_zlib_data`.
481 pub fn zlib_compress(&mut self,
482 in_raw_data: &[u8],
483 out_zlib_data: &mut [u8]) -> CompressionResult<usize> {
484 unsafe {
485 let in_ptr = in_raw_data.as_ptr() as *const std::ffi::c_void;
486 let out_ptr = out_zlib_data.as_mut_ptr() as *mut std::ffi::c_void;
487
488 let sz = libdeflate_zlib_compress(self.p.as_ptr(),
489 in_ptr,
490 in_raw_data.len(),
491 out_ptr,
492 out_zlib_data.len());
493
494 if sz != 0 {
495 Ok(sz)
496 } else {
497 Err(CompressionError::InsufficientSpace)
498 }
499 }
500 }
501
502 /// Returns the maximum number of bytes required to encode
503 /// `n_bytes` as [`gzip`](https://tools.ietf.org/html/rfc1952)
504 /// data. This is a hard upper-bound that assumes the worst
505 /// possible compression ratio (i.e. assumes the data cannot be
506 /// compressed), format overhead, etc.
507 pub fn gzip_compress_bound(&mut self, n_bytes: usize) -> usize {
508 unsafe {
509 libdeflate_gzip_compress_bound(self.p.as_ptr(), n_bytes)
510 }
511 }
512
513 /// Compresses `in_raw_data` as
514 /// [`gzip`](https://tools.ietf.org/html/rfc1952) data, writing
515 /// the data into `out_gzip_data`. Returns the number of bytes
516 /// written into `out_gzip_data`.
517 pub fn gzip_compress(&mut self,
518 in_raw_data: &[u8],
519 out_gzip_data: &mut [u8]) -> CompressionResult<usize> {
520 unsafe {
521 let in_ptr = in_raw_data.as_ptr() as *const std::ffi::c_void;
522 let out_ptr = out_gzip_data.as_mut_ptr() as *mut std::ffi::c_void;
523
524 let sz = libdeflate_gzip_compress(self.p.as_ptr(),
525 in_ptr,
526 in_raw_data.len(),
527 out_ptr,
528 out_gzip_data.len());
529
530 if sz != 0 {
531 Ok(sz)
532 } else {
533 Err(CompressionError::InsufficientSpace)
534 }
535 }
536 }
537}
538
539impl Drop for Compressor {
540 fn drop(&mut self) {
541 unsafe {
542 libdeflate_free_compressor(self.p.as_ptr());
543 }
544 }
545}
546
547/// Struct holding the state required to compute a rolling crc32
548/// value.
549pub struct Crc {
550 val: u32,
551}
552
553impl Default for Crc {
554 fn default() -> Self {
555 Self::new()
556 }
557}
558
559impl Crc {
560 /// Returns a new `Crc` instance
561 pub const fn new() -> Crc {
562 Crc { val: 0 }
563 }
564
565 /// Update the CRC with the bytes in `data`
566 pub fn update(&mut self, data: &[u8]) {
567 unsafe {
568 self.val = libdeflate_crc32(self.val,
569 data.as_ptr() as *const core::ffi::c_void,
570 data.len());
571 }
572 }
573
574 /// Returns the current CRC32 checksum
575 pub const fn sum(&self) -> u32 {
576 self.val
577 }
578}
579
580/// Returns the CRC32 checksum of the bytes in `data`.
581///
582/// Note: this is a one-shot method that requires all data
583/// up-front. Developers wanting to compute a rolling crc32 from
584/// (e.g.) a stream should use [`Crc`](struct.Crc.html)
585pub fn crc32(data: &[u8]) -> u32 {
586 let mut crc = Crc::new();
587 crc.update(&data);
588 crc.sum()
589}
590
591/// Struct holding the state required to compute a rolling adler32
592/// value.
593pub struct Adler32 {
594 val: u32,
595}
596
597impl Default for Adler32 {
598 fn default() -> Self {
599 Self::new()
600 }
601}
602
603impl Adler32 {
604 /// Returns a new `Adler32` instance (with initial adler32 value 1, which is default for adler32)
605 pub const fn new() -> Adler32 {
606 Adler32 { val: 1 }
607 }
608 /// Update the Adler32 with the bytes in `data`
609 pub fn update(&mut self, data: &[u8]) {
610 unsafe
611 {
612 self.val = libdeflate_adler32(self.val,
613 data.as_ptr() as *const core::ffi::c_void,
614 data.len());
615 }
616 }
617 /// Returns the current Adler32 checksum
618 pub const fn sum(&self) -> u32 {
619 self.val
620 }
621}
622/// Returns the Adler32 checksum of the bytes in `data`.
623///
624/// Note: this is a one-shot method that requires all data
625/// up-front. Developers wanting to compute a rolling adler32 from
626/// (e.g.) a stream should use [`Adler32`](struct.Adler32.html)
627pub fn adler32(data:&[u8]) -> u32 {
628 let mut adler32 = Adler32::new();
629 adler32.update(&data);
630 adler32.sum()
631}