brotli2 0.2.3

Bindings to libbrotli to provide brotli decompression and compression to Rust
Documentation
//! In-memory compression/decompression streams

use std::error;
use std::fmt;
use std::io;
use std::mem;
use std::slice;

use brotli_sys;
use libc::c_int;

/// In-memory state for decompressing brotli-encoded data.
///
/// This stream is at the heart of the I/O streams and is used to decompress an
/// incoming brotli stream.
pub struct Decompress {
    state: *mut brotli_sys::BrotliState,
}

unsafe impl Send for Decompress {}
unsafe impl Sync for Decompress {}

/// In-memory state for compressing/encoding data with brotli
///
/// This stream is at the heart of the I/O encoders and is used to compress
/// data.
pub struct Compress {
    state: *mut brotli_sys::BrotliEncoderState,
}

unsafe impl Send for Compress {}
unsafe impl Sync for Compress {}

/// Parameters passed to various compression routines.
#[derive(Clone,Debug)]
pub struct CompressParams {
    /// Compression mode.
    mode: u32,
    /// Controls the compression-speed vs compression-density tradeoffs. The higher the `quality`,
    /// the slower the compression. Range is 0 to 11.
    quality: u32,
    /// Base 2 logarithm of the sliding window size. Range is 10 to 24.
    lgwin: u32,
    /// Base 2 logarithm of the maximum input block size. Range is 16 to 24. If set to 0, the value
    /// will be set based on the quality.
    lgblock: u32,
}

/// Possible choices for modes of compression
#[repr(isize)]
#[derive(Copy,Clone,Debug)]
pub enum CompressMode {
    /// Default compression mode, the compressor does not know anything in
    /// advance about the properties of the input.
    Generic = brotli_sys::BROTLI_MODE_GENERIC as isize,
    /// Compression mode for utf-8 formatted text input.
    Text = brotli_sys::BROTLI_MODE_TEXT as isize,
    /// Compression mode in WOFF 2.0.
    Font = brotli_sys::BROTLI_MODE_FONT as isize,
}

/// Error that can happen from decompressing or compressing a brotli stream.
#[derive(Debug, Clone, PartialEq)]
pub struct Error(());

/// Possible status results returned from compressing or decompressing.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Status {
    /// Decompression was successful and has finished
    Finished,
    /// More input is needed to continue
    NeedInput,
    /// More output is needed to continue
    NeedOutput,
}

impl Decompress {
    /// Creates a new brotli decompression/decoding stream ready to receive
    /// data.
    pub fn new() -> Decompress {
        unsafe {
            let state = brotli_sys::BrotliCreateState(None, None, 0 as *mut _);
            assert!(!state.is_null());
            Decompress { state: state }
        }
    }

    /// Decompress a block of input data into a block of output data.
    ///
    /// This function will decompress the data in `input` and place the output
    /// in `output`, returning the result. Possible statuses that can be
    /// returned are that the stream is finished, more input is needed, or more
    /// output space is needed.
    ///
    /// The `input` slice is updated to point to the remaining data that was not
    /// consumed, and the `output` slice is updated to point to the portion of
    /// the output slice that still needs to be filled in.
    ///
    /// # Errors
    ///
    /// If the input stream is not a valid brotli stream, then an error is
    /// returned.
    pub fn decompress(&mut self,
                      input: &mut &[u8],
                      output: &mut &mut [u8]) -> Result<Status, Error> {
        let mut available_in = input.len();
        let mut next_in = input.as_ptr();
        let mut available_out = output.len();
        let mut next_out = output.as_mut_ptr();
        let mut total_out = 0;
        let r = unsafe {
            brotli_sys::BrotliDecompressStream(&mut available_in,
                                               &mut next_in,
                                               &mut available_out,
                                               &mut next_out,
                                               &mut total_out,
                                               self.state)
        };
        *input = &input[input.len() - available_in..];
        let out_len = output.len();
        *output = &mut mem::replace(output, &mut [])[out_len - available_out..];
        Decompress::rc(r)
    }

    /// Decompress a block of input data into the remaining capacity of a
    /// vector.
    ///
    /// This function is the same as `decompress` except that it will fill up
    /// the remaining capacity in a destination vector and update the length as
    /// necessary.
    pub fn decompress_vec(&mut self,
                          input: &mut &[u8],
                          output: &mut Vec<u8>) -> Result<Status, Error> {
        let cap = output.capacity();
        let len = output.len();

        unsafe {
            let (ret, remaining) = {
                let ptr = output.as_mut_ptr().offset(len as isize);
                let mut out = slice::from_raw_parts_mut(ptr, cap - len);
                let r = self.decompress(input, &mut out);
                (r, out.len())
            };
            output.set_len(cap - remaining);
            return ret
        }
    }

