libzarr
Header-only C++17 Zarr v2/v3, WASM-compatible
Loading...
Searching...
No Matches
codecs_zstd.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2
3#ifndef LIBZARR_CODECS_ZSTD_HPP
4#define LIBZARR_CODECS_ZSTD_HPP
5
6#ifndef LIBZARR_HAS_ZSTD
7#error "libzarr/codecs_zstd.hpp requires libzstd: define LIBZARR_HAS_ZSTD and link zstd"
8#endif
9
10#include <cstdint>
11#include <optional>
12#include <string>
13
14#include <zstd.h>
15
16#include "libzarr/detail/common.hpp"
17#include "libzarr/types.hpp"
18
23
24namespace zarr::detail {
25
26// LIBZARR_ZSTD_DECODE_ONLY omits the compress side so a read-only consumer (a
27// WASM viewer) can link zstd's decompress-only amalgamation (zstddeclib.c)
28// instead of the full library. The decode path stays unconditional; encoding a
29// zstd chunk throws a clear error at the one call site (codecs.hpp).
30#ifndef LIBZARR_ZSTD_DECODE_ONLY
31class ZstdCctxGuard {
32 public:
33 ZstdCctxGuard() : cctx_(ZSTD_createCCtx()) {}
34 ZstdCctxGuard(const ZstdCctxGuard&) = delete;
35 ZstdCctxGuard& operator=(const ZstdCctxGuard&) = delete;
36 ZstdCctxGuard(ZstdCctxGuard&&) = delete;
37 ZstdCctxGuard& operator=(ZstdCctxGuard&&) = delete;
38 ~ZstdCctxGuard() { ZSTD_freeCCtx(cctx_); }
39 [[nodiscard]] ZSTD_CCtx* get() const { return cctx_; }
40
41 private:
42 ZSTD_CCtx* cctx_;
43};
44#endif // LIBZARR_ZSTD_DECODE_ONLY
45
46class ZstdDctxGuard {
47 public:
48 ZstdDctxGuard() : dctx_(ZSTD_createDCtx()) {}
49 ZstdDctxGuard(const ZstdDctxGuard&) = delete;
50 ZstdDctxGuard& operator=(const ZstdDctxGuard&) = delete;
51 ZstdDctxGuard(ZstdDctxGuard&&) = delete;
52 ZstdDctxGuard& operator=(ZstdDctxGuard&&) = delete;
53 ~ZstdDctxGuard() { ZSTD_freeDCtx(dctx_); }
54 [[nodiscard]] ZSTD_DCtx* get() const { return dctx_; }
55
56 private:
57 ZSTD_DCtx* dctx_;
58};
59
60#ifndef LIBZARR_ZSTD_DECODE_ONLY
63inline Bytes zstd_compress_bytes(const Bytes& src, int level, bool checksum, const char* what) {
64 const ZstdCctxGuard cctx;
65 if (cctx.get() == nullptr) {
66 throw error(std::string(what) + ": ZSTD_createCCtx failed");
67 }
68 ZSTD_CCtx_setParameter(cctx.get(), ZSTD_c_compressionLevel, level);
69 ZSTD_CCtx_setParameter(cctx.get(), ZSTD_c_checksumFlag, checksum ? 1 : 0);
70 Bytes out(ZSTD_compressBound(src.size()));
71 const std::size_t n = ZSTD_compress2(cctx.get(), out.data(), out.size(), src.data(), src.size());
72 if (ZSTD_isError(n) != 0) {
73 throw error(std::string(what) + ": zstd compression failed (" + ZSTD_getErrorName(n) + ")");
74 }
75 out.resize(n);
76 return out;
77}
78#endif // LIBZARR_ZSTD_DECODE_ONLY
79
83inline Bytes zstd_decompress_bytes(const Bytes& src, std::optional<std::uint64_t> expected,
84 const char* what) {
85 const unsigned long long content = ZSTD_getFrameContentSize(src.data(), src.size());
86 if (content == ZSTD_CONTENTSIZE_ERROR) {
87 throw error(std::string(what) + ": corrupt zstd frame");
88 }
89 if (content != ZSTD_CONTENTSIZE_UNKNOWN) {
90 if (expected && content != *expected) {
91 throw error(std::string(what) + ": zstd frame decodes to " + std::to_string(content) +
92 " bytes, expected " + std::to_string(*expected));
93 }
94 Bytes out(checked_size(content, what));
95 const std::size_t n = ZSTD_decompress(out.data(), out.size(), src.data(), src.size());
96 if (ZSTD_isError(n) != 0 || n != out.size()) {
97 throw error(std::string(what) + ": zstd decompression failed");
98 }
99 return out;
100 }
101
102 // No recorded content size (seen from streaming writers): decompress
103 // incrementally, growing the output — capped by `expected` when known.
104 const ZstdDctxGuard dctx;
105 if (dctx.get() == nullptr) {
106 throw error(std::string(what) + ": ZSTD_createDCtx failed");
107 }
108 Bytes out;
109 if (expected) {
110 out.resize(checked_size(*expected, what));
111 } else {
112 out.resize(std::max<std::size_t>(src.size() * 3, 64));
113 }
114 ZSTD_inBuffer in{src.data(), src.size(), 0};
115 ZSTD_outBuffer ob{out.data(), out.size(), 0};
116 while (true) {
117 const std::size_t ret = ZSTD_decompressStream(dctx.get(), &ob, &in);
118 if (ZSTD_isError(ret) != 0) {
119 throw error(std::string(what) + ": corrupt zstd data (" + ZSTD_getErrorName(ret) + ")");
120 }
121 if (ret == 0) {
122 break; // frame complete
123 }
124 if (ob.pos == ob.size) {
125 if (expected) {
126 throw error(std::string(what) + ": decompressed data exceeds expected " +
127 std::to_string(*expected) + " bytes");
128 }
129 out.resize(out.size() * 2);
130 ob.dst = out.data();
131 ob.size = out.size();
132 } else if (in.pos == in.size) {
133 throw error(std::string(what) + ": zstd data is truncated");
134 }
135 }
136 if (in.pos != in.size) {
137 throw error(std::string(what) + ": trailing garbage after zstd data");
138 }
139 if (expected && ob.pos != *expected) {
140 throw error(std::string(what) + ": decompressed to " + std::to_string(ob.pos) +
141 " bytes, expected " + std::to_string(*expected));
142 }
143 out.resize(ob.pos);
144 return out;
145}
146
147} // namespace zarr::detail
148
149#endif // LIBZARR_CODECS_ZSTD_HPP
std::vector< std::uint8_t > Bytes
Owned byte buffer used throughout the value-based public API.
Definition types.hpp:42