libzarr
Header-only C++17 Zarr v2/v3, WASM-compatible
Loading...
Searching...
No Matches
codecs_gzip.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2
3#ifndef LIBZARR_CODECS_GZIP_HPP
4#define LIBZARR_CODECS_GZIP_HPP
5
6#ifndef LIBZARR_HAS_ZLIB
7#error "libzarr/codecs_gzip.hpp requires zlib: define LIBZARR_HAS_ZLIB and link against zlib"
8#endif
9
10#include <algorithm>
11#include <climits>
12#include <cstdint>
13#include <optional>
14#include <string>
15
16#include <zlib.h>
17
18#include "libzarr/detail/common.hpp"
19#include "libzarr/types.hpp"
20
25
26namespace zarr::detail {
27
28class ZlibDeflateGuard {
29 public:
30 explicit ZlibDeflateGuard(z_stream* zs) : zs_(zs) {}
31 ZlibDeflateGuard(const ZlibDeflateGuard&) = delete;
32 ZlibDeflateGuard& operator=(const ZlibDeflateGuard&) = delete;
33 ZlibDeflateGuard(ZlibDeflateGuard&&) = delete;
34 ZlibDeflateGuard& operator=(ZlibDeflateGuard&&) = delete;
35 ~ZlibDeflateGuard() { deflateEnd(zs_); }
36
37 private:
38 z_stream* zs_;
39};
40
41class ZlibInflateGuard {
42 public:
43 explicit ZlibInflateGuard(z_stream* zs) : zs_(zs) {}
44 ZlibInflateGuard(const ZlibInflateGuard&) = delete;
45 ZlibInflateGuard& operator=(const ZlibInflateGuard&) = delete;
46 ZlibInflateGuard(ZlibInflateGuard&&) = delete;
47 ZlibInflateGuard& operator=(ZlibInflateGuard&&) = delete;
48 ~ZlibInflateGuard() { inflateEnd(zs_); }
49
50 private:
51 z_stream* zs_;
52};
53
55inline Bytes deflate_bytes(const Bytes& src, int level, bool gzip_framing, const char* what) {
56 z_stream zs{};
57 const int window_bits = gzip_framing ? 15 + 16 : 15;
58 if (deflateInit2(&zs, level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
59 throw error(std::string(what) + ": deflateInit2 failed");
60 }
61 const ZlibDeflateGuard guard(&zs);
62 Bytes out(deflateBound(&zs, static_cast<uLong>(src.size())));
63 std::size_t in_pos = 0;
64 std::size_t out_pos = 0;
65 int ret = Z_OK;
66 do {
67 const std::size_t in_step = std::min<std::size_t>(src.size() - in_pos, UINT_MAX);
68 // zlib's C API takes a non-const next_in but never writes through it.
69 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
70 zs.next_in = const_cast<Bytef*>(src.data() + in_pos);
71 zs.avail_in = static_cast<uInt>(in_step);
72 zs.next_out = out.data() + out_pos;
73 zs.avail_out = static_cast<uInt>(std::min<std::size_t>(out.size() - out_pos, UINT_MAX));
74 const bool last_input = in_pos + in_step == src.size();
75 ret = deflate(&zs, last_input ? Z_FINISH : Z_NO_FLUSH);
76 if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
77 throw error(std::string(what) + ": deflate failed (" + std::to_string(ret) + ")");
78 }
79 in_pos += in_step - zs.avail_in;
80 out_pos = static_cast<std::size_t>(zs.next_out - out.data());
81 if (out_pos == out.size() && ret != Z_STREAM_END) {
82 out.resize(out.size() + out.size() / 2 + 64);
83 }
84 } while (ret != Z_STREAM_END);
85 out.resize(out_pos);
86 return out;
87}
88
94inline Bytes inflate_bytes(const Bytes& src, std::optional<std::uint64_t> expected_size,
95 const char* what) {
96 z_stream zs{};
97 if (inflateInit2(&zs, 15 + 32) != Z_OK) {
98 throw error(std::string(what) + ": inflateInit2 failed");
99 }
100 const ZlibInflateGuard guard(&zs);
101 Bytes out;
102 if (expected_size) {
103 out.resize(checked_size(*expected_size, what));
104 } else {
105 out.resize(std::max<std::size_t>(src.size() * 3, 64));
106 }
107 std::size_t in_pos = 0;
108 std::size_t out_pos = 0;
109 int ret = Z_OK;
110 while (true) {
111 const std::size_t in_step = std::min<std::size_t>(src.size() - in_pos, UINT_MAX);
112 // zlib's C API takes a non-const next_in but never writes through it.
113 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
114 zs.next_in = const_cast<Bytef*>(src.data() + in_pos);
115 zs.avail_in = static_cast<uInt>(in_step);
116 zs.next_out = out.data() + out_pos;
117 zs.avail_out = static_cast<uInt>(std::min<std::size_t>(out.size() - out_pos, UINT_MAX));
118 ret = inflate(&zs, Z_NO_FLUSH);
119 if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
120 throw error(std::string(what) + ": corrupt compressed data (zlib error " +
121 std::to_string(ret) + ")");
122 }
123 in_pos += in_step - zs.avail_in;
124 out_pos = static_cast<std::size_t>(zs.next_out - out.data());
125 if (ret == Z_STREAM_END) {
126 break;
127 }
128 if (out_pos == out.size()) {
129 if (expected_size) {
130 throw error(std::string(what) + ": decompressed data exceeds expected " +
131 std::to_string(*expected_size) + " bytes");
132 }
133 out.resize(out.size() * 2);
134 } else if (in_pos == src.size()) {
135 throw error(std::string(what) + ": compressed data is truncated");
136 }
137 }
138 if (in_pos != src.size()) {
139 throw error(std::string(what) + ": trailing garbage after compressed data");
140 }
141 if (expected_size && out_pos != *expected_size) {
142 throw error(std::string(what) + ": decompressed to " + std::to_string(out_pos) +
143 " bytes, expected " + std::to_string(*expected_size));
144 }
145 out.resize(out_pos);
146 return out;
147}
148
149} // namespace zarr::detail
150
151#endif // LIBZARR_CODECS_GZIP_HPP
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42