    fn rc(rc: brotli_sys::BrotliResult) -> Result<Status, Error> {
        match rc {
            brotli_sys::BROTLI_RESULT_ERROR => Err(Error(())),
            brotli_sys::BROTLI_RESULT_SUCCESS => Ok(Status::Finished),
            brotli_sys::BROTLI_RESULT_NEEDS_MORE_INPUT => Ok(Status::NeedInput),
            brotli_sys::BROTLI_RESULT_NEEDS_MORE_OUTPUT => Ok(Status::NeedOutput),
            n => panic!("unknown return code: {}", n)
        }
    }
}

impl Drop for Decompress {
    fn drop(&mut self) {
        unsafe {
            brotli_sys::BrotliDestroyState(self.state);
        }
    }
}

/// Returns the decompressed size of the given encoded stream.
///
/// This function only works if the encoded buffer has a single meta block,
/// or if it has two meta-blocks, where the first is uncompressed and the
/// second is empty.
pub fn decompressed_size(data: &[u8]) -> Result<usize, Error> {
    let mut size = 0;
    let ret = unsafe {
        brotli_sys::BrotliDecompressedSize(data.len(),
                                           data.as_ptr(),
                                           &mut size)
    };
    if ret == 0 {
        Err(Error(()))
    } else {
        Ok(size)
    }
}

/// Decompress data in one go in memory.
///
/// Decompresses the data in `input` into the `output` buffer. The `output`
/// buffer is updated to point to the actual output slice if successful
pub fn decompress_buf(input: &[u8],
                      output: &mut &mut [u8]) -> Result<Status, Error> {
    let mut size = output.len();
    let r = unsafe {
        brotli_sys::BrotliDecompressBuffer(input.len(),
                                           input.as_ptr(),
                                           &mut size,
                                           output.as_mut_ptr())
    };
    *output = &mut mem::replace(output, &mut [])[..size];
    Decompress::rc(r)
}

impl Compress {
    /// Creates a new compressor ready to encode data into brotli
    pub fn new() -> Compress {
        unsafe {
            let state = brotli_sys::BrotliEncoderCreateInstance(None, None, 0 as *mut _);
            assert!(!state.is_null());

            Compress { state: state }
        }
    }

    /// Returns the maximum amount of data that can be internally buffered to
    /// get processed at once.
    ///
    /// Data is fed into this compressor via the `copy_input` function, and then
    /// it's later compressed via the `write_brotli_data` function.
    pub fn input_block_size(&self) -> usize {
        unsafe { brotli_sys::BrotliEncoderInputBlockSize(self.state) }
    }

    // Apparently this is just a shim around CopyInputToRingBuffer,
    // WriteBrotliData, and then finally a memcpy?
    //
    // #[allow(dead_code)]
    // fn write_metablock(&mut self,
    //                    input: &[u8],
    //                    last: bool,
    //                    encoded: &mut [u8]) -> Result<usize, Error> {
    //     let mut size = encoded.len();
    //     let r = unsafe {
    //         brotli_sys::RustBrotliCompressorWriteMetaBlock(self.state,
    //                                                        input.len(),
    //                                                        input.as_ptr(),
    //                                                        last as c_int,
    //                                                        &mut size,
    //                                                        encoded.as_mut_ptr())
    //     };
    //     if r == 0 {
    //         Err(Error(()))
    //     } else {
    //         Ok(size)
    //     }
    // }

    // Maybe someone will eventually come up with a use for this?
    //
    // #[allow(dead_code)]
    // fn write_metadata(&mut self,
    //                   input: &[u8],
    //                   last: bool,
    //                   encoded: &mut [u8]) -> Result<usize, Error> {
    //     let mut size = encoded.len();
    //     let r = unsafe {
    //         brotli_sys::RustBrotliCompressorWriteMetadata(self.state,
    //                                                       input.len(),
    //                                                       input.as_ptr(),
    //                                                       last as c_int,
    //                                                       &mut size,
    //                                                       encoded.as_mut_ptr())
    //     };
    //     if r == 0 {
    //         Err(Error(()))
    //     } else {
    //         Ok(size)
    //     }
    // }

    // This is just a shim around WriteMetaBlock, which is in turn just a shim
    // around WriteBrotliData, so let's just delegate there I guess?
    //
    // #[allow(dead_code)]
    // fn finish_stream(&mut self, output: &mut [u8]) -> Result<usize, Error> {
    //     let mut size = output.len();
    //     let r = unsafe {
    //         brotli_sys::RustBrotliCompressorFinishStream(self.state,
    //                                                      &mut size,
    //                                                      output.as_mut_ptr())
    //     };
    //     if r == 0 {
    //         Err(Error(()))
    //     } else {
    //         Ok(size)
    //     }
    // }

