summaryrefslogtreecommitdiff
path: root/src/decompress_z.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/decompress_z.cc')
-rw-r--r--src/decompress_z.cc120
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