From 07865bd095a6af586013cd2d63d67efc1a3bb56b Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Wed, 18 Mar 2026 10:30:40 +0100 Subject: [PATCH] Replaced the underlying zlib/deflate implementation with the actual zstd library Signed-off-by: Alejandro Hernandez Cordero --- zstd_image_transport/CMakeLists.txt | 6 +- .../zstd_image_transport/zstd_publisher.hpp | 7 + .../zstd_image_transport/zstd_subscriber.hpp | 7 + zstd_image_transport/package.xml | 2 +- zstd_image_transport/src/zlib_cpp.cpp | 164 ------------------ zstd_image_transport/src/zstd_publisher.cpp | 71 ++++---- zstd_image_transport/src/zstd_subscriber.cpp | 111 +++++++----- zstd_image_transport/src/zstd_wrapper.cpp | 103 +++++++++++ .../src/{zlib_cpp.hpp => zstd_wrapper.hpp} | 101 +++++------ 9 files changed, 275 insertions(+), 297 deletions(-) delete mode 100644 zstd_image_transport/src/zlib_cpp.cpp create mode 100644 zstd_image_transport/src/zstd_wrapper.cpp rename zstd_image_transport/src/{zlib_cpp.hpp => zstd_wrapper.hpp} (52%) diff --git a/zstd_image_transport/CMakeLists.txt b/zstd_image_transport/CMakeLists.txt index a210990..1553ffa 100644 --- a/zstd_image_transport/CMakeLists.txt +++ b/zstd_image_transport/CMakeLists.txt @@ -16,20 +16,20 @@ find_package(image_transport REQUIRED) find_package(pluginlib REQUIRED) find_package(rclcpp REQUIRED) find_package(sensor_msgs REQUIRED) -find_package(ZLIB REQUIRED) +find_package(zstd REQUIRED) include_directories(include) add_library( ${PROJECT_NAME} SHARED - src/zlib_cpp.cpp + src/zstd_wrapper.cpp src/zstd_publisher.cpp src/zstd_subscriber.cpp src/manifest.cpp ) target_link_libraries(${PROJECT_NAME} - ZLIB::ZLIB + zstd::libzstd_shared image_transport::image_transport rclcpp::rclcpp pluginlib::pluginlib diff --git a/zstd_image_transport/include/zstd_image_transport/zstd_publisher.hpp b/zstd_image_transport/include/zstd_image_transport/zstd_publisher.hpp index 0caa69c..1df12aa 100644 --- a/zstd_image_transport/include/zstd_image_transport/zstd_publisher.hpp +++ b/zstd_image_transport/include/zstd_image_transport/zstd_publisher.hpp @@ -31,6 +31,7 @@ #ifndef ZSTD_IMAGE_TRANSPORT__ZSTD_PUBLISHER_HPP_ #define ZSTD_IMAGE_TRANSPORT__ZSTD_PUBLISHER_HPP_ +#include #include #include #include @@ -44,6 +45,9 @@ #include "zstd_image_transport/zstd_common.hpp" +// Forward declaration — defined in the private zstd_wrapper.hpp header. +namespace zstd_wrapper {class Compressor;} + namespace zstd_image_transport { @@ -73,6 +77,9 @@ class ZstdPublisher : public image_transport::SimplePublisherPlugin compressor_; + private: std::vector parameters_; std::unordered_set deprecated_parameters_; diff --git a/zstd_image_transport/include/zstd_image_transport/zstd_subscriber.hpp b/zstd_image_transport/include/zstd_image_transport/zstd_subscriber.hpp index 4a281d2..7ad491a 100644 --- a/zstd_image_transport/include/zstd_image_transport/zstd_subscriber.hpp +++ b/zstd_image_transport/include/zstd_image_transport/zstd_subscriber.hpp @@ -31,6 +31,7 @@ #ifndef ZSTD_IMAGE_TRANSPORT__ZSTD_SUBSCRIBER_HPP_ #define ZSTD_IMAGE_TRANSPORT__ZSTD_SUBSCRIBER_HPP_ +#include #include #include @@ -44,6 +45,9 @@ #include "zstd_image_transport/zstd_common.hpp" +// Forward declaration — defined in the private zstd_wrapper.hpp header. +namespace zstd_wrapper {class Decompressor;} + namespace zstd_image_transport { @@ -71,6 +75,9 @@ class ZstdSubscriber final const Callback & user_cb) override; rclcpp::Logger logger_; + + // Reusable decompression context — avoids per-frame ZSTD_DCtx allocation. + std::unique_ptr decompressor_; }; } // namespace zstd_image_transport diff --git a/zstd_image_transport/package.xml b/zstd_image_transport/package.xml index 5129f1e..82a4fc8 100644 --- a/zstd_image_transport/package.xml +++ b/zstd_image_transport/package.xml @@ -14,7 +14,7 @@ ament_cmake image_transport - zlib + libzstd-dev ament_lint_auto ament_lint_common diff --git a/zstd_image_transport/src/zlib_cpp.cpp b/zstd_image_transport/src/zlib_cpp.cpp deleted file mode 100644 index 4594a5c..0000000 --- a/zstd_image_transport/src/zlib_cpp.cpp +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2023, Open Source Robotics Foundation, Inc. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// * Neither the name of the copyright holder nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#include "zlib_cpp.hpp" - -#include -#include - -namespace zlib -{ - -// Block size block used to compress/uncompress the data -const int MAX_CHUNK_SIZE = 1024; - -const int WINDOW_BITS = 15; - -/// Allocate memory to DataBlock and assign to a shared_ptr object. -std::shared_ptr AllocateData(std::size_t size) -{ - std::shared_ptr data(new DataBlock, [](DataBlock * p) { - delete[] p->ptr; - delete p; - }); - data->ptr = new uint8_t[size]; - data->size = size; - return data; -} - -std::shared_ptr ExpandDataList(const std::list> & data_list) -{ - std::size_t total_size = 0; - for (const std::shared_ptr & this_data : data_list) { - total_size += this_data->size; - } - std::shared_ptr out_data = AllocateData(total_size); - uint8_t * this_ptr = out_data->ptr; - for (const std::shared_ptr & this_data : data_list) { - memcpy(this_ptr, this_data->ptr, this_data->size); - this_ptr += this_data->size; - } - return out_data; -} - -Comp::Comp(Level level, bool zlib_header) -: level_(level) -{ - memset(&zs_, 0, sizeof(zs_)); - int windowBits = WINDOW_BITS; - if (zlib_header) { - // Configurate the compressor to write a simple zlib header and trailer - // around the compressed data instead of a zlib wrapper - windowBits += 16; - } - int ret = deflateInit2( - &zs_, static_cast(level_), Z_DEFLATED, windowBits, - 8, Z_DEFAULT_STRATEGY); - init_ok_ = ret == Z_OK; -} - -Comp::~Comp() {deflateEnd(&zs_);} - -bool Comp::IsSucc() const -{ - return init_ok_; -} - -std::list> Comp::Process( - const uint8_t * buffer, std::size_t size, - bool last_block) -{ - std::list> out_data_list; - // Prepare output buffer memory. - uint8_t out_buffer[MAX_CHUNK_SIZE]; - zs_.next_in = reinterpret_cast(const_cast(buffer)); - zs_.avail_in = static_cast(size); - do { - // Reset output buffer position and size. - zs_.avail_out = MAX_CHUNK_SIZE; - zs_.next_out = out_buffer; - // Do compress. - deflate(&zs_, last_block ? Z_FINISH : Z_NO_FLUSH); - // Allocate output memory. - std::size_t out_size = MAX_CHUNK_SIZE - zs_.avail_out; - std::shared_ptr out_data = AllocateData(out_size); - // Copy and add to output data list. - memcpy(out_data->ptr, out_buffer, out_size); - out_data_list.push_back(std::move(out_data)); - } while (zs_.avail_out == 0); - // Done. - return out_data_list; -} - -Decomp::Decomp() -{ - memset(&zs_, 0, sizeof(zs_)); - // Enable zlib and zlib decoding with automatic header detection - int windowBits = WINDOW_BITS + 32; - int ret = inflateInit2(&zs_, windowBits); - init_ok_ = ret == Z_OK; -} - -Decomp::~Decomp() {inflateEnd(&zs_);} - -std::list> Decomp::Process( - const std::shared_ptr & compressed_data) -{ - std::list> out_data_list; - uint8_t out_buffer[MAX_CHUNK_SIZE]; - // Incoming buffer. - zs_.avail_in = static_cast(compressed_data->size); - zs_.next_in = compressed_data->ptr; - int ret; - do { - // Prepare outcoming buffer and size. - zs_.avail_out = MAX_CHUNK_SIZE; - zs_.next_out = out_buffer; - // Decompress data. - ret = inflate(&zs_, Z_NO_FLUSH); - switch (ret) { - case Z_NEED_DICT: - // Incoming data is invalid. - return out_data_list; - case Z_DATA_ERROR: - case Z_MEM_ERROR: - // Critical error. - return out_data_list; - } - // Outcome size. - std::size_t out_size = MAX_CHUNK_SIZE - zs_.avail_out; - // Allocate outcome buffer. - std::shared_ptr out_data = AllocateData(out_size); - memcpy(out_data->ptr, out_buffer, out_size); - out_data_list.push_back(std::move(out_data)); - } while (zs_.avail_out == 0); - return out_data_list; -} - -} // namespace zlib diff --git a/zstd_image_transport/src/zstd_publisher.cpp b/zstd_image_transport/src/zstd_publisher.cpp index 3db1beb..5bf35a2 100644 --- a/zstd_image_transport/src/zstd_publisher.cpp +++ b/zstd_image_transport/src/zstd_publisher.cpp @@ -31,7 +31,7 @@ #include -#include "zlib_cpp.hpp" +#include "zstd_wrapper.hpp" namespace zstd_image_transport { @@ -102,57 +102,68 @@ void ZstdPublisher::publish( const sensor_msgs::msg::Image & message, const PublisherT & publisher) const { - // Fresh Configuration int cfg_zstd_level = node_param_interface_->get_parameter( parameters_[ZSTD_LEVEL]).as_int(); - zlib::Comp comp(static_cast(cfg_zstd_level), true); - auto g_compressed_data = - comp.Process(&message.data[0], message.data.size(), true); - - size_t total_size = 0; - for (const auto & data : g_compressed_data) { - total_size += data->size; + // Lazily initialize the reusable compression context. + if (!compressor_) { + compressor_ = std::make_unique(); } - sensor_msgs::msg::CompressedImage compressed; + // Pre-size the output buffer to the zstd upper bound — no intermediate + // chunked list, no extra copy. + const std::size_t bound = zstd_wrapper::Compressor::compressBound(message.data.size()); + const std::size_t metadata = + 4 + // height + 4 + // width + 1 + // is_bigendian + 4 + // step + 4 + // encoding string length + message.encoding.size(); - int metadata = 4 + 4 + 1 + 4 + 4 + message.encoding.size(); + sensor_msgs::msg::CompressedImage compressed; + compressed.data.resize(metadata + bound); - compressed.data.resize(total_size + metadata); + const std::size_t compressed_size = compressor_->compress( + &compressed.data[metadata], bound, + message.data.data(), message.data.size(), + cfg_zstd_level); - size_t index = metadata; - for (const auto & data : g_compressed_data) { - memcpy(&compressed.data[index], data->ptr, data->size); - index += data->size; + if (compressed_size == 0) { + RCLCPP_ERROR(logger_, "zstd compression failed"); + return; } + // Trim to actual compressed size (avoids sending the unused bound padding). + compressed.data.resize(metadata + compressed_size); + + // ---- Metadata header (little-endian) ---- compressed.data[0] = static_cast(message.height & 0xFF); - compressed.data[1] = static_cast(message.height >> 8) & 0xFF; - compressed.data[2] = static_cast(message.height >> 16) & 0xFF; - compressed.data[3] = static_cast(message.height >> 24) & 0xFF; + compressed.data[1] = static_cast((message.height >> 8) & 0xFF); + compressed.data[2] = static_cast((message.height >> 16) & 0xFF); + compressed.data[3] = static_cast((message.height >> 24) & 0xFF); compressed.data[4] = static_cast(message.width & 0xFF); - compressed.data[5] = static_cast(message.width >> 8) & 0xFF; - compressed.data[6] = static_cast(message.width >> 16) & 0xFF; - compressed.data[7] = static_cast(message.width >> 24) & 0xFF; + compressed.data[5] = static_cast((message.width >> 8) & 0xFF); + compressed.data[6] = static_cast((message.width >> 16) & 0xFF); + compressed.data[7] = static_cast((message.width >> 24) & 0xFF); compressed.data[8] = message.is_bigendian; compressed.data[9] = static_cast(message.step & 0xFF); - compressed.data[10] = static_cast(message.step >> 8) & 0xFF; - compressed.data[11] = static_cast(message.step >> 16) & 0xFF; - compressed.data[12] = static_cast(message.step >> 24) & 0xFF; + compressed.data[10] = static_cast((message.step >> 8) & 0xFF); + compressed.data[11] = static_cast((message.step >> 16) & 0xFF); + compressed.data[12] = static_cast((message.step >> 24) & 0xFF); compressed.data[13] = static_cast(message.encoding.size() & 0xFF); - compressed.data[14] = static_cast(message.encoding.size() >> 8) & 0xFF; - compressed.data[15] = static_cast(message.encoding.size() >> 16) & 0xFF; - compressed.data[16] = static_cast(message.encoding.size() >> 24) & 0xFF; + compressed.data[14] = static_cast((message.encoding.size() >> 8) & 0xFF); + compressed.data[15] = static_cast((message.encoding.size() >> 16) & 0xFF); + compressed.data[16] = static_cast((message.encoding.size() >> 24) & 0xFF); - memcpy(&compressed.data[17], &message.encoding[0], message.encoding.size()); + memcpy(&compressed.data[17], message.encoding.data(), message.encoding.size()); + // ----------------------------------------- - // Compressed image message compressed.header = message.header; compressed.format = "zstd"; publisher->publish(compressed); diff --git a/zstd_image_transport/src/zstd_subscriber.cpp b/zstd_image_transport/src/zstd_subscriber.cpp index 6586460..de58766 100644 --- a/zstd_image_transport/src/zstd_subscriber.cpp +++ b/zstd_image_transport/src/zstd_subscriber.cpp @@ -30,17 +30,14 @@ #include "zstd_image_transport/zstd_subscriber.hpp" -#include -#include +#include #include #include #include -#include -#include -#include "zlib_cpp.hpp" +#include "zstd_wrapper.hpp" namespace zstd_image_transport { @@ -70,54 +67,88 @@ void ZstdSubscriber::internalCallback( const sensor_msgs::msg::CompressedImage::ConstSharedPtr & msg, const Callback & user_cb) { - auto result = std::make_shared(); + constexpr std::size_t kFixedHeaderSize = 4 + 4 + 1 + 4 + 4; // = 17 bytes - zlib::Decomp decomp; + if (msg->data.size() < kFixedHeaderSize) { + RCLCPP_ERROR(logger_, "Compressed image too small to contain header"); + return; + } - int metadata = 4 + 4 + 1 + 4 + 4; + auto result = std::make_shared(); + // ---- Decode fixed metadata header (little-endian) ---- result->height = - (msg->data[3] << 24 ) + - (msg->data[2] << 16 ) + - (msg->data[1] << 8 ) + - (msg->data[0]); + (static_cast(msg->data[3]) << 24) | + (static_cast(msg->data[2]) << 16) | + (static_cast(msg->data[1]) << 8) | + static_cast(msg->data[0]); result->width = - (msg->data[7] << 24 ) + - (msg->data[6] << 16 ) + - (msg->data[5] << 8 ) + - (msg->data[4]); + (static_cast(msg->data[7]) << 24) | + (static_cast(msg->data[6]) << 16) | + (static_cast(msg->data[5]) << 8) | + static_cast(msg->data[4]); result->is_bigendian = msg->data[8]; result->step = - (msg->data[12] << 24 ) + - (msg->data[11] << 16 ) + - (msg->data[10] << 8 ) + - (msg->data[9]); - - uint32_t encoding_size = - (msg->data[16] << 24 ) + - (msg->data[15] << 16 ) + - (msg->data[14] << 8 ) + - (msg->data[13]); - - std::string encoding; + (static_cast(msg->data[12]) << 24) | + (static_cast(msg->data[11]) << 16) | + (static_cast(msg->data[10]) << 8) | + static_cast(msg->data[9]); + + const uint32_t encoding_size = + (static_cast(msg->data[16]) << 24) | + (static_cast(msg->data[15]) << 16) | + (static_cast(msg->data[14]) << 8) | + static_cast(msg->data[13]); + // ------------------------------------------------------ + + const std::size_t metadata = kFixedHeaderSize + encoding_size; + if (msg->data.size() < metadata) { + RCLCPP_ERROR(logger_, "Compressed image data truncated (encoding string missing)"); + return; + } + result->encoding.resize(encoding_size); memcpy(&result->encoding[0], &msg->data[17], encoding_size); - metadata += encoding_size; - - std::shared_ptr data = zlib::AllocateData(msg->data.size()); - memcpy(data->ptr, &msg->data[metadata], msg->data.size()); - - std::list> out_data_list; - out_data_list = decomp.Process(data); - - std::shared_ptr data2 = zlib::ExpandDataList(out_data_list); - - result->data.resize(data2->size); - memcpy(&result->data[0], data2->ptr, data2->size); + // Pointer and size of the actual compressed payload. + const uint8_t * compressed_ptr = &msg->data[metadata]; + const std::size_t compressed_size = msg->data.size() - metadata; + + if (compressed_size == 0) { + RCLCPP_ERROR(logger_, "Compressed image payload is empty"); + return; + } + + // Lazily initialise the reusable decompression context. + if (!decompressor_) { + decompressor_ = std::make_unique(); + } + + // zstd always writes the content size in the frame header for single-block + // compression, so this gives us the exact destination buffer size. + const std::size_t decompressed_size = + zstd_wrapper::Decompressor::getDecompressedSize(compressed_ptr, compressed_size); + + if (decompressed_size == 0) { + RCLCPP_ERROR(logger_, "Could not read decompressed size from zstd frame header"); + return; + } + + result->data.resize(decompressed_size); + const std::size_t actual_size = decompressor_->decompress( + result->data.data(), decompressed_size, + compressed_ptr, compressed_size); + + if (actual_size == 0) { + RCLCPP_ERROR(logger_, "zstd decompression failed"); + return; + } + + result->data.resize(actual_size); + result->header = msg->header; user_cb(result); } diff --git a/zstd_image_transport/src/zstd_wrapper.cpp b/zstd_image_transport/src/zstd_wrapper.cpp new file mode 100644 index 0000000..b8bcaf3 --- /dev/null +++ b/zstd_image_transport/src/zstd_wrapper.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2023, Open Source Robotics Foundation, Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "zstd_wrapper.hpp" + +#include +#include +#include +#include + +namespace zstd_wrapper +{ + +Compressor::Compressor() +: ctx_(ZSTD_createCCtx()) +{ + if (!ctx_) { + throw std::runtime_error("Failed to create ZSTD compression context"); + } +} + +Compressor::~Compressor() +{ + ZSTD_freeCCtx(ctx_); +} + +std::size_t Compressor::compress( + uint8_t * dst, std::size_t dst_capacity, + const uint8_t * src, std::size_t src_size, + int level) +{ + std::size_t result = ZSTD_compressCCtx(ctx_, dst, dst_capacity, src, src_size, level); + if (ZSTD_isError(result)) { + return 0; + } + return result; +} + +std::size_t Compressor::compressBound(std::size_t src_size) +{ + return ZSTD_compressBound(src_size); +} + +Decompressor::Decompressor() +: ctx_(ZSTD_createDCtx()) +{ + if (!ctx_) { + throw std::runtime_error("Failed to create ZSTD decompression context"); + } +} + +Decompressor::~Decompressor() +{ + ZSTD_freeDCtx(ctx_); +} + +std::size_t Decompressor::decompress( + uint8_t * dst, std::size_t dst_capacity, + const uint8_t * src, std::size_t src_size) +{ + std::size_t result = ZSTD_decompressDCtx(ctx_, dst, dst_capacity, src, src_size); + if (ZSTD_isError(result)) { + return 0; + } + return result; +} + +std::size_t Decompressor::getDecompressedSize(const uint8_t * src, std::size_t src_size) +{ + uint64_t size = ZSTD_getFrameContentSize(src, src_size); + if (size == ZSTD_CONTENTSIZE_UNKNOWN || size == ZSTD_CONTENTSIZE_ERROR) { + return 0; + } + return static_cast(size); +} + +} // namespace zstd_wrapper diff --git a/zstd_image_transport/src/zlib_cpp.hpp b/zstd_image_transport/src/zstd_wrapper.hpp similarity index 52% rename from zstd_image_transport/src/zlib_cpp.hpp rename to zstd_image_transport/src/zstd_wrapper.hpp index 45a583a..b2a9614 100644 --- a/zstd_image_transport/src/zlib_cpp.hpp +++ b/zstd_image_transport/src/zstd_wrapper.hpp @@ -27,86 +27,69 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -#ifndef ZLIB_CPP_HPP_ -#define ZLIB_CPP_HPP_ +#ifndef ZSTD_WRAPPER_HPP_ +#define ZSTD_WRAPPER_HPP_ -#include +#include +#include #include -#include -#include -#include +#include -namespace zlib +namespace zstd_wrapper { -struct DataBlock -{ - uint8_t * ptr; - std::size_t size; -}; - -std::shared_ptr AllocateData(std::size_t size); -std::shared_ptr ExpandDataList(const std::list> & data_list); - -/// Compress processor. -class Comp +/// Reusable ZSTD compression context. +/// Avoids per-frame context creation overhead. +class Compressor { public: - enum class Level - { - Default = -1, - Min = 0, - Level_1 = 1, - Level_2 = 2, - Level_3 = 3, - Level_4 = 4, - Level_5 = 5, - Level_6 = 6, - Level_7 = 7, - Level_8 = 8, - Max = 9 - }; + Compressor(); + ~Compressor(); -public: - /// Construct a compressor. - explicit Comp(Level level = Level::Default, bool zlib_header = false); - - /// Destructor, will release z_stream. - ~Comp(); + // Non-copyable + Compressor(const Compressor &) = delete; + Compressor & operator=(const Compressor &) = delete; - /// Returns true if compressor initialize successfully. - bool IsSucc() const; + /// Compress src into dst. dst must be at least compressBound(srcSize) bytes. + /// Returns actual compressed size, or 0 on error. + std::size_t compress( + uint8_t * dst, std::size_t dst_capacity, + const uint8_t * src, std::size_t src_size, + int level); - /// Compress incoming buffer to DataBlock list. - std::list> Process( - const uint8_t * buffer, std::size_t size, bool last_block = false); + /// Returns the maximum compressed size for src_size bytes of input. + static std::size_t compressBound(std::size_t src_size); private: - Level level_; - z_stream zs_; - bool init_ok_; + ZSTD_CCtx * ctx_; }; -/// Decompress processor. -class Decomp +/// Reusable ZSTD decompression context. +class Decompressor { public: - /// Construct a decompressor. - Decomp(); + Decompressor(); + ~Decompressor(); + + // Non-copyable + Decompressor(const Decompressor &) = delete; + Decompressor & operator=(const Decompressor &) = delete; - /// Destructor, will release z_stream. - ~Decomp(); + /// Decompress src into dst. + /// Returns actual decompressed size, or 0 on error. + std::size_t decompress( + uint8_t * dst, std::size_t dst_capacity, + const uint8_t * src, std::size_t src_size); - /// Decompress incoming buffer to DataBlock list. - std::list> Process( - const std::shared_ptr & compressed_data); + /// Returns the decompressed content size encoded in the zstd frame header. + /// Returns 0 if the size is not stored in the frame or on error. + static std::size_t getDecompressedSize(const uint8_t * src, std::size_t src_size); private: - z_stream zs_; - bool init_ok_; + ZSTD_DCtx * ctx_; }; -} // namespace zlib +} // namespace zstd_wrapper -#endif // ZLIB_CPP_HPP_ +#endif // ZSTD_WRAPPER_HPP_