    /// Feeds data into this compressor.
    ///
    /// This compressor can store up to `self.input_block_size()` bytes
    /// internally after which the `compress` call must be made to generate all
    /// output of the compressor.
    ///
    /// If too much data is copied in then the next call to `compress` will
    /// generate an error.
    pub fn copy_input(&mut self, input: &[u8]) {
        unsafe {
            brotli_sys::BrotliEncoderCopyInputToRingBuffer(self.state,
                                                           input.len(),
                                                           input.as_ptr())
        }
    }

    /// Compresses the internal data in this compressor, returning the output
    /// buffer of the compressed data.
    ///
    /// After data has been fed to this compressor via the `copy_input` method,
    /// the data is then compressed by calling this method. The `last` flag
    /// indicates whether this is the last block of the input (it should only
    /// get passed on EOF), and the `force_flush` flag indicates whether a new
    /// meta-block should be created to flush the internal data.
    ///
    /// Returns an error, if any, and otherwise the internal buffer which
    /// contains the output data of compressed information.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::cmp;
    /// use brotli2::stream::{Error, Compress, decompress_buf};
    ///
    /// // An example of compressing `input` into the destination vector
    /// // `output`
    /// fn compress_vec(input: &[u8],
    ///                 output: &mut Vec<u8>) -> Result<(), Error> {
    ///     let mut compress = Compress::new();
    ///     for chunk in input.chunks(compress.input_block_size()) {
    ///         compress.copy_input(chunk);
    ///         output.extend_from_slice(&try!(compress.compress(false, false)));
    ///     }
    ///     output.extend_from_slice(&try!(compress.compress(true, false)));
    ///     Ok(())
    /// }
    ///
    /// fn assert_roundtrip(data: &[u8]) {
    ///     let mut compressed = Vec::new();
    ///     compress_vec(data, &mut compressed).unwrap();
    ///
    ///     let mut decompressed = [0; 2048];
    ///     let mut decompressed = &mut decompressed[..];
    ///     decompress_buf(&compressed, &mut decompressed).unwrap();
    ///     assert_eq!(decompressed, data);
    /// }
    ///
    /// assert_roundtrip(b"Hello, World!");
    /// assert_roundtrip(b"");
    /// assert_roundtrip(&[6; 1024]);
    /// ```
    pub fn compress(&mut self, last: bool, force_flush: bool)
                    -> Result<&[u8], Error> {
        let mut size = 0;
        let mut ptr = 0 as *mut _;
        unsafe {
            let (last, flush) = (last as c_int, force_flush as c_int);
            let r = brotli_sys::BrotliEncoderWriteData(self.state,
                                                       last,
                                                       flush,
                                                       &mut size,
                                                       &mut ptr);
            if r == 0 {
                Err(Error(()))
            } else if size == 0 {
                Ok(slice::from_raw_parts_mut(1 as *mut _, size))
            } else {
                Ok(slice::from_raw_parts_mut(ptr, size))
            }
        }
    }

    /// Configure the parameters of this compression session.
    ///
    /// Note that this is likely to only successful if called before compression
    /// starts.
    pub fn set_params(&mut self, params: &CompressParams) {
        unsafe {
            brotli_sys::BrotliEncoderSetParameter(self.state,
                                                  brotli_sys::BROTLI_PARAM_MODE,
                                                  params.mode);
            brotli_sys::BrotliEncoderSetParameter(self.state,
                                                  brotli_sys::BROTLI_PARAM_QUALITY,
                                                  params.quality);
            brotli_sys::BrotliEncoderSetParameter(self.state,
                                                  brotli_sys::BROTLI_PARAM_LGWIN,
                                                  params.lgwin);
            brotli_sys::BrotliEncoderSetParameter(self.state,
                                                  brotli_sys::BROTLI_PARAM_LGBLOCK,
                                                  params.lgblock);
        }
    }
}

impl Drop for Compress {
    fn drop(&mut self) {
        unsafe {
            brotli_sys::BrotliEncoderDestroyInstance(self.state);
        }
    }
}

/// Compresses the data in `input` into `output`.
///
/// The `output` buffer is updated to point to the exact slice which contains
/// the output data.
///
/// If successful, the amount of compressed bytes are returned (the size of the
/// `output` slice), and otherwise an error is returned.
pub fn compress_buf(params: &CompressParams,
                    input: &[u8],
                    output: &mut &mut [u8]) -> Result<usize, Error> {
    let mut size = output.len();
    let r = unsafe {
        brotli_sys::BrotliEncoderCompress(params.quality as c_int,
                                          params.lgwin as c_int,
                                          params.mode as brotli_sys::BrotliEncoderMode,
                                          input.len(),
                                          input.as_ptr(),
                                          &mut size,
                                          output.as_mut_ptr())
    };
    *output = &mut mem::replace(output, &mut [])[..size];
    if r == 0 {
        Err(Error(()))
    } else {
        Ok(size)
    }
}

