miniz_oxide_c_api 0.2.2

DEFLATE compression and decompression API designed to be Rust drop-in replacement for miniz
Documentation
use libc::*;
use std::{mem, cmp, ptr, slice};
use std::panic::{catch_unwind, AssertUnwindSafe};

use miniz_oxide::deflate::core::{compress, compress_to_output, create_comp_flags_from_zip_params,
                                 CompressorOxide, TDEFLFlush, TDEFLStatus};

/// Compression callback function type.
pub type PutBufFuncPtrNotNull = unsafe extern "C" fn(
    *const c_void, c_int, *mut c_void) -> bool;
/// `Option` alias for compression callback function type.
pub type PutBufFuncPtr = Option<PutBufFuncPtrNotNull>;

pub struct CallbackFunc {
    pub put_buf_func: PutBufFuncPtrNotNull,
    pub put_buf_user: *mut c_void,
}

/// Deflate flush modes.
pub mod flush_modes {
    use libc::c_int;
    use miniz_oxide::deflate::core::TDEFLFlush;
    pub const MZ_NO_FLUSH: c_int = TDEFLFlush::None as c_int;
    // TODO: This is simply sync flush for now, miniz also treats it as such.
    pub const MZ_PARTIAL_FLUSH: c_int = 1;
    pub const MZ_SYNC_FLUSH: c_int = TDEFLFlush::Sync as c_int;
    pub const MZ_FULL_FLUSH: c_int = TDEFLFlush::Full as c_int;
    pub const MZ_FINISH: c_int = TDEFLFlush::Finish as c_int;
    // TODO: Doesn't seem to be implemented by miniz.
    pub const MZ_BLOCK: c_int = 5;
}

pub mod strategy {
    use libc::c_int;
    use miniz_oxide::deflate::core::CompressionStrategy::*;
    pub const MZ_DEFAULT_STRATEGY: c_int = Default as c_int;
    pub const MZ_FILTERED: c_int = Filtered as c_int;
    pub const MZ_HUFFMAN_ONLY: c_int = HuffmanOnly as c_int;
    pub const MZ_RLE: c_int = RLE as c_int;
    pub const MZ_FIXED: c_int = Fixed as c_int;
}

/// Main compression struct. Not the same as `CompressorOxide`
#[repr(C)]
#[allow(bad_style)]
pub struct tdefl_compressor {
    pub inner: Option<CompressorOxide>,
    pub callback: Option<CallbackFunc>,
}

impl tdefl_compressor {
    pub(crate) fn new(flags: u32) -> Self {
        tdefl_compressor {
            inner: Some(CompressorOxide::new(flags)),
            callback: None,
        }
    }

    pub(crate) fn new_with_callback(flags: u32, func: CallbackFunc) -> Self {
        tdefl_compressor {
            inner: Some(CompressorOxide::new(flags)),
            callback: Some(func),
        }
    }

    /// Sets the inner state to `None` and thus drops it.
    pub fn drop_inner(&mut self) {
        self.inner = None;
    }

    pub fn adler32(&self) -> u32 {
        self.inner.as_ref().map(|i| i.adler32()).unwrap_or(0)
    }

    pub fn prev_return_status(&self) -> TDEFLStatus {
        // Not sure we should return on inner not existing, but that shouldn't happen
        // anyway.
        self.inner.as_ref().map(|i| i.prev_return_status()).unwrap_or(TDEFLStatus::BadParam)
    }

    pub fn flags(&self) -> i32 {
        self.inner.as_ref().map(|i| i.flags()).unwrap_or(0)
    }
}


