#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_STORAGE_COMPRESSION_ZPAQ_ZPAQHELPER_H #define NUCLEX_STORAGE_COMPRESSION_ZPAQ_ZPAQHELPER_H #include "Nuclex/Storage/Config.h" #if defined(NUCLEX_STORAGE_HAVE_ZPAQ) // The libzpaq header has its share of warnings on maximum warning levels. // #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4244) // conversion from 'U64' to 'double' #pragma warning(disable:4100) // unreferenced formal parameter #pragma warning(disable:4267) // conversion from 'size_t' to 'int' #elif defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" // parameter unused #elif defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" // parameter unused #endif #include #if defined(_MSC_VER) #pragma warning(pop) #elif defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif #include #include namespace Nuclex { namespace Storage { namespace Compression { namespace ZPaq { // ------------------------------------------------------------------------------------------- // /// /// Writes data into a fixed-size buffer and when that one is full, into a vector /// /// /// This curious adapter is necessary because libzpaq cannot be stopped generating /// output until it has consumed the number of input bytes it is told to... /// class SplitBufferWriter : public libzpaq::Writer { /// Initializes a new split buffer writer /// /// Buffer that will be used to cache data that did not fit into the output buffer /// public: SplitBufferWriter() : overflowBufferReadIndex(0), remainingOutputByteCount(0) {} /// Frees all resources owned by the instance public: ~SplitBufferWriter() = default; /// Retrieves the number of bytes still available in the active buffer /// The number of bytes available in the fixed output buffer public: std::size_t GetRemainingByteCount() const { return this->remainingOutputByteCount; } /// Checks whether the overflow buffer has been used /// True if there are bytes waiting in the overflow buffer public: bool HasOverflowBytes() const { return !this->overflowBuffer.empty(); } /// Assigns a new buffer into which output should be written /// Buffer into which output should be written until full /// Number of bytes that fit in the output buffer /// /// The number of bytes that have been copied into the output buffer straight away /// because they were still waiting from overflow of the previous output buffer /// public: std::size_t TargetNewOutputBuffer( std::uint8_t *newOutputBuffer, std::size_t byteCount ) { if(this->overflowBuffer.empty()) { // Without overflow, just adopt the buffer this->outputBuffer = newOutputBuffer; this->remainingOutputByteCount = byteCount; return 0; } else { // There was overflow, push as much as we can into the new buffer std::size_t overflowedByteCount = ( this->overflowBuffer.size() - this->overflowBufferReadIndex ); if(byteCount >= overflowedByteCount) { // Does it all fit into the new buffer? std::copy_n( &this->overflowBuffer[this->overflowBufferReadIndex], overflowedByteCount, newOutputBuffer ); this->overflowBuffer.clear(); this->overflowBufferReadIndex = 0; this->outputBuffer = newOutputBuffer + overflowedByteCount; this->remainingOutputByteCount = byteCount - overflowedByteCount; return overflowedByteCount; } else { // There's more overflow than can fit into the new buffer std::copy_n( &this->overflowBuffer[this->overflowBufferReadIndex], byteCount, newOutputBuffer ); this->overflowBufferReadIndex += byteCount; this->remainingOutputByteCount = 0; return byteCount; } } } /// Writes a single byte to the output buffer /// Byte that will be written to the output buffer public: void put(int outputByte) override { if(this->remainingOutputByteCount > 0) { this->outputBuffer[0] = static_cast(outputByte); ++this->outputBuffer; --this->remainingOutputByteCount; } else { this->overflowBuffer.push_back(static_cast(outputByte)); } } /// Writes multiple bytes to the output buffer /// Buffer holding the bytes that will be written /// Number of bytes that will be written public: void write(const char *buffer, int byteCount) override { assert((byteCount >= 0) && u8"Byte count most be a positive value or zero"); if(static_cast(byteCount) <= this->remainingOutputByteCount) { std::copy_n(buffer, byteCount, this->outputBuffer); this->outputBuffer += byteCount; this->remainingOutputByteCount -= byteCount; } else { if(this->remainingOutputByteCount > 0) { std::copy_n(buffer, this->remainingOutputByteCount, this->outputBuffer); buffer += this->remainingOutputByteCount; assert(this->remainingOutputByteCount < std::numeric_limits::max()); byteCount -= static_cast(this->remainingOutputByteCount); this->remainingOutputByteCount = 0; } std::size_t firstByteIndex = this->overflowBuffer.size(); this->overflowBuffer.resize(firstByteIndex + byteCount); std::copy_n(buffer, byteCount, &this->overflowBuffer[firstByteIndex]); } }; /// /// Buffer into which extra data is written when the output buffer is full /// private: std::vector overflowBuffer; /// Index at which the next read of the overflow buffer takes place private: std::size_t overflowBufferReadIndex; /// Buffer into which output data should be written until full private: std::uint8_t *outputBuffer; /// Number of bytes still available in the output buffer private: std::size_t remainingOutputByteCount; // replace with end pointer }; // ------------------------------------------------------------------------------------------- // /// Reads data from a fixed-size buffer that can be switched out class FixedBufferReader : public libzpaq::Reader { /// Initializes a new fixed buffer reader public: FixedBufferReader() : inputByteCount(0) {} /// Frees all resources owned by the fixed buffer reader public: ~FixedBufferReader() = default; /// Selects a different input buffer for the reader /// New buffer the reader will take its data from /// Number of bytes the input buffer is holding public: void SetInputBuffer(const std::uint8_t *buffer, std::size_t byteCount) { this->inputBuffer = buffer; this->inputByteCount = byteCount; } /// Returns the number of unprocessed bytes in the input buffer /// The number of unused bytes in the input buffer public: std::size_t GetRemainingByteCount() const { return this->inputByteCount; } /// Fetches a single byte from the input buffer /// /// The byte from the input buffer or -1 when the end of the file is reached /// public: int get() override { if(this->inputByteCount == 0) { return -1; // Indicates EOF to libzpaq } std::uint8_t value = this->inputBuffer[0]; ++this->inputBuffer; --this->inputByteCount; return value; } /// Reads multiple bytes from the input buffer /// Buffer in which the data should be deposited /// Number of bytes to read from the input buffer /// The number of bytes that were actually read public: int read(char *buffer, int byteCount) override { std::size_t copiedByteCount = std::min( static_cast(byteCount), this->inputByteCount ); if(copiedByteCount > 0) { std::copy_n(this->inputBuffer, copiedByteCount, buffer); this->inputBuffer += copiedByteCount; this->inputByteCount -= copiedByteCount; } assert(copiedByteCount < std::numeric_limits::max()); return static_cast(copiedByteCount); } /// Buffer from which the reader is currently reading private: const std::uint8_t *inputBuffer; /// Remaining number of bytes in the input buffer private: std::size_t inputByteCount; }; // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Storage::Compression::ZPaq #endif // defined(NUCLEX_STORAGE_HAVE_ZPAQ) #endif // NUCLEX_STORAGE_COMPRESSION_ZPAQ_ZPAQHELPER_H