use google_brotli_ffi as ffi;
pub struct Encoder<'a>(*mut ffi::BrotliEncoderState, Vec<Attached<'a>>);
struct Attached<'a> {
_source: &'a [u8],
prepared: *mut ffi::BrotliEncoderPreparedDictionary,
}
impl Drop for Attached<'_> {
fn drop(&mut self) {
unsafe {
ffi::BrotliEncoderDestroyPreparedDictionary(self.prepared);
}
}
}
impl<'a> Encoder<'a> {
pub fn new(parameters: &[(ffi::BrotliEncoderParameter, u32)]) -> Self {
let encoder = Self(
unsafe { ffi::BrotliEncoderCreateInstance(None, None, std::ptr::null_mut()) },
Vec::new(),
);
assert!(!encoder.0.is_null());
for &(parameter, value) in parameters {
assert_eq!(
unsafe { ffi::BrotliEncoderSetParameter(encoder.0, parameter, value) },
ffi::BROTLI_TRUE,
"generator rejection: {parameter:?}={value}"
);
}
encoder
}
pub fn attach(&mut self, prefix: &'a [u8]) {
self.attach_type(prefix, ffi::BROTLI_SHARED_DICTIONARY_RAW);
}
#[cfg(feature = "experimental")]
pub fn attach_serialized(&mut self, bytes: &'a [u8]) {
self.attach_type(bytes, ffi::BROTLI_SHARED_DICTIONARY_SERIALIZED);
}
fn attach_type(&mut self, prefix: &'a [u8], kind: ffi::BrotliSharedDictionaryType) {
unsafe {
let prepared = ffi::BrotliEncoderPrepareDictionary(
kind,
prefix.len(),
prefix.as_ptr(),
11,
None,
None,
std::ptr::null_mut(),
);
assert!(!prepared.is_null());
let attachment = Attached {
_source: prefix,
prepared,
};
assert_eq!(
ffi::BrotliEncoderAttachPreparedDictionary(self.0, prepared),
ffi::BROTLI_TRUE
);
self.1.push(attachment);
}
}
pub fn push(
&mut self,
input: &[u8],
operation: ffi::BrotliEncoderOperation,
chunk: usize,
take: bool,
) -> Vec<u8> {
let mut result = Vec::new();
let mut remaining = input.len();
let mut cursor = input.as_ptr();
loop {
let mut buffer = vec![0; chunk];
let mut available = if take { 0 } else { buffer.len() };
let mut output = buffer.as_mut_ptr();
unsafe {
assert_eq!(
ffi::BrotliEncoderCompressStream(
self.0,
operation,
&raw mut remaining,
&raw mut cursor,
&raw mut available,
&raw mut output,
std::ptr::null_mut()
),
ffi::BROTLI_TRUE
);
if take {
let mut length = chunk;
let bytes = ffi::BrotliEncoderTakeOutput(self.0, &raw mut length);
if length != 0 {
result.extend_from_slice(std::slice::from_raw_parts(bytes, length));
}
} else {
result.extend_from_slice(&buffer[..buffer.len() - available]);
}
if remaining == 0 && ffi::BrotliEncoderHasMoreOutput(self.0) == ffi::BROTLI_FALSE {
if operation == ffi::BROTLI_OPERATION_FINISH
&& ffi::BrotliEncoderIsFinished(self.0) != ffi::BROTLI_TRUE
{
continue;
}
break;
}
}
}
result
}
}
impl Drop for Encoder<'_> {
fn drop(&mut self) {
unsafe {
ffi::BrotliEncoderDestroyInstance(self.0);
}
}
}