blob: 0965bb26f303fb11d8a60424fdb81f71585c274e [file] [log] [blame]
//! Raw interface to in-memory compression/decompression streams
use std::error;
use std::fmt;
use std::io;
use std::mem;
use std::ptr;
use std::slice;
use brotli_sys;
use libc::c_int;
use super::CompressParams;
/// 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::BrotliDecoderState,
}
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 {}
/// Possible choices for the operation performed by the compressor.
///
/// When using any operation except `Process`, you must *not* alter the
/// input buffer or use a different operation until the current operation
/// has 'completed'. An operation may need to be repeated with more space to
/// write data until it can complete.
#[repr(isize)]
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
pub enum CompressOp {
/// Compress input data
Process = brotli_sys::BROTLI_OPERATION_PROCESS as isize,
/// Compress input data, ensuring that all input so far has been
/// written out
Flush = brotli_sys::BROTLI_OPERATION_FLUSH as isize,
/// Compress input data, ensuring that all input so far has been
/// written out and then finalizing the stream so no more data can
/// be written
Finish = brotli_sys::BROTLI_OPERATION_FINISH as isize,
/// Emit a metadata block to the stream, an opaque piece of out-of-band
/// data that does not interfere with the main stream of data. Metadata
/// blocks *must* be no longer than 16MiB
EmitMetadata = brotli_sys::BROTLI_OPERATION_EMIT_METADATA as isize,
}
/// Error that can happen from decompressing or compressing a brotli stream.
#[derive(Debug, Clone, PartialEq)]
pub struct Error(());
/// Indication of whether a compression operation is 'complete'. This does
/// not indicate whether the whole stream is complete - see `Compress::compress`
/// for details.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoStatus {
/// The operation completed successfully
Finished,
/// The operation has more work to do and needs to be called again with the
/// same buffer
Unfinished,
}
/// Possible status results returned from decompressing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeStatus {
/// 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::BrotliDecoderCreateInstance(None, None, 0 as *mut _);
assert!(!state.is_null());
Decompress { state: state }
}
}
/// Decompress some input data and write it to a buffer 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<DeStatus, 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 r = unsafe {
brotli_sys::BrotliDecoderDecompressStream(self.state,
&mut available_in,
&mut next_in,
&mut available_out,
&mut next_out,
ptr::null_mut())
};
*input = &input[input.len() - available_in..];
let out_len = output.len();
*output = &mut mem::replace(output, &mut [])[out_len - available_out..];
Decompress::rc(r)
}
/// Retrieve a slice of the internal decompressor buffer up to `size_limit` in length
/// (unlimited length if `None`), consuming it. As the internal buffer may not be
/// contiguous, consecutive calls may return more output until this function returns
/// `None`.
pub fn take_output(&mut self, size_limit: Option<usize>) -> Option<&[u8]> {
if let Some(0) = size_limit { return None }
let mut size_limit = size_limit.unwrap_or(0); // 0 now means unlimited
unsafe {
let ptr = brotli_sys::BrotliDecoderTakeOutput(self.state, &mut size_limit);
if size_limit == 0 { // ptr may or may not be null
None
} else {
assert!(!ptr.is_null());
Some(slice::from_raw_parts(ptr, size_limit))
}
}
}
fn rc(rc: brotli_sys::BrotliDecoderResult) -> Result<DeStatus, Error> {
match rc {
// TODO: get info from BrotliDecoderGetErrorCode/BrotliDecoderErrorString
// for these decode errors
brotli_sys::BROTLI_DECODER_RESULT_ERROR => Err(Error(())),
brotli_sys::BROTLI_DECODER_RESULT_SUCCESS => Ok(DeStatus::Finished),
brotli_sys::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT => Ok(DeStatus::NeedInput),
brotli_sys::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT => Ok(DeStatus::NeedOutput),
n => panic!("unknown return code: {}", n)
}
}
}
impl Drop for Decompress {
fn drop(&mut self) {
unsafe {
brotli_sys::BrotliDecoderDestroyInstance(self.state);
}
}
}
/// 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, or
/// an error is returned. The output buffer being too small is considered
/// to be an error.
pub fn decompress_buf(input: &[u8],
output: &mut &mut [u8]) -> Result<usize, Error> {
let mut size = output.len();
let r = unsafe {
brotli_sys::BrotliDecoderDecompress(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 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 }
}
}
// TODO: add the BrotliEncoderOperation variants of
// BrotliEncoderCompressStream here
/// Pass some input data to the compressor and write it to a buffer of
/// output data, compressing or otherwise handling it as instructed by
/// the specified operation.
///
/// This function will handle the data in `input` and place the output
/// in `output`, returning the Result. Possible statuses are that the
/// operation is complete or incomplete.
///
/// 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.
///
/// If the result of a compress operation is `Unfinished` (which it may be
/// for any operation except `Process`), you *must* call the operation again
/// with the same operation and input buffer and more space to output to.
/// `Process` will never return `Unfinished`, but it is a logic error to end
/// a buffer without calling either `Flush` or `Finish` as some output data
/// may not have been written.
///
/// # Errors
///
/// Returns an error if brotli encountered an error while processing the stream.
///
/// # Examples
///
/// ```
/// use brotli2::raw::{Error, Compress, CompressOp, CoStatus, decompress_buf};
///
/// // An example of compressing `input` into the destination vector
/// // `output`, expanding as necessary
/// fn compress_vec(mut input: &[u8],
/// output: &mut Vec<u8>) -> Result<(), Error> {
/// let mut compress = Compress::new();
/// let nilbuf = &mut &mut [][..];
/// loop {
/// // Compressing to a buffer is easiest when the slice is already
/// // available - since we need to grow, extend from compressor
/// // internal buffer.
/// let status = try!(compress.compress(CompressOp::Finish, &mut input, nilbuf));
/// while let Some(buf) = compress.take_output(None) {
/// output.extend_from_slice(buf)
/// }
/// match status {
/// CoStatus::Finished => break,
/// CoStatus::Unfinished => (),
/// }
/// }
/// 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,
op: CompressOp,
input: &mut &[u8],
output: &mut &mut [u8]) -> Result<CoStatus, 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 r = unsafe {
brotli_sys::BrotliEncoderCompressStream(self.state,
op as brotli_sys::BrotliEncoderOperation,
&mut available_in,
&mut next_in,
&mut available_out,
&mut next_out,
ptr::null_mut())
};
*input = &input[input.len() - available_in..];
let out_len = output.len();
*output = &mut mem::replace(output, &mut [])[out_len - available_out..];
if r == 0 { return Err(Error(())) }
Ok(if op == CompressOp::Process {
CoStatus::Finished
} else if available_in != 0 {
CoStatus::Unfinished
} else if unsafe { brotli_sys::BrotliEncoderHasMoreOutput(self.state) } == 1 {
CoStatus::Unfinished
} else if op == CompressOp::Finish &&
unsafe { brotli_sys::BrotliEncoderIsFinished(self.state) } == 0 {
CoStatus::Unfinished
} else {
CoStatus::Finished
})
}
/// Retrieve a slice of the internal compressor buffer up to `size_limit` in length
/// (unlimited length if `None`), consuming it. As the internal buffer may not be
/// contiguous, consecutive calls may return more output until this function returns
/// `None`.
pub fn take_output(&mut self, size_limit: Option<usize>) -> Option<&[u8]> {
if let Some(0) = size_limit { return None }
let mut size_limit = size_limit.unwrap_or(0); // 0 now means unlimited
unsafe {
let ptr = brotli_sys::BrotliEncoderTakeOutput(self.state, &mut size_limit);
if size_limit == 0 { // ptr may or may not be null
None
} else {
assert!(!ptr.is_null());
Some(slice::from_raw_parts(ptr, size_limit))
}
}
}
/// 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);
// TODO: add these two
// brotli_sys::BrotliEncoderSetParameter(self.state,
// brotli_sys::BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING,
// params.lgblock);
// brotli_sys::BrotliEncoderSetParameter(self.state,
// brotli_sys::BROTLI_PARAM_SIZE_HINT,
// 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), or an error is returned. The output buffer being too small
/// is considered to be an error.
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 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();
let mut dst = [0; 128];
{
let mut dst = &mut dst[..];
let n = decompress_buf(data, &mut dst).unwrap();
assert_eq!(n, dst.len());
assert_eq!(dst.len(), 6);
}
assert_eq!(&dst[..6], b"hello!");
}
#[test]
fn decompress_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(DeStatus::Finished));
}
assert_eq!(&dst[..6], b"hello!");
}
#[test]
fn compress_smoke() {
let mut data = [0; 128];
let mut dst = [0; 128];
{
let mut data = &mut data[..];
let mut c = Compress::new();
let mut input = &mut &b"hello!"[..];
assert_eq!(c.compress(CompressOp::Finish, input, &mut data), Ok(CoStatus::Finished));
assert!(input.is_empty());
}
decompress_buf(&data, &mut &mut dst[..]).unwrap();
assert_eq!(&dst[..6], b"hello!");
{
let mut data = &mut data[..];
let mut c = Compress::new();
let mut input = &mut &b"hel"[..];
assert_eq!(c.compress(CompressOp::Flush, input, &mut data), Ok(CoStatus::Finished));
assert!(input.is_empty());
let mut input = &mut &b"lo!"[..];
assert_eq!(c.compress(CompressOp::Finish, input, &mut data), Ok(CoStatus::Finished));
assert!(input.is_empty());
}
decompress_buf(&data, &mut &mut dst[..]).unwrap();
assert_eq!(&dst[..6], b"hello!");
}
}