unmangle!(
pub unsafe extern "C" fn tdefl_compress(
    d: Option<&mut tdefl_compressor>,
    in_buf: *const c_void,
    in_size: Option<&mut usize>,
    out_buf: *mut c_void,
    out_size: Option<&mut usize>,
    flush: TDEFLFlush,
) -> TDEFLStatus {
    match d {
        None => {
            in_size.map(|size| *size = 0);
            out_size.map(|size| *size = 0);
            TDEFLStatus::BadParam
        },
        Some(compressor_wrap) => {
            if let Some(ref mut compressor) = compressor_wrap.inner {
            let in_buf_size = in_size.as_ref().map_or(0, |size| **size);
            let out_buf_size = out_size.as_ref().map_or(0, |size| **size);

            if in_buf_size > 0 && in_buf.is_null() {
                in_size.map(|size| *size = 0);
                out_size.map(|size| *size = 0);
                return TDEFLStatus::BadParam;
            }

            let in_slice = (in_buf as *const u8).as_ref().map_or(&[][..],|in_buf| {
                slice::from_raw_parts(in_buf, in_buf_size)
            });

            let res = match compressor_wrap.callback {
                None => {
                    match (out_buf as *mut u8).as_mut() {
                        Some(out_buf) => compress(
                            compressor,
                            in_slice,
                            slice::from_raw_parts_mut(out_buf, out_buf_size),
                            flush,
                        ),
                        None => {
                            in_size.map(|size| *size = 0);
                            out_size.map(|size| *size = 0);
                            return TDEFLStatus::BadParam;
                        }
                    }
                },
                Some(ref func) => {
                    if out_buf_size > 0 || !out_buf.is_null() {
                        in_size.map(|size| *size = 0);
                        out_size.map(|size| *size = 0);
                        return TDEFLStatus::BadParam;
                    }
                    let res = compress_to_output(
                        compressor,
                        in_slice,
                        flush,
                        |out: &[u8]| {
                            (func.put_buf_func)(
                                &(out[0]) as *const u8 as *const c_void,
                                out.len() as i32,
                                func.put_buf_user
                            )
                        },
                    );
                    (res.0, res.1, 0)
                },
            };

            in_size.map(|size| *size = res.1);
            out_size.map(|size| *size = res.2);
            res.0
            } else {
                TDEFLStatus::BadParam
            }
        }
    }
}

pub unsafe extern "C" fn tdefl_compress_buffer(
    d: Option<&mut tdefl_compressor>,
    in_buf: *const c_void,
    mut in_size: usize,
    flush: TDEFLFlush,
) -> TDEFLStatus {
    tdefl_compress(d, in_buf, Some(&mut in_size), ptr::null_mut(), None, flush)
}

/// Allocate a compressor.
///
/// This does initialize the struct, but not the inner constructor,
/// tdefl_init has to be called before doing anything with it.
pub unsafe extern "C" fn tdefl_allocate() -> *mut tdefl_compressor {
    Box::into_raw(Box::<tdefl_compressor>::new(
        tdefl_compressor {
            inner: None,
            callback: None,
        }
    ))
}

/// Deallocate the compressor. (Does nothing if the argument is null).
///
/// This also calles the compressor's destructor, freeing the internal memory
/// allocated by it.
pub unsafe extern "C" fn tdefl_deallocate(c: *mut tdefl_compressor) {
    if !c.is_null() {
        Box::from_raw(c);
    }
}

/// Initialize the compressor struct in the space pointed to by `d`.
/// if d is null, an error is returned.
///
/// Deinitialization is handled by tdefl_deallocate, and thus
/// tdefl_compressor should not be allocated or freed manually, but only through
/// tdefl_allocate and tdefl_deallocate
pub unsafe extern "C" fn tdefl_init(
    d: Option<&mut tdefl_compressor>,
    put_buf_func: PutBufFuncPtr,
    put_buf_user: *mut c_void,
    flags: c_int,
) -> TDEFLStatus {
    if let Some(d) = d {
        match catch_unwind( AssertUnwindSafe(|| {
        d.inner = Some(CompressorOxide::new(flags as u32));
        if let Some(f) = put_buf_func {
            d.callback = Some(CallbackFunc {
                    put_buf_func: f,
                    put_buf_user: put_buf_user,
            })
        } else {
            d.callback = None;
        };

        })) {
            Ok(_) => TDEFLStatus::Okay,
            Err(_) => {
                eprintln!("FATAL ERROR: Caught panic when initializing the compressor!");
                TDEFLStatus::BadParam
            }
        }
    } else {
        TDEFLStatus::BadParam
    }
}

pub unsafe extern "C" fn tdefl_get_prev_return_status(
    d: Option<&mut tdefl_compressor>,
) -> TDEFLStatus {
    d.map_or(TDEFLStatus::Okay, |d| d.prev_return_status())
}

pub unsafe extern "C" fn tdefl_get_adler32(d: Option<&mut tdefl_compressor>) -> c_uint {
    d.map_or(::MZ_ADLER32_INIT as u32, |d| d.adler32())
}

pub unsafe extern "C" fn tdefl_compress_mem_to_output(
    buf: *const c_void,
    buf_len: usize,
    put_buf_func: PutBufFuncPtr,
    put_buf_user: *mut c_void,
    flags: c_int,
) -> bool {
    if let Some(put_buf_func) = put_buf_func {
        let compressor = ::miniz_def_alloc_func(
            ptr::null_mut(),
            1,
            mem::size_of::<tdefl_compressor>(),
        ) as *mut tdefl_compressor;

        ptr::write(compressor,tdefl_compressor::new_with_callback(flags as u32, CallbackFunc {
            put_buf_func: put_buf_func,
            put_buf_user: put_buf_user,
        }));

        let res = tdefl_compress_buffer(compressor.as_mut(), buf, buf_len, TDEFLFlush::Finish) ==
            TDEFLStatus::Done;
        compressor.as_mut().map(|c| c.drop_inner());
        ::miniz_def_free_func(ptr::null_mut(), compressor as *mut c_void);
        res
    } else {
        false
    }
}
);

