diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..d1afaf6 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,7 @@ +coverage: + status: + project: + default: + target: auto # Use previous coverage as the baseline + threshold: 100 # Allow a 100% drop without failing + informational: true # Do not cause CI to fail \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..74a3549 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# MaxZip + +C++ compression framework. + +## About + +This project is intended to provide an abstract C++ frontend for various +compression libraries. Each implementation is exposed through a common +API, along with configuration parameters. + +## Usage + +The API is designed to provide common interfaces for compressing and +decompressing data. + +### Block API + +The block API provides the `compressor` and `decompressor` interfaces, +which can be used for one shot processing. This is a clean and simple +API, best suited for small to medium payloads where memory usage and +IO complexity are not a major concern. + +### Streaming API + +The streaming API provides the `encoder` and `decoder` interfaces, to +allow for incremental data processing. This is preferable when input +is either too large, or the underlying IO is segmented. \ No newline at end of file diff --git a/include/maxzip.hpp b/include/maxzip.hpp index 45687d9..d21eb31 100644 --- a/include/maxzip.hpp +++ b/include/maxzip.hpp @@ -31,7 +31,6 @@ #include #include -#include -#include +#include #endif \ No newline at end of file diff --git a/include/maxzip/common.hpp b/include/maxzip/common.hpp index f201550..bf59cbd 100644 --- a/include/maxzip/common.hpp +++ b/include/maxzip/common.hpp @@ -25,6 +25,12 @@ #include #include +#include #include +namespace maxzip +{ + +} + #endif \ No newline at end of file diff --git a/include/maxzip/compressor.hpp b/include/maxzip/compressor.hpp index c06bc03..5cc5fee 100644 --- a/include/maxzip/compressor.hpp +++ b/include/maxzip/compressor.hpp @@ -29,16 +29,16 @@ namespace maxzip { /** * @class compressor - * @brief Abstract base class for block compression + * @brief Abstract base class for block compression. */ class compressor { public: /** - * @brief Compress a block of data - * @param input Pointer to the input data - * @param input_size Size of the input data in bytes - * @param output Pointer to the output buffer (can be nullptr) + * @brief Compress a block of data. + * @param input Pointer to the input data. + * @param input_size Size of the input data in bytes. + * @param output Pointer to the output buffer (can be nullptr). * @param output_size Reference to the size of the output buffer on input. If * output is nullptr, this will be set to the maximum compressed size. * @return The size of the compressed data in bytes, or 0 if output is nullptr. diff --git a/include/maxzip/decoder.hpp b/include/maxzip/decoder.hpp index 1c7b49a..3d5f598 100644 --- a/include/maxzip/decoder.hpp +++ b/include/maxzip/decoder.hpp @@ -35,15 +35,30 @@ namespace maxzip class decoder { public: - void init(); - void update( - const uint8_t* input, + /** + * @brief Initialize the decoder. + * @param flush if set, may flush incomplete frames. This is typically ignored, + * with the exception of zlib. + */ + virtual void init(bool flush = false) = 0; + + /** + * @brief Decompress data stream. + * + * @param input_func Function to retrieve input buffer. + * @param output_func Function to retrieve output buffer. + * @param notify_func Function to notify actual output size. + * @param flush Whether to flush incomplete frames. This is typically + * ignored, with the exception of zlib. + */ + virtual bool decode( + const uint8_t *input, size_t input_size, - uint8_t* output, - size_t& output_size); - void finish( - uint8_t* output, - size_t& output_size); + size_t &read_size, + uint8_t *output, + size_t output_size, + size_t &write_size + ) = 0; }; } diff --git a/include/maxzip/encoder.hpp b/include/maxzip/encoder.hpp index b7c1466..1455f19 100644 --- a/include/maxzip/encoder.hpp +++ b/include/maxzip/encoder.hpp @@ -35,16 +35,31 @@ namespace maxzip class encoder { public: - void init(); - void update( - const uint8_t* input, - size_t input_size, - uint8_t* output, - size_t& output_size); - void finish( - uint8_t* output, - size_t& output_size); - }; + /** + * @brief Initialize the encoder state. + */ + virtual void init(bool flush = false) = 0; + + /** + * @brief Advance encoder state. + * @param input Pointer to input data. If no input is available, this should be nullptr. + * @param input_size Size of input data. If no input is available, this should be 0. + * @param read_size Size of input data processed. + * @param output Pointer to output buffer. + * @param output_size Size of output buffer. + * @param write_size Size of output data written. + * @return true if still processing, false if compression is complete. + */ + virtual bool encode( + const uint8_t *input, + size_t input_size, + size_t &read_size, + uint8_t *output, + size_t output_size, + size_t &write_size) = 0; +}; + + } #endif \ No newline at end of file diff --git a/include/maxzip/stream.hpp b/include/maxzip/stream.hpp new file mode 100644 index 0000000..4b48fde --- /dev/null +++ b/include/maxzip/stream.hpp @@ -0,0 +1,56 @@ +#ifndef MAXZIP_STREAM_HPP +#define MAXZIP_STREAM_HPP + +#include "common.hpp" + +namespace maxzip +{ + class stream + { + public: + virtual void initialize( + bool flush = false) = 0; + + virtual std::pair update( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t output_size) = 0; + + virtual bool finalize( + uint8_t *output, + size_t output_size, + size_t &write_size) = 0; + + virtual std::pair block_sizes() const = 0; + }; + + struct brotli_encoder_params + { + std::optional mode; + std::optional quality; + std::optional window_size; + std::optional block_size; + std::optional literal_context_modeling; + std::optional size_hint; + std::optional large_window; + std::optional postfix_bits; + std::optional num_direct_distance_codes; + std::optional stream_offset; + }; + + struct brotli_decoder_params + { + std::optional disable_ring_buffer_reallocation; + std::optional large_window; + }; + + stream *create_brotli_encoder( + const brotli_encoder_params ¶ms); + + stream *create_brotli_decoder( + const brotli_decoder_params ¶ms); + +} + +#endif \ No newline at end of file diff --git a/src/brotli.cpp b/src/brotli.cpp index 28e3bb5..1ffa416 100644 --- a/src/brotli.cpp +++ b/src/brotli.cpp @@ -24,7 +24,7 @@ namespace maxzip { - class brotli_compressor : public compressor + class brotli_compressor : public basic_compressor { public: brotli_compressor(int quality, int window_size, int mode) : _quality(quality), _window_size(window_size), _mode(static_cast(mode)) @@ -51,35 +51,36 @@ namespace maxzip } } - size_t compress( + protected: + size_t compress_data( const uint8_t *input, size_t input_size, uint8_t *output, - size_t &output_size) override + size_t output_size) override { - size_t compressed_size(0); - if (output != nullptr) - { - compressed_size = output_size; - if (BrotliEncoderCompress( - _quality, - _window_size, - _mode, - input_size, - input, - &compressed_size, - output) != BROTLI_TRUE) - { - throw std::runtime_error("Insufficient output buffer size."); - } - } - else + size_t compressed_size(output_size); + const int rc = BrotliEncoderCompress( + _quality, + _window_size, + _mode, + input_size, + input, + &compressed_size, + output); + if (rc != BROTLI_TRUE) { - output_size = BrotliEncoderMaxCompressedSize(input_size); + throw std::runtime_error("Insufficient output buffer size."); } return compressed_size; } + size_t compress_bound( + const uint8_t * /*unused*/, + size_t input_size) override + { + return BrotliEncoderMaxCompressedSize(input_size); + } + private: int _quality; int _window_size; @@ -99,7 +100,10 @@ namespace maxzip size_t decompressed_size = output_size; const BrotliDecoderResult result = BrotliDecoderDecompress( - input_size, input, &decompressed_size, output); + input_size, + input, + &decompressed_size, + output); if (result != BROTLI_DECODER_RESULT_SUCCESS) { throw std::runtime_error("Decompression failed."); @@ -109,9 +113,321 @@ namespace maxzip } }; + template < + typename ContextType, + typename ConstructorType, + ConstructorType Constructor, + typename DestructorType, + DestructorType Destructor> + class stream_handle + { + public: + using context_type = ContextType; + stream_handle() : _ctx(nullptr, Destructor) {} + ~stream_handle() = default; + void create() + { + _ctx.reset(Constructor(nullptr, nullptr, nullptr)); + } + ContextType *get() const + { + return _ctx.get(); + } + void reset() + { + _ctx.reset(); + } + + private: + std::unique_ptr _ctx; + }; + + template + class stream_config + { + public: + stream_config() = default; + + template + void configure(ContextType *ctx) + { + for (const auto &[param, value] : _params) + { + if (value.has_value()) + { + set_parameter(ctx, param, value.value()); + } + } + + for (const auto &[param, value] : _flags) + { + if (value.has_value()) + { + set_flag(ctx, param, value.value()); + } + } + } + + std::unordered_map> _params; + std::unordered_map> _flags; + + private: + template + void set_parameter( + ContextType *ctx, + ParameterType param, + int value) + { + const int rc = SetParameter(ctx, param, value); + if (rc == BROTLI_FALSE) + { + throw std::invalid_argument("failed to set parameter " + std::to_string(param) + " to value " + std::to_string(value)); + } + } + + template + void set_flag( + ContextType *ctx, + ParameterType param, + bool value) + { + static_cast(SetParameter(ctx, param, value ? 1 : 0)); + } + }; + + template < + typename QueryFuncType, + QueryFuncType QueryFunc, + typename ProcessFuncType, + ProcessFuncType ProcessFunc, + typename... ArgTypes> + class stream_functions + { + public: + template + static void stream_process( + ContextType *ctx, + ArgTypes... args, + const uint8_t *input, + size_t input_size, + size_t *read_size, + uint8_t *output, + size_t output_size, + size_t *write_size) + { + size_t available_in = input_size; + size_t available_out = output_size; + const int rc = ProcessFunc(ctx, args..., &available_in, &input, &available_out, &output, nullptr); + if (rc != BROTLI_TRUE) + { + throw std::runtime_error("stream processing failed."); + } + if (read_size) + { + *read_size = input_size - available_in; + } + *write_size = output_size - available_out; + } + + template + static bool stream_query(ContextType *ctx) + { + bool result(false); + const int rc = QueryFunc(ctx); + if (rc == BROTLI_FALSE) + { + result = true; + } + return result; + } + }; + + using encoder_functions = stream_functions< + decltype(&BrotliEncoderIsFinished), + &BrotliEncoderIsFinished, + decltype(&BrotliEncoderCompressStream), + &BrotliEncoderCompressStream, + BrotliEncoderOperation>; + + using decoder_functions = stream_functions< + decltype(&BrotliDecoderIsFinished), + &BrotliDecoderIsFinished, + decltype(&BrotliDecoderDecompressStream), + &BrotliDecoderDecompressStream>; + + template + class brotli_stream : public basic_stream, public StreamFunctions + { + public: + void setup() override + { + _handle.create(); + _config.configure(_handle.get()); + } + + bool finish( + uint8_t *output, + size_t output_size, + size_t &write_size) override + { + finish_stream(output, output_size, write_size); + return StreamFunctions::stream_query(_handle.get()); + } + + protected: + virtual void finish_stream( + uint8_t *output, + size_t output_size, + size_t &write_size) = 0; + HandleType _handle; + ConfigType _config; + }; + + using encoder_handle = stream_handle< + BrotliEncoderState, + decltype(&BrotliEncoderCreateInstance), + &BrotliEncoderCreateInstance, + decltype(&BrotliEncoderDestroyInstance), + &BrotliEncoderDestroyInstance>; + + using encoder_config = stream_config< + BrotliEncoderParameter, + decltype(&BrotliEncoderSetParameter), + &BrotliEncoderSetParameter>; + + using decoder_handle = stream_handle< + BrotliDecoderState, + decltype(&BrotliDecoderCreateInstance), + &BrotliDecoderCreateInstance, + decltype(&BrotliDecoderDestroyInstance), + &BrotliDecoderDestroyInstance>; + + using decoder_config = stream_config< + BrotliDecoderParameter, + decltype(&BrotliDecoderSetParameter), + &BrotliDecoderSetParameter>; + + class brotli_encoder : public brotli_stream + { + public: + brotli_encoder(const brotli_encoder_params ¶ms) + { + _config._params[BROTLI_PARAM_MODE] = params.mode; + _config._params[BROTLI_PARAM_QUALITY] = params.quality; + _config._params[BROTLI_PARAM_LGWIN] = params.window_size; + _config._params[BROTLI_PARAM_LGBLOCK] = params.block_size; + _config._flags[BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING] = params.literal_context_modeling; + _config._params[BROTLI_PARAM_SIZE_HINT] = params.size_hint; + _config._flags[BROTLI_PARAM_LARGE_WINDOW] = params.large_window; + _config._params[BROTLI_PARAM_NPOSTFIX] = params.postfix_bits; + _config._params[BROTLI_PARAM_NDIRECT] = params.num_direct_distance_codes; + _config._params[BROTLI_PARAM_STREAM_OFFSET] = params.stream_offset; + _handle.create(); + _config.configure(_handle.get()); + _handle.reset(); + } + + protected: + void process( + const uint8_t *input, + size_t input_size, + size_t &read_size, + uint8_t *output, + size_t output_size, + size_t &write_size, + bool flush) override + { + const BrotliEncoderOperation op = flush ? BROTLI_OPERATION_FLUSH : BROTLI_OPERATION_PROCESS; + stream_process( + _handle.get(), + op, + input, + input_size, + &read_size, + output, + output_size, + &write_size); + } + + void finish_stream( + uint8_t *output, + size_t output_size, + size_t &write_size) override + { + stream_process( + _handle.get(), + BROTLI_OPERATION_FINISH, + nullptr, 0, nullptr, output, output_size, &write_size); + } + + size_t input_block_size() const override + { + return 16000; + } + + size_t output_block_size() const override + { + return 16000; + } + }; + + class brotli_decoder : public brotli_stream + { + public: + brotli_decoder(const brotli_decoder_params ¶ms) + { + _config._flags[BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION] = params.disable_ring_buffer_reallocation; + _config._flags[BROTLI_DECODER_PARAM_LARGE_WINDOW] = params.large_window; + _handle.create(); + _config.configure(_handle.get()); + _handle.reset(); + } + + protected: + void process( + const uint8_t *input, + size_t input_size, + size_t &read_size, + uint8_t *output, + size_t output_size, + size_t &write_size, + bool flush) override + { + stream_process( + _handle.get(), + input, + input_size, + &read_size, + output, + output_size, + &write_size); + } + + void finish_stream( + uint8_t *output, + size_t output_size, + size_t &write_size) override + { + stream_process( + _handle.get(), + nullptr, 0, nullptr, output, output_size, &write_size); + } + + size_t input_block_size() const override + { + return 16000; + } + + size_t output_block_size() const override + { + return 16000; + } + }; + compressor *create_brotli_compressor( const brotli_compressor_params ¶ms) { + std::unique_ptr compressor = std::make_unique( params.quality.value_or(BROTLI_DEFAULT_QUALITY), params.window_size.value_or(BROTLI_DEFAULT_WINDOW), @@ -120,8 +436,20 @@ namespace maxzip } decompressor *create_brotli_decompressor( - const brotli_decompressor_params ¶ms) + const brotli_decompressor_params & /* unused */) { return new brotli_decompressor(); } + + stream *create_brotli_encoder( + const brotli_encoder_params ¶ms) + { + return new brotli_encoder(params); + } + + stream *create_brotli_decoder( + const brotli_decoder_params ¶ms) + { + return new brotli_decoder(params); + } } \ No newline at end of file diff --git a/src/internal.hpp b/src/internal.hpp index 7ead873..353bce9 100644 --- a/src/internal.hpp +++ b/src/internal.hpp @@ -38,13 +38,84 @@ namespace maxzip { - template bool in_range(T value, T min, T max) { return (value >= min && value <= max); } + // DRY principle for compressor + class basic_compressor : public compressor + { + public: + virtual size_t compress( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t &output_size) override; + + protected: + virtual size_t compress_bound( + const uint8_t *input, + size_t input_size) = 0; + + virtual size_t compress_data( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t output_size) = 0; + }; + + // handle overlap in stream state management + class basic_stream : public stream + { + public: + virtual void initialize( + bool flush = false) override final; + + virtual std::pair update( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t output_size) override final; + + virtual bool finalize( + uint8_t *output, + size_t output_size, + size_t &write_size) override final; + + virtual std::pair block_sizes() const override final; + protected: + virtual void setup() = 0; + + virtual void process( + const uint8_t *input, + size_t input_size, + size_t &read_size, + uint8_t *output, + size_t output_size, + size_t &write_size, + bool flush) = 0; + + virtual bool finish( + uint8_t *output, + size_t output_size, + size_t &write_size) = 0; + + virtual size_t input_block_size() const = 0; + + virtual size_t output_block_size() const = 0; + private: + enum class state + { + CREATED, + PROCESSING, + FINALIZING, + FINALIZED + }; + state _state = state::CREATED; + bool _flush = false; + }; } #endif \ No newline at end of file diff --git a/src/maxzip.cpp b/src/maxzip.cpp index 9a668f9..d9b54f8 100644 --- a/src/maxzip.cpp +++ b/src/maxzip.cpp @@ -20,6 +20,92 @@ * SOFTWARE. */ - #include +#include - \ No newline at end of file +size_t maxzip::basic_compressor::compress( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t &output_size) +{ + size_t result(0); + if (output != nullptr) + { + result = compress_data(input, input_size, output, output_size); + } + else + { + output_size = compress_bound(input, input_size); + } + + return result; +} + +void maxzip::basic_stream::initialize( + bool flush) +{ + _flush = flush; + _state = state::PROCESSING; + setup(); +} + +std::pair maxzip::basic_stream::update( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t output_size) +{ + size_t read_size = 0; + size_t write_size = 0; + + // this should fail if we are not in PROCESSING state + if(_state != state::PROCESSING) + { + throw std::runtime_error("invalid stream state " + std::to_string(static_cast(_state))); + } + + // TODO: validate input and output + + process(input, input_size, read_size, output, output_size, write_size, _flush); + + return {read_size, write_size}; +} + +bool maxzip::basic_stream::finalize( + uint8_t *output, + size_t output_size, + size_t &write_size) +{ + bool finalizing(false); + + // nothing has been submitted, go to end + if(_state == state::CREATED) + { + _state = state::FINALIZED; + } + + // done processing, go to finalizing + if(_state == state::PROCESSING) + { + _state = state::FINALIZING; + } + + // try to finalize + if(_state == state::FINALIZING) + { + finalizing = finish(output, output_size, write_size); + } + + // if finalizing stage is done, go to end + if (!finalizing) + { + _state = state::FINALIZED; + } + + return finalizing; +} + +std::pair maxzip::basic_stream::block_sizes() const +{ + return {input_block_size(), output_block_size()}; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d59789e..9aabb64 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,4 +38,5 @@ endif() maxtest_add_test(unit brotli::block) maxtest_add_test(unit zlib::block) -maxtest_add_test(unit zstd::block) \ No newline at end of file +maxtest_add_test(unit zstd::block) +maxtest_add_test(unit brotli::stream) \ No newline at end of file diff --git a/tests/unit.cpp b/tests/unit.cpp index cdbb178..72c5eb9 100644 --- a/tests/unit.cpp +++ b/tests/unit.cpp @@ -22,15 +22,18 @@ #include #include + +#include #include +#include -template -static std::pair> try_create_compressor(CreateFunction create_func, ParamType &¶m) +template +static std::pair> try_create(CreateFunction create_func, Args &&...args) { - std::pair> result; + std::pair> result; try { - result.second = std::unique_ptr(create_func(std::forward(param))); + result.second = std::unique_ptr(create_func(std::forward(args)...)); result.first = true; } catch (...) @@ -41,37 +44,119 @@ static std::pair> try_create_compresso return result; } -template -static std::pair> try_create_decompressor(CreateFunction create_func, Args &&...args) +static bool try_func(const std::function &func) { - std::pair> result; + bool result; try { - result.second = std::unique_ptr(create_func(std::forward(args)...)); - result.first = true; + func(); + result = true; } catch (...) { - result.first = false; + result = false; } - return result; } -static bool try_func(const std::function &func) +class stream_processor { - bool result; - try +public: + stream_processor() { - func(); - result = true; + std::default_random_engine generator(std::random_device{}()); + std::sample( + characters.begin(), characters.end(), + std::back_inserter(_input), + input_size, + generator); } - catch (...) + + void test_encode_decode(const std::unique_ptr &encoder, + const std::unique_ptr &decoder, + bool flush) { - result = false; + std::istringstream input_stream(_input); + std::stringstream compressed_stream; + std::ostringstream output_stream; + + process_stream(encoder, input_stream, compressed_stream, flush); + compressed_stream.seekg(0); + process_stream(decoder, compressed_stream, output_stream, flush); + output_stream.seekp(0); + std::string output = output_stream.str(); + MAXTEST_ASSERT(std::equal( + _input.begin(), _input.end(), + output.begin(), output.end())); } - return result; -} + +private: + static void process_stream( + const std::unique_ptr &stream, + std::istream &input_stream, + std::ostream &output_stream, + bool flush) + { + const auto block_sizes = stream->block_sizes(); + std::vector input_buffer(block_sizes.first); + std::vector output_buffer(block_sizes.second / (flush ? 10 : 1)); + size_t available_input = 0; + bool finalizing(true); + size_t finalizing_write_size = 0; + + // finalize the stream before starting + MAXTEST_ASSERT(try_func([&]() { + stream->finalize(nullptr, 0, finalizing_write_size); + })); + MAXTEST_ASSERT(finalizing_write_size == 0); + + std::string input_data("this should trigger an error"); + MAXTEST_ASSERT(!try_func([&]() { + stream->update( + reinterpret_cast(input_data.data()), + input_data.size(), + output_buffer.data(), + output_buffer.size()); + })); + + + stream->initialize(flush); + + while(!input_stream.eof() || available_input > 0) + { + if (available_input > 0) + { + auto [read_size, write_size] = stream->update( + input_buffer.data(), available_input, + output_buffer.data(), output_buffer.size()); + if (write_size > 0) + { + output_stream.write(reinterpret_cast(output_buffer.data()), write_size); + } + available_input -= read_size; + } + else + { + input_stream.read(reinterpret_cast(input_buffer.data()), input_buffer.size()); + available_input = static_cast(input_stream.gcount()); + } + } + + while(finalizing) + { + finalizing = stream->finalize( + output_buffer.data(), output_buffer.size(), finalizing_write_size); + if (finalizing_write_size > 0) + { + output_stream.write(reinterpret_cast(output_buffer.data()), finalizing_write_size); + } + } + } + + const size_t input_size = 1000000; + const std::string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + std::string _input; +}; static void test_block_compression(const std::unique_ptr &compressor, const std::unique_ptr &decompressor) @@ -119,26 +204,34 @@ static void test_block_compression(const std::unique_ptr &co MAXTEST_ASSERT(std::equal(decompressed_data.begin(), decompressed_data.end(), input_data.begin())); } +static void test_stream_compression(const std::unique_ptr &encoder, + const std::unique_ptr &decoder) +{ + stream_processor processor; + processor.test_encode_decode(encoder, decoder, false); + processor.test_encode_decode(encoder, decoder, true); +} + MAXTEST_MAIN { MAXTEST_TEST_CASE(brotli::block) { maxzip::brotli_compressor_params params; params.quality = -100; - auto compressor_result = try_create_compressor(maxzip::create_brotli_compressor, params); + auto compressor_result = try_create(maxzip::create_brotli_compressor, params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); params.quality.reset(); params.window_size = -100; - compressor_result = try_create_compressor(maxzip::create_brotli_compressor, params); + compressor_result = try_create(maxzip::create_brotli_compressor, params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); params.window_size.reset(); params.mode = -100; - compressor_result = try_create_compressor(maxzip::create_brotli_compressor, params); + compressor_result = try_create(maxzip::create_brotli_compressor, params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); params.mode.reset(); - compressor_result = try_create_compressor(maxzip::create_brotli_compressor, params); + compressor_result = try_create(maxzip::create_brotli_compressor, params); MAXTEST_ASSERT(compressor_result.first && (compressor_result.second != nullptr)); - auto decompressor_result = try_create_decompressor(maxzip::create_brotli_decompressor, maxzip::brotli_decompressor_params{}); + auto decompressor_result = try_create(maxzip::create_brotli_decompressor, maxzip::brotli_decompressor_params{}); MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); test_block_compression(compressor_result.second, decompressor_result.second); }; @@ -148,16 +241,16 @@ MAXTEST_MAIN maxzip::zlib_compressor_params compress_params; maxzip::zlib_decompressor_params decompress_params; compress_params.level = -100; - auto compressor_result = try_create_compressor(maxzip::create_zlib_compressor, compress_params); + auto compressor_result = try_create(maxzip::create_zlib_compressor, compress_params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); compress_params.level.reset(); compress_params.window_bits = -100; - compressor_result = try_create_compressor(maxzip::create_zlib_compressor, compress_params); + compressor_result = try_create(maxzip::create_zlib_compressor, compress_params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); compress_params.window_bits.reset(); - compressor_result = try_create_compressor(maxzip::create_zlib_compressor, compress_params); + compressor_result = try_create(maxzip::create_zlib_compressor, compress_params); MAXTEST_ASSERT(compressor_result.first && (compressor_result.second != nullptr)); - auto decompressor_result = try_create_decompressor(maxzip::create_zlib_decompressor, decompress_params); + auto decompressor_result = try_create(maxzip::create_zlib_decompressor, decompress_params); MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); test_block_compression(compressor_result.second, decompressor_result.second); }; @@ -167,21 +260,32 @@ MAXTEST_MAIN maxzip::zstd_compressor_params compress_params; maxzip::zstd_decompressor_params decompress_params; compress_params.window_log = -100; - auto compressor_result = try_create_compressor(maxzip::create_zstd_compressor, compress_params); + auto compressor_result = try_create(maxzip::create_zstd_compressor, compress_params); MAXTEST_ASSERT(!compressor_result.first && (compressor_result.second == nullptr)); compress_params.window_log = 0; compress_params.enable_checksum = true; - compressor_result = try_create_compressor(maxzip::create_zstd_compressor, compress_params); + compressor_result = try_create(maxzip::create_zstd_compressor, compress_params); MAXTEST_ASSERT(compressor_result.first && (compressor_result.second != nullptr)); decompress_params.window_log_max = -100; - auto decompressor_result = try_create_decompressor(maxzip::create_zstd_decompressor, decompress_params); + auto decompressor_result = try_create(maxzip::create_zstd_decompressor, decompress_params); MAXTEST_ASSERT(!decompressor_result.first && (decompressor_result.second == nullptr)); decompress_params.window_log_max = 0; - decompressor_result = try_create_decompressor(maxzip::create_zstd_decompressor, decompress_params); + decompressor_result = try_create(maxzip::create_zstd_decompressor, decompress_params); MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); decompress_params.window_log_max.reset(); - decompressor_result = try_create_decompressor(maxzip::create_zstd_decompressor, decompress_params); + decompressor_result = try_create(maxzip::create_zstd_decompressor, decompress_params); MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); test_block_compression(compressor_result.second, decompressor_result.second); }; + + MAXTEST_TEST_CASE(brotli::stream) + { + maxzip::brotli_encoder_params encoder_params; + maxzip::brotli_decoder_params decoder_params; + auto encoder_result = try_create(maxzip::create_brotli_encoder, encoder_params); + MAXTEST_ASSERT(encoder_result.first && (encoder_result.second != nullptr)); + auto decoder_result = try_create(maxzip::create_brotli_decoder, decoder_params); + MAXTEST_ASSERT(decoder_result.first && (decoder_result.second != nullptr)); + test_stream_compression(encoder_result.second, decoder_result.second); + }; } \ No newline at end of file