diff --git a/include/maxzip/decompressor.hpp b/include/maxzip/decompressor.hpp index e8291e6..0944753 100644 --- a/include/maxzip/decompressor.hpp +++ b/include/maxzip/decompressor.hpp @@ -41,11 +41,6 @@ namespace maxzip struct zstd_decompressor_params { std::optional window_log_max; - std::optional decoder_format; - std::optional stable_output_buffer; - std::optional ignore_checksum; - std::optional multiple_dictionaries; - std::optional disable_huffman_assembly; }; decompressor *create_brotli_decompressor(const brotli_decompressor_params ¶ms = {}); diff --git a/src/internal.hpp b/src/internal.hpp index 19623c8..7ead873 100644 --- a/src/internal.hpp +++ b/src/internal.hpp @@ -32,6 +32,7 @@ #include +#include #include #include @@ -44,19 +45,6 @@ namespace maxzip return (value >= min && value <= max); } - class zstd_compressor : public compressor - { - public: - zstd_compressor(int level); - ~zstd_compressor(); - size_t compress( - const uint8_t* input, - size_t input_size, - uint8_t* output, - size_t& output_size) override; - private: - ZSTD_CCtx* _cctx; - }; } #endif \ No newline at end of file diff --git a/src/zstd.cpp b/src/zstd.cpp index f2c13af..6ecdb00 100644 --- a/src/zstd.cpp +++ b/src/zstd.cpp @@ -1 +1,157 @@ -#include \ No newline at end of file +#include + +namespace maxzip +{ + template< + class ContextType, + class ParameterType, + class SetParameterFuncType, + SetParameterFuncType SetParameterFunc, + class DeleterType, + DeleterType DeleterFunc> + class zstd_context + { + public: + static void deleter(ContextType *ctx) + { + static_cast(DeleterFunc(ctx)); + } + + zstd_context(ContextType *ctx) : _ctx(ctx, deleter) + { + } + + void set_parameter(ParameterType key, int value) + { + const size_t ret = SetParameterFunc(_ctx.get(), key, value); + const bool is_error = (ZSTD_isError(ret) == 1); + if (is_error) + { + throw std::runtime_error("Failed to set Zstandard parameter: " + std::to_string(key)); + } + } + + void set_flag(ParameterType key, bool value) + { + static_cast(SetParameterFunc(_ctx.get(), key, value ? 1 : 0)); + } + + protected: + std::unique_ptr> _ctx; + }; + + class zstd_compressor : public compressor, public zstd_context + { + public: + zstd_compressor() : zstd_context(ZSTD_createCCtx()) + { + } + + size_t compress( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t &output_size) override + { + size_t compressed_size(0); + if (output != nullptr) + { + static_cast(ZSTD_CCtx_reset(_ctx.get(), ZSTD_reset_session_only)); + compressed_size = ZSTD_compressCCtx( + _ctx.get(), + output, + output_size, + input, + input_size, + 0); + if(ZSTD_isError(compressed_size)) + { + throw std::runtime_error("Zstandard compression failed: " + std::string(ZSTD_getErrorName(compressed_size))); + } + } + else + { + output_size = ZSTD_compressBound(input_size); + } + return compressed_size; + } + }; + + class zstd_decompressor : public decompressor, public zstd_context + { + public: + zstd_decompressor() : zstd_context(ZSTD_createDCtx()) + { + } + + size_t decompress( + const uint8_t *input, + size_t input_size, + uint8_t *output, + size_t output_size) override + { + size_t decompressed_size = ZSTD_decompressDCtx( + _ctx.get(), + output, + output_size, + input, + input_size); + if (ZSTD_isError(decompressed_size)) + { + throw std::runtime_error("Zstandard decompression failed: " + std::string(ZSTD_getErrorName(decompressed_size))); + } + return decompressed_size; + } + }; + + compressor *create_zstd_compressor(const zstd_compressor_params ¶ms) + { + std::unique_ptr compressor = std::make_unique(); + + std::unordered_map> param_map; + param_map[ZSTD_c_compressionLevel] = params.level; + param_map[ZSTD_c_windowLog] = params.window_log; + param_map[ZSTD_c_hashLog] = params.hash_log; + param_map[ZSTD_c_chainLog] = params.chain_log; + param_map[ZSTD_c_searchLog] = params.search_log; + param_map[ZSTD_c_minMatch] = params.min_match; + param_map[ZSTD_c_targetLength] = params.target_length; + param_map[ZSTD_c_strategy] = params.strategy; + + std::unordered_map> flag_map; + flag_map[ZSTD_c_enableLongDistanceMatching] = params.enable_long_distance_matching; + flag_map[ZSTD_c_contentSizeFlag] = params.enable_content_size; + flag_map[ZSTD_c_checksumFlag] = params.enable_checksum; + flag_map[ZSTD_c_dictIDFlag] = params.enable_dict_id; + + for (const auto &[key, value] : param_map) + { + if (value.has_value()) + { + compressor->set_parameter(key, value.value()); + } + } + + for (const auto &[key, value] : flag_map) + { + if (value.has_value()) + { + compressor->set_flag(key, value.value()); + } + } + + return compressor.release(); + } + + decompressor *create_zstd_decompressor(const zstd_decompressor_params ¶ms) + { + std::unique_ptr decompressor = std::make_unique(); + + if(params.window_log_max.has_value()) + { + decompressor->set_parameter(ZSTD_d_windowLogMax, params.window_log_max.value()); + } + + return decompressor.release(); + } +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7354852..d59789e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,4 +37,5 @@ if(MAXZIP_COVER) endif() maxtest_add_test(unit brotli::block) -maxtest_add_test(unit zlib::block) \ No newline at end of file +maxtest_add_test(unit zlib::block) +maxtest_add_test(unit zstd::block) \ No newline at end of file diff --git a/tests/unit.cpp b/tests/unit.cpp index 9d9790f..cdbb178 100644 --- a/tests/unit.cpp +++ b/tests/unit.cpp @@ -161,4 +161,27 @@ MAXTEST_MAIN MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); test_block_compression(compressor_result.second, decompressor_result.second); }; + + MAXTEST_TEST_CASE(zstd::block) + { + 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); + 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); + 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); + 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); + 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); + MAXTEST_ASSERT(decompressor_result.first && (decompressor_result.second != nullptr)); + test_block_compression(compressor_result.second, decompressor_result.second); + }; } \ No newline at end of file