shared-brotli-patch-decoder 0.1.2

Wrapper around brotli-sys which allows for decoding shared brotli (https://datatracker.ietf.org/doc/draft-vandevenne-shared-brotli-format/) encoded patch data.
Documentation
use crate::decode_error::DecodeError;
use crate::sys::{
    BrotliDecoderAttachDictionary, BrotliDecoderCreateInstance, BrotliDecoderDecompressStream,
    BrotliDecoderDestroyInstance, BrotliDecoderResult_BROTLI_DECODER_RESULT_ERROR,
    BrotliDecoderResult_BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT,
    BrotliDecoderResult_BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT,
    BrotliDecoderResult_BROTLI_DECODER_RESULT_SUCCESS,
    BrotliSharedDictionaryType_BROTLI_SHARED_DICTIONARY_RAW, BROTLI_FALSE,
};
use core::ptr;

pub fn shared_brotli_decode_c(
    encoded: &[u8],
    shared_dictionary: Option<&[u8]>,
    max_uncompressed_length: usize,
) -> Result<Vec<u8>, DecodeError> {
    let decoder = unsafe { BrotliDecoderCreateInstance(None, None, ptr::null_mut()) };
    if decoder.is_null() {
        return Err(DecodeError::InitFailure);
    }

    if let Some(shared_dictionary) = shared_dictionary {
        if unsafe {
            BrotliDecoderAttachDictionary(
                decoder,
                BrotliSharedDictionaryType_BROTLI_SHARED_DICTIONARY_RAW,
                shared_dictionary.len(),
                shared_dictionary.as_ptr(),
            )
        } == BROTLI_FALSE
        {
            unsafe {
                BrotliDecoderDestroyInstance(decoder);
            }
            return Err(DecodeError::InvalidDictionary);
        }
    }

    let mut sink = vec![0u8; max_uncompressed_length];

    let mut next_in = encoded.as_ptr();
    let mut available_in = encoded.len();
    let mut next_out = sink.as_mut_ptr();
    let mut available_out = sink.len();
    let mut total_out = 0;

    let mut error: Option<DecodeError> = None;
    loop {
        let result = unsafe {
            BrotliDecoderDecompressStream(
                decoder,
                &mut available_in,
                &mut next_in,
                &mut available_out,
                &mut next_out,
                &mut total_out,
            )
        };

        #[allow(non_upper_case_globals)]
        match result {
            BrotliDecoderResult_BROTLI_DECODER_RESULT_SUCCESS => break,
            BrotliDecoderResult_BROTLI_DECODER_RESULT_ERROR => {
                error = Some(DecodeError::InvalidStream);
                break;
            }
            BrotliDecoderResult_BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT if available_in == 0 => {
                // Needs more input and none is available.
                error = Some(DecodeError::InvalidStream);
                break;
            }
            BrotliDecoderResult_BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT if available_out == 0 => {
                // Needs more output space, but none is available.
                error = Some(DecodeError::MaxSizeExceeded);
                break;
            }
            _ => continue,
        }
    }

    unsafe {
        BrotliDecoderDestroyInstance(decoder);
    }
    if let Some(error) = error {
        return Err(error);
    }

    if available_in > 0 {
        // There's is data left in the input stream, which is not allowed
        return Err(DecodeError::ExcessInputData);
    }

    if total_out > sink.len() {
        return Err(DecodeError::MaxSizeExceeded);
    }

    sink.resize(total_out, 0);

    Ok(sink)
}