struct BufferUser {
    pub size: usize,
    pub capacity: usize,
    pub buf: *mut u8,
    pub expandable: bool,
}

pub unsafe extern "C" fn output_buffer_putter(
    buf: *const c_void,
    len: c_int,
    user: *mut c_void,
) -> bool {
    let user = (user as *mut BufferUser).as_mut();
    match user {
        None => false,
        Some(user) => {
            let new_size = user.size + len as usize;
            if new_size > user.capacity {
                if !user.expandable {
                    return false;
                }
                let mut new_capacity = cmp::max(user.capacity, 128);
                while new_size > new_capacity {
                    new_capacity <<= 1;
                }

                let new_buf = ::miniz_def_realloc_func(
                    ptr::null_mut(),
                    user.buf as *mut c_void,
                    1,
                    new_capacity,
                );

                if new_buf.is_null() {
                    return false;
                }

                user.buf = new_buf as *mut u8;
                user.capacity = new_capacity;
            }

            ptr::copy_nonoverlapping(
                buf as *const u8,
                user.buf.offset(user.size as isize),
                len as usize,
            );
            user.size = new_size;
            true
        }
    }
}

unmangle!(
pub unsafe extern "C" fn tdefl_compress_mem_to_heap(
    src_buf: *const c_void,
    src_buf_len: usize,
    out_len: *mut usize,
    flags: c_int,
) -> *mut c_void {
    match out_len.as_mut() {
        None => ptr::null_mut(),
        Some(len) => {
            *len = 0;

            let mut buffer_user = BufferUser {
                size: 0,
                capacity: 0,
                buf: ptr::null_mut(),
                expandable: true,
            };

            if !tdefl_compress_mem_to_output(
                src_buf,
                src_buf_len,
                Some(output_buffer_putter),
                &mut buffer_user as *mut BufferUser as *mut c_void,
                flags,
            ) {
                ptr::null_mut()
            } else {
                *len = buffer_user.size;
                buffer_user.buf as *mut c_void
            }
        }
    }
}

pub unsafe extern "C" fn tdefl_compress_mem_to_mem(
    out_buf: *mut c_void,
    out_buf_len: usize,
    src_buf: *const c_void,
    src_buf_len: usize,
    flags: c_int,
) -> usize {
    if out_buf.is_null() {
        return 0;
    }
    let mut buffer_user = BufferUser {
        size: 0,
        capacity: out_buf_len,
        buf: out_buf as *mut u8,
        expandable: false,
    };

    if tdefl_compress_mem_to_output(
        src_buf,
        src_buf_len,
        Some(output_buffer_putter),
        &mut buffer_user as *mut BufferUser as *mut c_void,
        flags,
    )
    {
        buffer_user.size
    } else {
        0
    }
}

pub extern "C" fn tdefl_create_comp_flags_from_zip_params(
    level: c_int,
    window_bits: c_int,
    strategy: c_int,
) -> c_uint {
    create_comp_flags_from_zip_params(level, window_bits, strategy)
}
);

#[cfg(test)]
mod test {
    use super::*;
    use miniz_oxide::inflate::decompress_to_vec;

    #[test]
    fn mem_to_heap() {
        let data = b"blargharghawrf31086t13qa9pt7gnseatgawe78vtb6p71v";
        let mut out_len = 0;
        let data_len = data.len();
        let out_data = unsafe {
            let res = tdefl_compress_mem_to_heap(
                data.as_ptr() as *const c_void, data_len, &mut out_len, 0);
            assert!(!res.is_null());
            res
        };
        {
            let out_slice = unsafe {
                slice::from_raw_parts(out_data as *const u8, out_len)
            };
            let dec = decompress_to_vec(out_slice).unwrap();
            assert!(dec.as_slice() == &data[..]);
        }
   }
}