diff --git a/miniz_oxide/src/inflate/core.rs b/miniz_oxide/src/inflate/core.rs index 93ba3663..0fe06347 100644 --- a/miniz_oxide/src/inflate/core.rs +++ b/miniz_oxide/src/inflate/core.rs @@ -205,7 +205,10 @@ impl DecompressorOxide { /// Create a new decompressor with only the state field initialized. /// /// This is how it's created in miniz. Unsafe due to uninitialized values. + /// Usage is not recommended. #[inline] + #[deprecated(since = "0.2.2", + note="Will be removed as the safety of using this is hard to verify.")] pub unsafe fn with_init_state_only() -> DecompressorOxide { let mut decomp: DecompressorOxide = mem::uninitialized(); decomp.state = core::State::Start; @@ -1785,7 +1788,7 @@ mod test { } fn check_result(input: &[u8], expected_status: TINFLStatus, expected_state: State, zlib: bool) { - let mut r = unsafe { DecompressorOxide::with_init_state_only() }; + let mut r = DecompressorOxide::default(); let mut output_buf = vec![0; 1024 * 32]; let mut out_cursor = Cursor::new(output_buf.as_mut_slice()); let flags = if zlib { diff --git a/src/lib.rs b/src/lib.rs index 018aed02..8456da1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ mod libc { use std::alloc::{alloc as rust_alloc, realloc as rust_realloc, dealloc as rust_dealloc, Layout}; use std::mem; - use std::ptr::NonNull; pub type c_void = u8; pub type c_int = i32; @@ -146,10 +145,16 @@ macro_rules! oxidize { // Make sure we catch a potential panic, as // this is called from C. match catch_unwind(AssertUnwindSafe(|| { - let mut stream_oxide = StreamOxide::new(&mut *stream); - let status = $mz_func_oxide(&mut stream_oxide, $($arg_name),*); - *stream = stream_oxide.into_mz_stream(); - as_c_return_code(status) + // Do some checks to see if the stream object has the right type. + match StreamOxide::try_new(stream) { + Ok(mut stream_oxide) => { + let status = $mz_func_oxide(&mut stream_oxide, $($arg_name),*); + *stream = stream_oxide.into_mz_stream(); + as_c_return_code(status) } + Err(e) => { + e as c_int + } + } })) { Ok(res) => res, Err(_) => { @@ -162,16 +167,12 @@ macro_rules! oxidize { }; } -oxidize!(mz_deflateInit2, mz_deflate_init2_oxide; - level: c_int, method: c_int, window_bits: c_int, mem_level: c_int, strategy: c_int); oxidize!(mz_deflate, mz_deflate_oxide; flush: c_int); oxidize!(mz_deflateEnd, mz_deflate_end_oxide;); oxidize!(mz_deflateReset, mz_deflate_reset_oxide;); -oxidize!(mz_inflateInit2, mz_inflate_init2_oxide; - window_bits: c_int); oxidize!(mz_inflate, mz_inflate_oxide; flush: c_int); oxidize!(mz_inflateEnd, mz_inflate_end_oxide;); @@ -188,6 +189,64 @@ pub unsafe extern "C" fn mz_deflateInit(stream: *mut mz_stream, level: c_int) -> ) } +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 { + match stream.as_mut() { + None => MZError::Stream as c_int, + Some(stream) => { + stream.data_type = StateTypeEnum::Deflate; + // Make sure we catch a potential panic, as + // this is called from C. + match catch_unwind(AssertUnwindSafe(|| { + match StreamOxide::try_new(stream) { + Ok(mut stream_oxide) => { + let status = mz_deflate_init2_oxide(&mut stream_oxide,level, method, + window_bits, mem_level, strategy); + *stream = stream_oxide.into_mz_stream(); + as_c_return_code(status) } + Err(e) => { + e as c_int + } + } + })) { + Ok(res) => res, + Err(_) => { + println!("FATAL ERROR: Caught panic!"); + MZError::Stream as c_int}, + } + } + } +} + +pub unsafe extern "C" fn mz_inflateInit2(stream: *mut mz_stream, window_bits: c_int) + -> c_int { + match stream.as_mut() { + None => MZError::Stream as c_int, + Some(stream) => { + stream.data_type = StateTypeEnum::Inflate; + // Make sure we catch a potential panic, as + // this is called from C. + match catch_unwind(AssertUnwindSafe(|| { + match StreamOxide::try_new(stream) { + Ok(mut stream_oxide) => { + let status = mz_inflate_init2_oxide(&mut stream_oxide,window_bits); + *stream = stream_oxide.into_mz_stream(); + as_c_return_code(status) } + Err(e) => { + e as c_int + } + } + })) { + Ok(res) => res, + Err(_) => { + println!("FATAL ERROR: Caught panic!"); + MZError::Stream as c_int}, + } + } + } +} + pub unsafe extern "C" fn mz_compress( dest: *mut u8, dest_len: *mut c_ulong, @@ -222,6 +281,7 @@ pub unsafe extern "C" fn mz_compress2( avail_in: source_len as c_uint, next_out: dest, avail_out: (*dest_len) as c_uint, + data_type: StateTypeEnum::Deflate, ..Default::default() }; @@ -260,9 +320,11 @@ pub unsafe extern "C" fn mz_uncompress( avail_in: source_len as c_uint, next_out: dest, avail_out: (*dest_len) as c_uint, + data_type: StateTypeEnum::Inflate, ..Default::default() }; + // We don't expect this to fail since we supply the stream ourselves. let mut stream_oxide = StreamOxide::new(&mut stream); as_c_return_code(mz_uncompress2_oxide(&mut stream_oxide, dest_len)) }, diff --git a/src/lib_oxide.rs b/src/lib_oxide.rs index 18d4741c..065ea8f8 100644 --- a/src/lib_oxide.rs +++ b/src/lib_oxide.rs @@ -2,6 +2,7 @@ use std::{cmp, mem, ptr, slice, usize}; use std::io::Cursor; +use std::alloc::{alloc_zeroed, dealloc, Layout}; use libc::{self, c_char, c_int, c_uint, c_ulong, c_void, size_t}; @@ -87,7 +88,7 @@ pub struct mz_stream { pub opaque: *mut c_void, // TODO: Not sure - pub data_type: c_int, + pub data_type: StateTypeEnum, /// Adler32 checksum of the data that has been compressed or uncompressed. pub adler: c_uint, /// Reserved @@ -112,7 +113,7 @@ impl Default for mz_stream { zfree: None, opaque: ptr::null_mut(), - data_type: 0, + data_type: StateTypeEnum::None, adler: 0, reserved: 0, } @@ -136,25 +137,38 @@ pub unsafe extern "C" fn def_free_func(_opaque: *mut c_void, address: *mut c_voi libc::free(address) } +/// Enum to keep track of what type the internal state is when moving over the C API boundary. +#[repr(C)] +#[derive(Debug,Copy,Clone,PartialEq)] +pub enum StateTypeEnum { + None = 0, + Inflate, + Deflate, +} + /// Trait used for states that can be carried by BoxedState. pub trait StateType { fn drop_state(&mut self); + const STATE_TYPE: StateTypeEnum; } impl StateType for tdefl_compressor { + const STATE_TYPE: StateTypeEnum = StateTypeEnum::Deflate; fn drop_state(&mut self) { self.drop_inner(); } } impl StateType for inflate_state { + const STATE_TYPE: StateTypeEnum = StateTypeEnum::Inflate; fn drop_state(&mut self) {} } /// Wrapper for a heap-allocated compressor/decompressor that frees the stucture on drop. struct BoxedState { inner: *mut ST, - alloc: mz_alloc_func, - free: mz_free_func, + alloc: Option, + free: Option, opaque: *mut c_void, + layout: Layout, } impl Drop for BoxedState { @@ -171,9 +185,10 @@ impl BoxedState { pub fn new(stream: &mut mz_stream) -> Self { BoxedState { inner: stream.state as *mut ST, - alloc: stream.zalloc.unwrap_or(def_alloc_func), - free: stream.zfree.unwrap_or(def_free_func), + alloc: stream.zalloc, + free: stream.zfree, opaque: stream.opaque, + layout: Layout::new::(), } } @@ -188,7 +203,10 @@ impl BoxedState { return Err(MZError::Param); } - self.inner = unsafe { (self.alloc)(self.opaque, 1, mem::size_of::()) as *mut ST }; + // We use the default rust allocator for now. To use a custom allocator we could need to + // add support for the internal allocations using the same one. + self.inner = unsafe { alloc_zeroed(self.layout) as *mut ST }; + if self.inner.is_null() { Err(MZError::Mem) } else { @@ -200,7 +218,7 @@ impl BoxedState { if !self.inner.is_null() { unsafe { self.inner.as_mut().map(|i| i.drop_state()); - (self.free)(self.opaque, self.inner as *mut c_void) + dealloc(self.inner as *mut u8, self.layout); } self.inner = ptr::null_mut(); } @@ -221,7 +239,30 @@ pub struct StreamOxide<'io, ST: StateType> { impl<'io, ST: StateType> StreamOxide<'io, ST> { + /// Create a new StreamOxide wrapper from a [mz_stream] object. + /// Custom allocation functions are not supported, supplying an mz_stream with allocation + /// function will cause creation to fail. + /// + /// Unsafe as the mz_stream object is not guaranteed to be valid. It is up to the + /// caller to ensure it is. pub unsafe fn new(stream: &mut mz_stream) -> Self { + Self::try_new(stream) + .expect("Failed to create StreamOxide, wrong state type or tried to specify allocators.") + } + + /// Try to create a new StreamOxide wrapper from a [mz_stream] object. + /// Custom allocation functions are not supported, supplying an mz_stream with allocation + /// functions will cause creation to fail. + /// + /// Unsafe as the mz_stream object is not guaranteed to be valid. It is up to the + /// caller to ensure it is. + pub unsafe fn try_new(stream: &mut mz_stream) -> Result { + // Make sure we don't make an inflate stream from a deflate stream and vice versa. + if stream.data_type != ST::STATE_TYPE + || stream.zalloc.is_some() || stream.zfree.is_some() { + return Err(MZError::Param); + } + let in_slice = stream.next_in.as_ref().map(|ptr| { slice::from_raw_parts(ptr, stream.avail_in as usize) }); @@ -230,14 +271,14 @@ impl<'io, ST: StateType> StreamOxide<'io, ST> { slice::from_raw_parts_mut(ptr, stream.avail_out as usize) }); - StreamOxide { + Ok(StreamOxide { next_in: in_slice, total_in: stream.total_in, next_out: out_slice, total_out: stream.total_out, state: BoxedState::new(stream), adler: stream.adler, - } + }) } pub fn into_mz_stream(mut self) -> mz_stream { @@ -261,12 +302,12 @@ impl<'io, ST: StateType> StreamOxide<'io, ST> { msg: ptr::null(), - zalloc: Some(self.state.alloc), - zfree: Some(self.state.free), + zalloc: self.state.alloc, + zfree: self.state.free, opaque: self.state.opaque, state: self.state.forget() as *mut mz_internal_state, - data_type: 0, + data_type: ST::STATE_TYPE, adler: self.adler, reserved: 0, } diff --git a/src/tinfl.rs b/src/tinfl.rs old mode 100644 new mode 100755 index db0e1dc9..8af0f08a --- a/src/tinfl.rs +++ b/src/tinfl.rs @@ -77,7 +77,7 @@ pub unsafe extern "C" fn tinfl_decompress_mem_to_heap( // We're not using a Vec for the buffer here to make sure the buffer is allocated and freed by // the same allocator. - let mut decomp = tinfl_decompressor::with_init_state_only(); + let mut decomp = tinfl_decompressor::default(); // Pointer to the buffer to place the decompressed data into. let mut p_buf: *mut c_void = ::miniz_def_alloc_func(ptr::null_mut(), MIN_BUFFER_CAPACITY, 1); // Capacity of the current output buffer. diff --git a/tests/test.rs b/tests/test.rs index 3cf41292..0b7eb54b 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,4 +1,5 @@ extern crate miniz_oxide; +extern crate miniz_oxide_c_api; use std::io::Read; @@ -49,3 +50,58 @@ fn roundtrip_level_1() { let dec = decompress_to_vec(enc.as_slice()).unwrap(); assert!(data == dec); } + +#[test] +fn c_api() { + use miniz_oxide_c_api::lib_oxide::mz_stream; + use miniz_oxide_c_api::{mz_deflateInit, mz_deflate, mz_deflateEnd, + mz_inflateInit, mz_inflate, mz_inflateEnd}; + use miniz_oxide::{MZStatus, MZError}; + let mut data = get_test_data(); + let mut compressed = vec![0; data.len() + 50]; + let compressed_size; + let decompressed_size; + unsafe { + let mut stream = mz_stream { + next_in: data.as_mut_ptr(), + avail_in: data.len() as u32, + next_out: compressed.as_mut_ptr(), + avail_out: compressed.len() as u32, + ..Default::default() + }; + + assert_eq!(mz_deflateInit(&mut stream, 1), MZStatus::Ok as i32); + assert_eq!(mz_deflate(&mut stream, 4), MZStatus::StreamEnd as i32); + assert_eq!(mz_deflateEnd(&mut stream), MZStatus::Ok as i32); + compressed_size = stream.total_out; + + assert_eq!(mz_inflate(&mut stream, 4), MZError::Param as i32); + assert_eq!(mz_inflateEnd(&mut stream), MZError::Param as i32); + } + + assert!(compressed_size as usize <= compressed.len()); + + let mut decompressed = vec![0;data.len()]; + + unsafe { + let mut stream = mz_stream { + next_in: compressed.as_mut_ptr(), + avail_in: compressed_size as u32, + next_out: decompressed.as_mut_ptr(), + avail_out: decompressed.len() as u32, + ..Default::default() + }; + + assert_eq!(mz_inflateInit(&mut stream),MZStatus::Ok as i32); + assert_eq!(mz_inflate(&mut stream, 4),MZStatus::StreamEnd as i32); + assert_eq!(mz_inflateEnd(&mut stream),MZStatus::Ok as i32); + + decompressed_size = stream.total_out; + + // This should fail as the stream is an inflate stream! + assert_eq!(mz_deflate(&mut stream, 4), MZError::Param as i32); + assert_eq!(mz_deflateEnd(&mut stream), MZError::Param as i32); + } + + assert_eq!(data[..], decompressed[0..decompressed_size as usize]); +}