Skip to main content

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