diff options
Diffstat (limited to 'src/decompress_z.cc')
| -rw-r--r-- | src/decompress_z.cc | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/src/decompress_z.cc b/src/decompress_z.cc new file mode 100644 index 0000000..f9f87ae --- /dev/null +++ b/src/decompress_z.cc @@ -0,0 +1,120 @@ +#include "decompress.hh" + +#include "buffer.hh" + +#define ZLIB_CONST +#include <zlib.h> + +#include <algorithm> +#include <cstddef> +#include <expected> +#include <limits> +#include <memory> +#include <optional> +#include <utility> + +namespace decompress { + +namespace { + +const size_t kBufferSizeZ = static_cast<size_t>(1024) * 1024; + +class DecompressReader : public io::Reader { + public: + DecompressReader(std::unique_ptr<io::Reader> reader, bool gzip) + : reader_(std::move(reader)), gzip_(gzip) {} + + ~DecompressReader() override { + if (initialized_) + inflateEnd(&stream_); + } + + std::expected<size_t, io::ReadError> read(void* dst, size_t max) override { + auto err = fill(); + if (err.has_value()) + return std::unexpected(err.value()); + + // NOLINTNEXTLINE(misc-include-cleaner) + stream_.next_out = reinterpret_cast<Bytef*>(dst); + stream_.avail_out = max; + + if (!initialized_) { + if (in_eof_ && buffer_->empty()) + return 0; + + stream_.zalloc = Z_NULL; + stream_.zfree = Z_NULL; + stream_.opaque = Z_NULL; + if (inflateInit2(&stream_, gzip_ ? 16 : 0) != Z_OK) { + return std::unexpected(io::ReadError::Error); + } + initialized_ = true; + } + + auto* const rptr = stream_.next_in; + auto ret = inflate(&stream_, in_eof_ ? Z_FINISH : Z_NO_FLUSH); + auto got = max - stream_.avail_out; + if (ret == Z_STREAM_END) { + inflateEnd(&stream_); + initialized_ = false; + buffer_->consume(stream_.next_in - rptr); + } else if (ret == Z_OK) { + if (!in_eof_) + buffer_->consume(stream_.next_in - rptr); + } else { + return std::unexpected( + ret == Z_DATA_ERROR + ? io::ReadError::InvalidData : io::ReadError::Error); + } + return got; + } + + std::expected<size_t, io::ReadError> skip(size_t max) override { + auto tmp = std::make_unique_for_overwrite<char[]>(max); + return read(tmp.get(), max); + } + + private: + std::optional<io::ReadError> fill() { + size_t avail; + auto* rptr = buffer_->rptr(avail); + if (!in_eof_ && avail < kBufferSizeZ / 2) { + auto* wptr = buffer_->wptr(avail); + auto got = reader_->read(wptr, avail); + if (got.has_value()) { + buffer_->commit(got.value()); + if (got.value() == 0) + in_eof_ = true; + } else { + return got.error(); + } + rptr = buffer_->rptr(avail); + } + // NOLINTNEXTLINE(misc-include-cleaner) + stream_.next_in = reinterpret_cast<z_const Bytef*>(rptr); + stream_.avail_in = std::min( + // NOLINTNEXTLINE(misc-include-cleaner) + static_cast<size_t>(std::numeric_limits<uInt>::max()), avail); + return std::nullopt; + } + + std::unique_ptr<io::Reader> reader_; + bool const gzip_; + bool in_eof_{false}; + std::unique_ptr<Buffer> buffer_{Buffer::fixed(kBufferSizeZ)}; + bool initialized_{false}; + z_stream stream_; +}; + +} // namespace + +std::unique_ptr<io::Reader> zlib(std::unique_ptr<io::Reader> reader) { + return std::make_unique<DecompressReader>(std::move(reader), /* gzip = */ false); +} + +std::unique_ptr<io::Reader> gzip(std::unique_ptr<io::Reader> reader) { + return std::make_unique<DecompressReader>(std::move(reader), /* gzip = */ true); +} + + +} // namespace decompress |