impl CompressParams {
    /// Creates a new default set of compression parameters.
    pub fn new() -> CompressParams {
        CompressParams {
            mode: brotli_sys::BROTLI_DEFAULT_MODE,
            quality: brotli_sys::BROTLI_DEFAULT_QUALITY,
            lgwin: brotli_sys::BROTLI_DEFAULT_WINDOW,
            lgblock: 0,
        }
    }

    /// Set the mode of this compression.
    pub fn mode(&mut self, mode: CompressMode) -> &mut CompressParams {
        self.mode = mode as u32;
        self
    }

    /// Controls the compression-speed vs compression-density tradeoffs.
    ///
    /// The higher the quality, the slower the compression. Currently the range
    /// for the quality is 0 to 11.
    pub fn quality(&mut self, quality: u32) -> &mut CompressParams {
        self.quality = quality;
        self
    }

    /// Sets the base 2 logarithm of the sliding window size.
    ///
    /// Currently the range is 10 to 24.
    pub fn lgwin(&mut self, lgwin: u32) -> &mut CompressParams {
        self.lgwin = lgwin;
        self
    }

    /// Sets the base 2 logarithm of the maximum input block size.
    ///
    /// Currently the range is 16 to 24, and if set to 0 the value will be set
    /// based on the quality.
    pub fn lgblock(&mut self, lgblock: u32) -> &mut CompressParams {
        self.lgblock = lgblock;
        self
    }

    /// Get the current block size
    #[inline]
    pub fn get_lgblock_readable(&self) -> usize {
        1usize << self.lgblock
    }

    /// Get the native lgblock size
    #[inline]
    pub fn get_lgblock(&self) -> u32 {
        self.lgblock.clone()
    }
    /// Get the current window size
    #[inline]
    pub fn get_lgwin_readable(&self) -> usize {
        1usize << self.lgwin
    }
    /// Get the native lgwin value
    #[inline]
    pub fn get_lgwin(&self) -> u32 {
        self.lgwin.clone()
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        error::Error::description(self).fmt(f)
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        "brotli error"
    }
}

impl From<Error> for io::Error {
    fn from(_err: Error) -> io::Error {
        io::Error::new(io::ErrorKind::Other, "brotli error")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn decompress_error() {
        let mut d = Decompress::new();
        d.decompress(&mut &[0; 1024][..], &mut &mut [0; 2048][..]).unwrap_err();
    }

    #[test]
    fn compress_buf_smoke() {
        let mut data = [0; 128];
        let mut data = &mut data[..];
        compress_buf(&CompressParams::new(), b"hello!", &mut data).unwrap();
        assert_eq!(decompressed_size(data), Ok(6));

        let mut dst = [0; 128];
        {
            let mut dst = &mut dst[..];
            let n = decompress_buf(data, &mut dst).unwrap();
            assert_eq!(n, Status::Finished);
            assert_eq!(dst.len(), 6);
        }
        assert_eq!(&dst[..6], b"hello!");
    }

    #[test]
    fn decompressor_smoke() {
        let mut data = [0; 128];
        let mut data = &mut data[..];
        compress_buf(&CompressParams::new(), b"hello!", &mut data).unwrap();

        let mut d = Decompress::new();
        let mut dst = [0; 128];
        {
            let mut data = &data[..];
            let mut dst = &mut dst[..];
            assert_eq!(d.decompress(&mut data, &mut dst), Ok(Status::Finished));
        }
        assert_eq!(&dst[..6], b"hello!");

        let mut d = Decompress::new();
        let mut dst = Vec::with_capacity(10);
        assert_eq!(d.decompress_vec(&mut &data[..], &mut dst),
                   Ok(Status::Finished));
        assert_eq!(&dst, b"hello!");
    }

    #[test]
    fn compress_smoke() {
        let mut data = Vec::new();
        let mut c = Compress::new();
        c.copy_input(b"hello!");
        data.extend_from_slice(c.compress(true, false).unwrap());

        let mut dst = [0; 128];
        decompress_buf(&data, &mut &mut dst[..]).unwrap();
        assert_eq!(&dst[..6], b"hello!");

        data.truncate(0);
        let mut c = Compress::new();
        c.copy_input(b"hel");
        data.extend_from_slice(c.compress(false, true).unwrap());
        c.copy_input(b"lo!");
        data.extend_from_slice(c.compress(true, false).unwrap());

        let mut dst = [0; 128];
        decompress_buf(&data, &mut &mut dst[..]).unwrap();
        assert_eq!(&dst[..6], b"hello!");
    }
}