| //! Implementation for C backends. |
| use std::alloc::{self, Layout}; |
| use std::cmp; |
| use std::convert::TryFrom; |
| use std::fmt; |
| use std::marker; |
| use std::ops::{Deref, DerefMut}; |
| use std::os::raw::{c_int, c_uint, c_void}; |
| use std::ptr; |
| |
| use super::*; |
| use crate::mem::{self, FlushDecompress, Status}; |
| |
| #[derive(Default)] |
| pub struct ErrorMessage(Option<&'static str>); |
| |
| impl ErrorMessage { |
| pub fn get(&self) -> Option<&str> { |
| self.0 |
| } |
| } |
| |
| pub struct StreamWrapper { |
| pub inner: Box<mz_stream>, |
| } |
| |
| impl fmt::Debug for StreamWrapper { |
| fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { |
| write!(f, "StreamWrapper") |
| } |
| } |
| |
| impl Default for StreamWrapper { |
| fn default() -> StreamWrapper { |
| StreamWrapper { |
| inner: Box::new(mz_stream { |
| next_in: ptr::null_mut(), |
| avail_in: 0, |
| total_in: 0, |
| next_out: ptr::null_mut(), |
| avail_out: 0, |
| total_out: 0, |
| msg: ptr::null_mut(), |
| adler: 0, |
| data_type: 0, |
| reserved: 0, |
| opaque: ptr::null_mut(), |
| state: ptr::null_mut(), |
| #[cfg(all(feature = "any_zlib", not(feature = "cloudflare-zlib-sys")))] |
| zalloc, |
| #[cfg(all(feature = "any_zlib", not(feature = "cloudflare-zlib-sys")))] |
| zfree, |
| #[cfg(not(all(feature = "any_zlib", not(feature = "cloudflare-zlib-sys"))))] |
| zalloc: Some(zalloc), |
| #[cfg(not(all(feature = "any_zlib", not(feature = "cloudflare-zlib-sys"))))] |
| zfree: Some(zfree), |
| }), |
| } |
| } |
| } |
| |
| const ALIGN: usize = std::mem::align_of::<usize>(); |
| |
| fn align_up(size: usize, align: usize) -> usize { |
| (size + align - 1) & !(align - 1) |
| } |
| |
| extern "C" fn zalloc(_ptr: *mut c_void, items: AllocSize, item_size: AllocSize) -> *mut c_void { |
| // We need to multiply `items` and `item_size` to get the actual desired |
| // allocation size. Since `zfree` doesn't receive a size argument we |
| // also need to allocate space for a `usize` as a header so we can store |
| // how large the allocation is to deallocate later. |
| let size = match items |
| .checked_mul(item_size) |
| .and_then(|i| usize::try_from(i).ok()) |
| .map(|size| align_up(size, ALIGN)) |
| .and_then(|i| i.checked_add(std::mem::size_of::<usize>())) |
| { |
| Some(i) => i, |
| None => return ptr::null_mut(), |
| }; |
| |
| // Make sure the `size` isn't too big to fail `Layout`'s restrictions |
| let layout = match Layout::from_size_align(size, ALIGN) { |
| Ok(layout) => layout, |
| Err(_) => return ptr::null_mut(), |
| }; |
| |
| unsafe { |
| // Allocate the data, and if successful store the size we allocated |
| // at the beginning and then return an offset pointer. |
| let ptr = alloc::alloc(layout) as *mut usize; |
| if ptr.is_null() { |
| return ptr as *mut c_void; |
| } |
| *ptr = size; |
| ptr.add(1) as *mut c_void |
| } |
| } |
| |
| extern "C" fn zfree(_ptr: *mut c_void, address: *mut c_void) { |
| unsafe { |
| // Move our address being freed back one pointer, read the size we |
| // stored in `zalloc`, and then free it using the standard Rust |
| // allocator. |
| let ptr = (address as *mut usize).offset(-1); |
| let size = *ptr; |
| let layout = Layout::from_size_align_unchecked(size, ALIGN); |
| alloc::dealloc(ptr as *mut u8, layout) |
| } |
| } |
| |
| impl Deref for StreamWrapper { |
| type Target = mz_stream; |
| |
| fn deref(&self) -> &Self::Target { |
| &*self.inner |
| } |
| } |
| |
| impl DerefMut for StreamWrapper { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut *self.inner |
| } |
| } |
| |
| unsafe impl<D: Direction> Send for Stream<D> {} |
| unsafe impl<D: Direction> Sync for Stream<D> {} |
| |
| /// Trait used to call the right destroy/end function on the inner |
| /// stream object on drop. |
| pub trait Direction { |
| unsafe fn destroy(stream: *mut mz_stream) -> c_int; |
| } |
| |
| #[derive(Debug)] |
| pub enum DirCompress {} |
| #[derive(Debug)] |
| pub enum DirDecompress {} |
| |
| #[derive(Debug)] |
| pub struct Stream<D: Direction> { |
| pub stream_wrapper: StreamWrapper, |
| pub total_in: u64, |
| pub total_out: u64, |
| pub _marker: marker::PhantomData<D>, |
| } |
| |
| impl<D: Direction> Stream<D> { |
| pub fn msg(&self) -> ErrorMessage { |
| let msg = self.stream_wrapper.msg; |
| ErrorMessage(if msg.is_null() { |
| None |
| } else { |
| let s = unsafe { std::ffi::CStr::from_ptr(msg) }; |
| std::str::from_utf8(s.to_bytes()).ok() |
| }) |
| } |
| } |
| |
| impl<D: Direction> Drop for Stream<D> { |
| fn drop(&mut self) { |
| unsafe { |
| let _ = D::destroy(&mut *self.stream_wrapper); |
| } |
| } |
| } |
| |
| impl Direction for DirCompress { |
| unsafe fn destroy(stream: *mut mz_stream) -> c_int { |
| mz_deflateEnd(stream) |
| } |
| } |
| impl Direction for DirDecompress { |
| unsafe fn destroy(stream: *mut mz_stream) -> c_int { |
| mz_inflateEnd(stream) |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct Inflate { |
| pub inner: Stream<DirDecompress>, |
| } |
| |
| impl InflateBackend for Inflate { |
| fn make(zlib_header: bool, window_bits: u8) -> Self { |
| unsafe { |
| let mut state = StreamWrapper::default(); |
| let ret = mz_inflateInit2( |
| &mut *state, |
| if zlib_header { |
| window_bits as c_int |
| } else { |
| -(window_bits as c_int) |
| }, |
| ); |
| assert_eq!(ret, 0); |
| Inflate { |
| inner: Stream { |
| stream_wrapper: state, |
| total_in: 0, |
| total_out: 0, |
| _marker: marker::PhantomData, |
| }, |
| } |
| } |
| } |
| |
| fn decompress( |
| &mut self, |
| input: &[u8], |
| output: &mut [u8], |
| flush: FlushDecompress, |
| ) -> Result<Status, DecompressError> { |
| let raw = &mut *self.inner.stream_wrapper; |
| raw.msg = ptr::null_mut(); |
| raw.next_in = input.as_ptr() as *mut u8; |
| raw.avail_in = cmp::min(input.len(), c_uint::MAX as usize) as c_uint; |
| raw.next_out = output.as_mut_ptr(); |
| raw.avail_out = cmp::min(output.len(), c_uint::MAX as usize) as c_uint; |
| |
| let rc = unsafe { mz_inflate(raw, flush as c_int) }; |
| |
| // Unfortunately the total counters provided by zlib might be only |
| // 32 bits wide and overflow while processing large amounts of data. |
| self.inner.total_in += (raw.next_in as usize - input.as_ptr() as usize) as u64; |
| self.inner.total_out += (raw.next_out as usize - output.as_ptr() as usize) as u64; |
| |
| // reset these pointers so we don't accidentally read them later |
| raw.next_in = ptr::null_mut(); |
| raw.avail_in = 0; |
| raw.next_out = ptr::null_mut(); |
| raw.avail_out = 0; |
| |
| match rc { |
| MZ_DATA_ERROR | MZ_STREAM_ERROR => mem::decompress_failed(self.inner.msg()), |
| MZ_OK => Ok(Status::Ok), |
| MZ_BUF_ERROR => Ok(Status::BufError), |
| MZ_STREAM_END => Ok(Status::StreamEnd), |
| MZ_NEED_DICT => mem::decompress_need_dict(raw.adler as u32), |
| c => panic!("unknown return code: {}", c), |
| } |
| } |
| |
| fn reset(&mut self, zlib_header: bool) { |
| let bits = if zlib_header { |
| MZ_DEFAULT_WINDOW_BITS |
| } else { |
| -MZ_DEFAULT_WINDOW_BITS |
| }; |
| unsafe { |
| inflateReset2(&mut *self.inner.stream_wrapper, bits); |
| } |
| self.inner.total_out = 0; |
| self.inner.total_in = 0; |
| } |
| } |
| |
| impl Backend for Inflate { |
| #[inline] |
| fn total_in(&self) -> u64 { |
| self.inner.total_in |
| } |
| |
| #[inline] |
| fn total_out(&self) -> u64 { |
| self.inner.total_out |
| } |
| } |
| |
| #[derive(Debug)] |
| pub struct Deflate { |
| pub inner: Stream<DirCompress>, |
| } |
| |
| impl DeflateBackend for Deflate { |
| fn make(level: Compression, zlib_header: bool, window_bits: u8) -> Self { |
| unsafe { |
| let mut state = StreamWrapper::default(); |
| let ret = mz_deflateInit2( |
| &mut *state, |
| level.0 as c_int, |
| MZ_DEFLATED, |
| if zlib_header { |
| window_bits as c_int |
| } else { |
| -(window_bits as c_int) |
| }, |
| 8, |
| MZ_DEFAULT_STRATEGY, |
| ); |
| assert_eq!(ret, 0); |
| Deflate { |
| inner: Stream { |
| stream_wrapper: state, |
| total_in: 0, |
| total_out: 0, |
| _marker: marker::PhantomData, |
| }, |
| } |
| } |
| } |
| fn compress( |
| &mut self, |
| input: &[u8], |
| output: &mut [u8], |
| flush: FlushCompress, |
| ) -> Result<Status, CompressError> { |
| let raw = &mut *self.inner.stream_wrapper; |
| raw.msg = ptr::null_mut(); |
| raw.next_in = input.as_ptr() as *mut _; |
| raw.avail_in = cmp::min(input.len(), c_uint::MAX as usize) as c_uint; |
| raw.next_out = output.as_mut_ptr(); |
| raw.avail_out = cmp::min(output.len(), c_uint::MAX as usize) as c_uint; |
| |
| let rc = unsafe { mz_deflate(raw, flush as c_int) }; |
| |
| // Unfortunately the total counters provided by zlib might be only |
| // 32 bits wide and overflow while processing large amounts of data. |
| self.inner.total_in += (raw.next_in as usize - input.as_ptr() as usize) as u64; |
| self.inner.total_out += (raw.next_out as usize - output.as_ptr() as usize) as u64; |
| |
| // reset these pointers so we don't accidentally read them later |
| raw.next_in = ptr::null_mut(); |
| raw.avail_in = 0; |
| raw.next_out = ptr::null_mut(); |
| raw.avail_out = 0; |
| |
| match rc { |
| MZ_OK => Ok(Status::Ok), |
| MZ_BUF_ERROR => Ok(Status::BufError), |
| MZ_STREAM_END => Ok(Status::StreamEnd), |
| MZ_STREAM_ERROR => mem::compress_failed(self.inner.msg()), |
| c => panic!("unknown return code: {}", c), |
| } |
| } |
| |
| fn reset(&mut self) { |
| self.inner.total_in = 0; |
| self.inner.total_out = 0; |
| let rc = unsafe { mz_deflateReset(&mut *self.inner.stream_wrapper) }; |
| assert_eq!(rc, MZ_OK); |
| } |
| } |
| |
| impl Backend for Deflate { |
| #[inline] |
| fn total_in(&self) -> u64 { |
| self.inner.total_in |
| } |
| |
| #[inline] |
| fn total_out(&self) -> u64 { |
| self.inner.total_out |
| } |
| } |
| |
| pub use self::c_backend::*; |
| |
| /// For backwards compatibility, we provide symbols as `mz_` to mimic the miniz API |
| #[allow(bad_style)] |
| mod c_backend { |
| use std::mem; |
| use std::os::raw::{c_char, c_int}; |
| |
| #[cfg(feature = "zlib-ng")] |
| use libz_ng_sys as libz; |
| |
| #[cfg(all(not(feature = "zlib-ng"), feature = "cloudflare_zlib"))] |
| use cloudflare_zlib_sys as libz; |
| |
| #[cfg(all(not(feature = "cloudflare_zlib"), not(feature = "zlib-ng")))] |
| use libz_sys as libz; |
| |
| pub use libz::deflate as mz_deflate; |
| pub use libz::deflateEnd as mz_deflateEnd; |
| pub use libz::deflateReset as mz_deflateReset; |
| pub use libz::inflate as mz_inflate; |
| pub use libz::inflateEnd as mz_inflateEnd; |
| pub use libz::z_stream as mz_stream; |
| pub use libz::*; |
| |
| pub use libz::Z_BLOCK as MZ_BLOCK; |
| pub use libz::Z_BUF_ERROR as MZ_BUF_ERROR; |
| pub use libz::Z_DATA_ERROR as MZ_DATA_ERROR; |
| pub use libz::Z_DEFAULT_STRATEGY as MZ_DEFAULT_STRATEGY; |
| pub use libz::Z_DEFLATED as MZ_DEFLATED; |
| pub use libz::Z_FINISH as MZ_FINISH; |
| pub use libz::Z_FULL_FLUSH as MZ_FULL_FLUSH; |
| pub use libz::Z_NEED_DICT as MZ_NEED_DICT; |
| pub use libz::Z_NO_FLUSH as MZ_NO_FLUSH; |
| pub use libz::Z_OK as MZ_OK; |
| pub use libz::Z_PARTIAL_FLUSH as MZ_PARTIAL_FLUSH; |
| pub use libz::Z_STREAM_END as MZ_STREAM_END; |
| pub use libz::Z_STREAM_ERROR as MZ_STREAM_ERROR; |
| pub use libz::Z_SYNC_FLUSH as MZ_SYNC_FLUSH; |
| pub type AllocSize = libz::uInt; |
| |
| pub const MZ_DEFAULT_WINDOW_BITS: c_int = 15; |
| |
| #[cfg(feature = "zlib-ng")] |
| const ZLIB_VERSION: &'static str = "2.1.0.devel\0"; |
| #[cfg(not(feature = "zlib-ng"))] |
| const ZLIB_VERSION: &'static str = "1.2.8\0"; |
| |
| pub unsafe extern "C" fn mz_deflateInit2( |
| stream: *mut mz_stream, |
| level: c_int, |
| method: c_int, |
| window_bits: c_int, |
| mem_level: c_int, |
| strategy: c_int, |
| ) -> c_int { |
| libz::deflateInit2_( |
| stream, |
| level, |
| method, |
| window_bits, |
| mem_level, |
| strategy, |
| ZLIB_VERSION.as_ptr() as *const c_char, |
| mem::size_of::<mz_stream>() as c_int, |
| ) |
| } |
| pub unsafe extern "C" fn mz_inflateInit2(stream: *mut mz_stream, window_bits: c_int) -> c_int { |
| libz::inflateInit2_( |
| stream, |
| window_bits, |
| ZLIB_VERSION.as_ptr() as *const c_char, |
| mem::size_of::<mz_stream>() as c_int, |
| ) |
| } |
| } |