diff --git a/src/decode.rs b/src/decode.rs index 1393d03..eb45668 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -6,10 +6,11 @@ //! [`Read`]: https://doc.rust-lang.org/stable/std/io/trait.Read.html //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html +use std::alloc::GlobalAlloc; +use std::error::Error; use std::ffi::CStr; use std::io::{BufRead, Read, Write}; use std::{fmt, io, ptr, slice}; -use std::error::Error; use brotlic_sys::*; @@ -20,9 +21,12 @@ use crate::{IntoInnerError, SetParameterError}; /// This decoder contains internal state of the decoding process. This low-level wrapper intended to /// be used for people who are familiar with the C API. For higher level abstractions, see /// [`DecompressorReader`] and [`DecompressorWriter`]. -#[derive(Debug)] pub struct BrotliDecoder { state: *mut BrotliDecoderState, + + // this field is read read across FFI boundaries + #[allow(dead_code)] + alloc: Option>>, } unsafe impl Send for BrotliDecoder {} @@ -34,21 +38,48 @@ impl BrotliDecoder { /// # Panics /// /// Panics if the decoder fails to be allocated or initialized + #[doc(alias = "BrotliDecoderCreateInstance")] pub fn new() -> Self { - unsafe { - let instance = BrotliDecoderCreateInstance(None, None, ptr::null_mut()); - - if !instance.is_null() { - BrotliDecoder { state: instance } - } else { - panic!( - "BrotliDecoderCreateInstance returned NULL: failed to allocate or initialize" - ); + let instance = unsafe { BrotliDecoderCreateInstance(None, None, ptr::null_mut()) }; + + if !instance.is_null() { + BrotliDecoder { + state: instance, + alloc: None, } + } else { + panic!("BrotliDecoderCreateInstance returned NULL: failed to allocate or initialize"); + } + } + + /// Constructs a new brotli decoder instance using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the decoder fails to be allocated or initialized + #[doc(alias = "BrotliDecoderCreateInstance")] + pub fn new_in(alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + let alloc: Box> = Box::new(Box::new(alloc)); + let alloc_ptr: *const Box = alloc.as_ref(); + let instance = unsafe { + BrotliDecoderCreateInstance(Some(crate::malloc), Some(crate::free), alloc_ptr as _) + }; + + if !instance.is_null() { + BrotliDecoder { + state: instance, + alloc: Some(alloc), + } + } else { + panic!("BrotliDecoderCreateInstance returned NULL: failed to allocate or initialize"); } } /// Checks if the decoder instance reached its final state. + #[doc(alias = "BrotliDecoderIsFinished")] pub fn is_finished(&self) -> bool { unsafe { BrotliDecoderIsFinished(self.state) != 0 } } @@ -66,6 +97,7 @@ impl BrotliDecoder { /// if `info` is [`DecoderInfo::NeedsMoreInput`], more input is required to continue decoding. /// Likewise, if `info` is [`DecoderInfo::NeedsMoreOutput`], more output is required to continue /// the decoding conversion. [`DecoderInfo::Finished`] indicates that the decoding has finished. + #[doc(alias = "BrotliDecoderDecompressStream")] pub fn decompress( &mut self, input: &[u8], @@ -118,6 +150,7 @@ impl BrotliDecoder { } /// Checks if the decoder has more output. + #[doc(alias = "BrotliDecoderHasMoreOutput")] pub fn has_output(&self) -> bool { unsafe { BrotliDecoderHasMoreOutput(self.state) != 0 } } @@ -131,6 +164,7 @@ impl BrotliDecoder { /// # Safety /// /// For every consecutive call of this function, the previous slice becomes invalidated. + #[doc(alias = "BrotliDecoderTakeOutput")] pub unsafe fn take_output(&mut self) -> Option<&[u8]> { if self.has_output() { let mut len: usize = 0; @@ -143,6 +177,7 @@ impl BrotliDecoder { } /// Returns the version of the C brotli decoder library. + #[doc(alias = "BrotliDecoderVersion")] pub fn version() -> u32 { unsafe { BrotliDecoderVersion() } } @@ -247,6 +282,14 @@ impl BrotliDecoder { } } +impl fmt::Debug for BrotliDecoder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BrotliDecoder") + .field("state", &self.state) + .finish_non_exhaustive() + } +} + impl Default for BrotliDecoder { fn default() -> Self { BrotliDecoder::new() @@ -322,6 +365,29 @@ impl BrotliDecoderOptions { pub fn build(&self) -> Result { let mut decoder = BrotliDecoder::new(); + self.configure(&mut decoder)?; + + Ok(decoder) + } + + /// Creates a brotli decoder with the specified settings using allocator `alloc`. + /// + /// # Errors + /// + /// If any of the preconditions of the parameters are violated, an error is returned. + #[doc(alias = "BrotliDecoderSetParameter")] + pub fn build_in(&self, alloc: A) -> Result + where + A: GlobalAlloc + 'static, + { + let mut decoder = BrotliDecoder::new_in(alloc); + + self.configure(&mut decoder)?; + + Ok(decoder) + } + + fn configure(&self, decoder: &mut BrotliDecoder) -> Result<(), SetParameterError> { if let Some(disable_ring_buffer_reallocation) = self.disable_ring_buffer_reallocation { let key = BrotliDecoderParameter_BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION; let value = disable_ring_buffer_reallocation as u32; @@ -336,7 +402,7 @@ impl BrotliDecoderOptions { decoder.set_param(key, value)?; } - Ok(decoder) + Ok(()) } } @@ -474,6 +540,21 @@ impl DecompressorReader { } } + /// Creates a new `DecompressorReader` with a newly created decoder using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the decoder fails to be allocated or initialized + pub fn new_in(inner: R, alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + DecompressorReader { + inner, + decoder: BrotliDecoder::new_in(alloc), + } + } + /// Creates a new `DecompressorReader` with a specified decoder. /// /// # Examples @@ -603,6 +684,22 @@ impl DecompressorWriter { } } + /// Creates a new `DecompressorWriter` with a newly created decoder using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the decoder fails to be allocated or initialized + pub fn new_in(inner: W, alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + DecompressorWriter { + inner, + decoder: BrotliDecoder::new_in(alloc), + panicked: false, + } + } + /// Creates a new `DecompressorWriter` with a specified decoder. /// /// # Examples diff --git a/src/encode.rs b/src/encode.rs index 4ea9969..33d1290 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -6,22 +6,26 @@ //! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html use crate::{ - BlockSize, CompressionMode, IntoInnerError, LargeWindowSize, SetParameterError, Quality, + BlockSize, CompressionMode, IntoInnerError, LargeWindowSize, Quality, SetParameterError, WindowSize, }; use brotlic_sys::*; +use std::alloc::GlobalAlloc; +use std::error::Error; use std::io::{BufRead, Read, Write}; use std::{fmt, io, mem, ptr, slice}; -use std::error::Error; /// A reference to a brotli encoder. /// /// This encoder contains internal state of the encoding process. This low-level wrapper intended to /// be used for people who are familiar with the C API. For higher level abstractions, see /// [`CompressorReader`] and [`CompressorWriter`]. -#[derive(Debug)] pub struct BrotliEncoder { state: *mut BrotliEncoderState, + + // this field is read read across FFI boundaries + #[allow(dead_code)] + alloc: Option>>, } unsafe impl Send for BrotliEncoder {} @@ -35,16 +39,41 @@ impl BrotliEncoder { /// Panics if the encoder fails to be allocated or initialized #[doc(alias = "BrotliEncoderCreateInstance")] pub fn new() -> Self { - unsafe { - let instance = BrotliEncoderCreateInstance(None, None, ptr::null_mut()); - - if !instance.is_null() { - BrotliEncoder { state: instance } - } else { - panic!( - "BrotliEncoderCreateInstance returned NULL: failed to allocate or initialize" - ); + let instance = unsafe { BrotliEncoderCreateInstance(None, None, ptr::null_mut()) }; + + if !instance.is_null() { + BrotliEncoder { + state: instance, + alloc: None, + } + } else { + panic!("BrotliEncoderCreateInstance returned NULL: failed to allocate or initialize"); + } + } + + /// Constructs a new brotli encoder instance using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the encoder fails to be allocated or initialized + #[doc(alias = "BrotliEncoderCreateInstance")] + pub fn new_in(alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + let alloc: Box> = Box::new(Box::new(alloc)); + let alloc_ptr: *const Box = alloc.as_ref(); + let instance = unsafe { + BrotliEncoderCreateInstance(Some(crate::malloc), Some(crate::free), alloc_ptr as _) + }; + + if !instance.is_null() { + BrotliEncoder { + state: instance, + alloc: Some(alloc), } + } else { + panic!("BrotliEncoderCreateInstance returned NULL: failed to allocate or initialize"); } } @@ -140,6 +169,7 @@ impl BrotliEncoder { } /// Checks if the encoder has more output. + #[doc(alias = "BrotliEncoderHasMoreOutput")] pub fn has_output(&self) -> bool { unsafe { BrotliEncoderHasMoreOutput(self.state) != 0 } } @@ -155,7 +185,6 @@ impl BrotliEncoder { /// # Safety /// /// For every consecutive call of this function, the previous slice becomes invalidated. - #[doc(alias = "BrotliEncoderHasMoreOutput")] #[doc(alias = "BrotliEncoderTakeOutput")] pub unsafe fn take_output(&mut self) -> Option<&[u8]> { if self.has_output() { @@ -169,6 +198,7 @@ impl BrotliEncoder { } /// Returns the version of the C brotli encoder library. + #[doc(alias = "BrotliEncoderVersion")] pub fn version() -> u32 { unsafe { BrotliEncoderVersion() } } @@ -193,6 +223,14 @@ impl BrotliEncoder { } } +impl fmt::Debug for BrotliEncoder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BrotliEncoder") + .field("state", &self.state) + .finish_non_exhaustive() + } +} + impl Default for BrotliEncoder { fn default() -> Self { BrotliEncoder::new() @@ -371,7 +409,7 @@ impl BrotliEncoderOptions { self } - /// Creates a brotli encoder using the specified settings. + /// Creates a brotli encoder with the specified settings using allocator `alloc`. /// /// # Errors /// @@ -380,6 +418,29 @@ impl BrotliEncoderOptions { pub fn build(&self) -> Result { let mut encoder = BrotliEncoder::new(); + self.configure(&mut encoder)?; + + Ok(encoder) + } + + /// Creates a brotli encoder using the specified settings. + /// + /// # Errors + /// + /// If any of the preconditions of the parameters are violated, an error is returned. + #[doc(alias = "BrotliEncoderSetParameter")] + pub fn build_in(&self, alloc: A) -> Result + where + A: GlobalAlloc + 'static, + { + let mut encoder = BrotliEncoder::new_in(alloc); + + self.configure(&mut encoder)?; + + Ok(encoder) + } + + fn configure(&self, encoder: &mut BrotliEncoder) -> Result<(), SetParameterError> { if let Some(mode) = self.mode { let key = BrotliEncoderParameter_BROTLI_PARAM_MODE; let value = mode as u32; @@ -466,7 +527,7 @@ impl BrotliEncoderOptions { encoder.set_param(key, value)?; } - Ok(encoder) + Ok(()) } } @@ -551,6 +612,22 @@ impl CompressorReader { } } + /// Creates a new `CompressorReader` with a newly created encoder using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the encoder fails to be allocated or initialized + pub fn new_in(inner: R, alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + CompressorReader { + inner, + encoder: BrotliEncoder::new_in(alloc), + op: BrotliOperation::Process, + } + } + /// Creates a new `CompressorReader` with a specified encoder. /// /// # Examples @@ -685,7 +762,7 @@ impl CompressorWriter { /// # Panics /// /// Panics if the encoder fails to be allocated or initialized - pub fn new(inner: W) -> CompressorWriter { + pub fn new(inner: W) -> Self { CompressorWriter { inner, encoder: BrotliEncoder::new(), @@ -693,6 +770,22 @@ impl CompressorWriter { } } + /// Creates a new `CompressorWriter` with a newly created encoder using allocator `alloc`. + /// + /// # Panics + /// + /// Panics if the encoder fails to be allocated or initialized + pub fn new_in(inner: W, alloc: A) -> Self + where + A: GlobalAlloc + 'static, + { + CompressorWriter { + inner, + encoder: BrotliEncoder::new_in(alloc), + panicked: false, + } + } + /// Creates a new `CompressorWriter` with a specified encoder. /// /// # Examples diff --git a/src/lib.rs b/src/lib.rs index e8cc926..2599269 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,8 +127,9 @@ pub use decode::DecompressorReader; pub use decode::DecompressorWriter; use brotlic_sys::*; -use std::os::raw::c_int; -use std::{fmt, io}; +use std::os::raw::{c_int, c_void}; +use std::{fmt, io, ptr}; +use std::alloc::{GlobalAlloc, Layout}; use std::error::Error; /// Quality level of the brotli compression @@ -1002,3 +1003,37 @@ impl fmt::Display for IntoInnerError { self.error().fmt(f) } } + +const MIN_ALIGN: usize = 16; + +extern "C" fn malloc(opaque: *mut c_void, size: usize) -> *mut c_void { + let global_alloc = opaque as *const Box; + + if size > usize::MAX - 2 * MIN_ALIGN + 1 { + return ptr::null_mut(); + } + + unsafe { + let layout = Layout::from_size_align_unchecked(size + MIN_ALIGN, MIN_ALIGN); + let alloc = (*global_alloc).alloc(layout); + (alloc as *mut usize).write(size); + + (alloc.add(MIN_ALIGN)) as _ + } +} + +extern "C" fn free(opaque: *mut c_void, address: *mut c_void) { + if address.is_null() { + return; + } + + let global_alloc = opaque as *const Box; + + unsafe { + let alloc = (address as *mut u8).sub(MIN_ALIGN); + let size = (alloc as *const usize).read(); + let layout = Layout::from_size_align_unchecked(size + MIN_ALIGN, MIN_ALIGN); + + (*global_alloc).dealloc(alloc, layout); + } +} \ No newline at end of file diff --git a/tests/stream_alloc.rs b/tests/stream_alloc.rs new file mode 100644 index 0000000..345d2c1 --- /dev/null +++ b/tests/stream_alloc.rs @@ -0,0 +1,129 @@ +use std::alloc::System; +use brotlic::{CompressorReader, CompressorWriter, DecompressorReader, DecompressorWriter}; +use std::io::{Read, Write}; + +mod common; + +fn write_comp_read_decomp_verify(input: &[u8]) { + let compressed = { + let mut compressor = CompressorWriter::new_in(Vec::new(), System); + compressor.write_all(input).unwrap(); + compressor.into_inner().unwrap() + }; + + let decompressed = { + let mut decompressor = DecompressorReader::new_in(compressed.as_slice(), System); + let mut decompressed = Vec::new(); + decompressor.read_to_end(&mut decompressed).unwrap(); + decompressed + }; + + assert_eq!(input, decompressed); +} + +fn read_comp_write_decomp_verify(input: &[u8]) { + let compressed = { + let mut compressor = CompressorReader::new_in(input, System); + let mut compressed = Vec::new(); + compressor.read_to_end(&mut compressed).unwrap(); + compressed + }; + + let decompressed = { + let mut decompressor = DecompressorWriter::new_in(Vec::new(), System); + decompressor.write_all(compressed.as_slice()).unwrap(); + decompressor.into_inner().unwrap() + }; + + assert_eq!(input, decompressed); +} + +#[test] +fn test_write_comp_min_entropy_small_alloc() { + write_comp_read_decomp_verify(common::gen_min_entropy(32).as_slice()); +} + +#[test] +fn test_write_comp_medium_entropy_small_alloc() { + write_comp_read_decomp_verify(common::gen_medium_entropy(32).as_slice()); +} + +#[test] +fn test_write_comp_max_entropy_small_alloc() { + write_comp_read_decomp_verify(common::gen_max_entropy(32).as_slice()); +} + +#[test] +fn test_write_comp_min_entropy_medium_alloc() { + write_comp_read_decomp_verify(common::gen_min_entropy(512).as_slice()); +} + +#[test] +fn test_write_comp_medium_entropy_medium_alloc() { + write_comp_read_decomp_verify(common::gen_medium_entropy(512).as_slice()); +} + +#[test] +fn test_write_comp_max_entropy_medium_alloc() { + write_comp_read_decomp_verify(common::gen_max_entropy(512).as_slice()); +} + +#[test] +fn test_write_comp_min_entropy_large_alloc() { + write_comp_read_decomp_verify(common::gen_min_entropy(8192).as_slice()); +} + +#[test] +fn test_write_comp_medium_entropy_large_alloc() { + write_comp_read_decomp_verify(common::gen_medium_entropy(8192).as_slice()); +} + +#[test] +fn test_write_comp_max_entropy_large_alloc() { + write_comp_read_decomp_verify(common::gen_max_entropy(8192).as_slice()); +} + +#[test] +fn test_read_comp_min_entropy_small_alloc() { + read_comp_write_decomp_verify(common::gen_min_entropy(32).as_slice()); +} + +#[test] +fn test_read_comp_medium_entropy_small_alloc() { + read_comp_write_decomp_verify(common::gen_medium_entropy(32).as_slice()); +} + +#[test] +fn test_read_comp_max_entropy_small_alloc() { + read_comp_write_decomp_verify(common::gen_max_entropy(32).as_slice()); +} + +#[test] +fn test_read_comp_min_entropy_medium_alloc() { + read_comp_write_decomp_verify(common::gen_min_entropy(512).as_slice()); +} + +#[test] +fn test_read_comp_medium_entropy_medium_alloc() { + read_comp_write_decomp_verify(common::gen_medium_entropy(512).as_slice()); +} + +#[test] +fn test_read_comp_max_entropy_medium_alloc() { + read_comp_write_decomp_verify(common::gen_max_entropy(512).as_slice()); +} + +#[test] +fn test_read_comp_min_entropy_large_alloc() { + read_comp_write_decomp_verify(common::gen_min_entropy(8192).as_slice()); +} + +#[test] +fn test_read_comp_medium_entropy_large_alloc() { + read_comp_write_decomp_verify(common::gen_medium_entropy(8192).as_slice()); +} + +#[test] +fn test_read_comp_max_entropy_large_alloc() { + read_comp_write_decomp_verify(common::gen_max_entropy(8192).as_slice()); +}