Project
Loading...
Searching...
No Matches
Codecs.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "AODNative/Codecs.h"
13#include "AODNative/Errors.h"
14#include "Endian.h"
15#include <bit>
16#include <cstring>
17#include <lz4.h>
18#include <lzma.h>
19#include <stdexcept>
20#include <zlib.h>
21// For the buffer-less decompression API, exported by the shared library too.
22#define ZSTD_STATIC_LINKING_ONLY
23#include <zstd.h>
24
25namespace o2::aod::native
26{
27
28namespace
29{
30constexpr size_t kChunkHeaderSize = 9;
31constexpr size_t kLZ4ChecksumSize = 8;
32
33uint32_t le24(uint8_t const* p) { return p[0] | (p[1] << 8) | (p[2] << 16); }
34} // namespace
35
36// One decoder state per codec, created on first use and reset for each chunk.
38 ZSTD_DCtx* zstd = nullptr;
39 z_stream zlib{};
40 bool zlibInit = false;
41 lzma_stream lzma = LZMA_STREAM_INIT;
42 bool lzmaInit = false;
43 bool lz4Partial = false; // the current LZ4 chunk was decoded partially, straight into the destination
44
46 {
47 if (zstd) {
48 ZSTD_freeDCtx(zstd);
49 }
50 if (zlibInit) {
51 inflateEnd(&zlib);
52 }
53 if (lzmaInit) {
54 lzma_end(&lzma);
55 }
56 }
57};
58
59PayloadDecompressor::PayloadDecompressor(std::span<const uint8_t> src, uint32_t objLen, std::string const& file, uint64_t fileOffset)
60 : mSrc(src), mObjLen(objLen), mFile(file), mFileOffset(fileOffset), mCodec(std::make_unique<Codec>())
61{
62}
63
65
66void PayloadDecompressor::nextChunk()
67{
68 auto fail = [this](std::string const& what) { throw CorruptData(mFile, mFileOffset + mSrcPos, what); };
69 if (mSrc.size() - mSrcPos < kChunkHeaderSize) {
70 fail("compressed payload ends after " + std::to_string(mProduced) + " of " + std::to_string(mObjLen) + " bytes");
71 }
72 auto* h = mSrc.data() + mSrcPos;
73 mTag[0] = (char)h[0];
74 mTag[1] = (char)h[1];
75 size_t cSize = le24(h + 3);
76 mChunkOut = le24(h + 6);
77 mChunkDone = 0;
78 if (cSize > mSrc.size() - mSrcPos - kChunkHeaderSize) {
79 fail("compressed chunk of " + std::to_string(cSize) + " bytes past the end of the payload");
80 }
81 if (mChunkOut == 0 || mChunkOut > mObjLen - mProduced) {
82 fail("chunk of " + std::to_string(mChunkOut) + " uncompressed bytes does not fit the object length");
83 }
84 mChunkIn = mSrc.subspan(mSrcPos + kChunkHeaderSize, cSize);
85 auto& codec = *mCodec;
86 mScratch.clear();
87 if (mTag[0] == 'Z' && mTag[1] == 'S') {
88 if (!codec.zstd) {
89 codec.zstd = ZSTD_createDCtx();
90 }
91 ZSTD_DCtx_reset(codec.zstd, ZSTD_reset_session_only);
92 } else if (mTag[0] == 'Z' && mTag[1] == 'L') {
93 if (codec.zlibInit) {
94 inflateReset(&codec.zlib);
95 } else {
96 if (inflateInit(&codec.zlib) != Z_OK) {
97 fail("cannot initialise zlib");
98 }
99 codec.zlibInit = true;
100 }
101 codec.zlib.next_in = (Bytef*)mChunkIn.data();
102 codec.zlib.avail_in = mChunkIn.size();
103 } else if (mTag[0] == 'X' && mTag[1] == 'Z') {
104 if (codec.lzmaInit) {
105 lzma_end(&codec.lzma);
106 }
107 codec.lzma = LZMA_STREAM_INIT;
108 if (lzma_stream_decoder(&codec.lzma, UINT64_MAX, 0) != LZMA_OK) {
109 fail("cannot initialise liblzma");
110 }
111 codec.lzmaInit = true;
112 codec.lzma.next_in = mChunkIn.data();
113 codec.lzma.avail_in = mChunkIn.size();
114 } else if (mTag[0] == 'L' && mTag[1] == '4') {
115 if (cSize < kLZ4ChecksumSize) {
116 fail("LZ4 chunk too small for its checksum");
117 }
118 uint64_t expected = 0;
119 for (size_t i = 0; i < kLZ4ChecksumSize; ++i) {
120 expected = (expected << 8) | mChunkIn[i];
121 }
122 mChunkIn = mChunkIn.subspan(kLZ4ChecksumSize);
123 if (xxh64(mChunkIn) != expected) {
124 fail("LZ4 checksum mismatch");
125 }
126 codec.lz4Partial = false;
127 } else {
128 throw UnsupportedLayout(std::string("compression algorithm \"") + mTag[0] + mTag[1] + "\" in " + mFile);
129 }
130 mSrcPos += kChunkHeaderSize + cSize;
131}
132
133size_t PayloadDecompressor::produceFromChunk(uint8_t* dst, size_t n)
134{
135 auto fail = [this](std::string const& what) { throw CorruptData(mFile, mFileOffset + mSrcPos, what); };
136 auto& codec = *mCodec;
137 bool wholeChunk = mChunkDone == 0 && n == mChunkOut;
138 if (mTag[0] == 'Z' && mTag[1] == 'S') {
139 if (wholeChunk) {
140 auto r = ZSTD_decompressDCtx(codec.zstd, dst, n, mChunkIn.data(), mChunkIn.size());
141 if (ZSTD_isError(r) || r != n) {
142 fail(std::string("zstd: ") + (ZSTD_isError(r) ? ZSTD_getErrorName(r) : "size mismatch"));
143 }
144 return n;
145 }
146 if (mPrefixOnly && mChunkDone == 0) {
147 return zstdPrefix(dst, n);
148 }
149 // Resume where the previous read stopped: the input position is kept in mChunkIn.
150 // The streaming API keeps its own window buffer, sized by the writer's window.
151 ZSTD_outBuffer out{dst, n, 0};
152 while (out.pos < out.size) {
153 ZSTD_inBuffer in{mChunkIn.data(), mChunkIn.size(), 0};
154 auto r = ZSTD_decompressStream(codec.zstd, &out, &in);
155 mChunkIn = mChunkIn.subspan(in.pos);
156 if (ZSTD_isError(r)) {
157 fail(std::string("zstd: ") + ZSTD_getErrorName(r));
158 }
159 if (out.pos < out.size && (r == 0 || (in.pos == 0 && mChunkIn.empty()))) {
160 fail("zstd chunk ended early");
161 }
162 }
163 return n;
164 }
165 if (mTag[0] == 'Z' && mTag[1] == 'L') {
166 auto& z = codec.zlib;
167 z.next_out = dst;
168 z.avail_out = n;
169 while (z.avail_out > 0) {
170 auto r = inflate(&z, Z_NO_FLUSH);
171 if (r == Z_STREAM_END && z.avail_out > 0) {
172 fail("zlib chunk ended early");
173 }
174 if (r != Z_OK && r != Z_STREAM_END) {
175 fail(std::string("zlib: ") + (z.msg ? z.msg : std::to_string(r)));
176 }
177 }
178 return n;
179 }
180 if (mTag[0] == 'X' && mTag[1] == 'Z') {
181 auto& s = codec.lzma;
182 s.next_out = dst;
183 s.avail_out = n;
184 while (s.avail_out > 0) {
185 auto r = lzma_code(&s, LZMA_RUN);
186 if (r == LZMA_STREAM_END && s.avail_out > 0) {
187 fail("xz chunk ended early");
188 }
189 if (r != LZMA_OK && r != LZMA_STREAM_END) {
190 fail("liblzma error " + std::to_string(r));
191 }
192 }
193 return n;
194 }
195 // LZ4 block format cannot be resumed. Decode partially straight into the
196 // destination when this read starts the chunk, which is the common case of
197 // stopping at the end of the value region. Should a later read need the
198 // rest, decode the whole chunk into scratch memory.
199 if (wholeChunk) {
200 // A whole chunk must use all of its input and produce exactly its size.
201 auto r = LZ4_decompress_safe((char const*)mChunkIn.data(), (char*)dst, mChunkIn.size(), n);
202 if (r < 0 || (size_t)r != n) {
203 fail("LZ4 chunk size mismatch");
204 }
205 return n;
206 }
207 if (mScratch.empty() && mChunkDone == 0) {
208 auto r = LZ4_decompress_safe_partial((char const*)mChunkIn.data(), (char*)dst, mChunkIn.size(), n, n);
209 if (r < 0 || (size_t)r != n) {
210 fail("LZ4 decompression failed");
211 }
212 codec.lz4Partial = true;
213 return n;
214 }
215 if (mScratch.empty()) {
216 mScratch.resize(mChunkOut);
217 auto r = LZ4_decompress_safe((char const*)mChunkIn.data(), (char*)mScratch.data(), mChunkIn.size(), mChunkOut);
218 if (r < 0 || (size_t)r != mChunkOut) {
219 fail("LZ4 decompression failed");
220 }
221 }
222 std::memcpy(dst, mScratch.data() + mChunkDone, n);
223 return n;
224}
225
226// Decodes the first @a n bytes of a zstd chunk straight into @a dst, with the
227// buffer-less API: the destination itself is zstd's history, so no window
228// buffer is allocated. Blocks are written in place while a whole block (at most
229// ZSTD_BLOCKSIZE_MAX) still fits; the last ones go to a fixed scratch, contiguous
230// with each other, and only the needed bytes are copied out.
231size_t PayloadDecompressor::zstdPrefix(uint8_t* dst, size_t n)
232{
233 auto fail = [this](std::string const& what) { throw CorruptData(mFile, mFileOffset + mSrcPos, what); };
234 auto* dctx = mCodec->zstd;
235 if (ZSTD_isError(ZSTD_decompressBegin(dctx))) {
236 fail("cannot initialise zstd");
237 }
238 auto in = mChunkIn;
239 size_t written = 0;
240 bool inScratch = false;
241 size_t scratchUsed = 0;
242 while (written < n) {
243 auto need = ZSTD_nextSrcSizeToDecompress(dctx);
244 if (need == 0) {
245 fail("zstd chunk ended early");
246 }
247 if (need > in.size()) {
248 fail("truncated zstd chunk");
249 }
250 size_t r;
251 if (!inScratch && n - written >= ZSTD_BLOCKSIZE_MAX) {
252 r = ZSTD_decompressContinue(dctx, dst + written, n - written, in.data(), need);
253 if (!ZSTD_isError(r)) {
254 written += r;
255 }
256 } else {
257 if (!inScratch) {
258 mScratch.resize(2 * ZSTD_BLOCKSIZE_MAX);
259 inScratch = true;
260 }
261 r = ZSTD_decompressContinue(dctx, mScratch.data() + scratchUsed, mScratch.size() - scratchUsed, in.data(), need);
262 if (!ZSTD_isError(r)) {
263 scratchUsed += r;
264 if (written + scratchUsed >= n) {
265 std::memcpy(dst + written, mScratch.data(), n - written);
266 written = n;
267 }
268 }
269 }
270 if (ZSTD_isError(r)) {
271 fail(std::string("zstd: ") + ZSTD_getErrorName(r));
272 }
273 in = in.subspan(need);
274 }
275 mScratch.clear();
276 mScratch.shrink_to_fit();
277 return n;
278}
279
281{
282 if (mProduced != 0) {
283 throw std::logic_error("readPrefix must be the first read of a payload");
284 }
285 mPrefixOnly = true;
286 read(dst, n);
287}
288
289void PayloadDecompressor::read(uint8_t* dst, size_t n)
290{
291 if (mPrefixOnly && mProduced != 0) {
292 throw std::logic_error("no read can follow readPrefix");
293 }
294 if (n > mObjLen - mProduced) {
295 throw CorruptData(mFile, mFileOffset, "read of " + std::to_string(n) + " bytes past the object length " + std::to_string(mObjLen));
296 }
297 while (n > 0) {
298 if (mChunkDone == mChunkOut) {
299 nextChunk();
300 }
301 auto k = std::min(n, mChunkOut - mChunkDone);
302 produceFromChunk(dst, k);
303 dst += k;
304 n -= k;
305 mChunkDone += k;
306 mProduced += k;
307 }
308}
309
310size_t decompressBounded(std::span<const uint8_t> src, uint32_t objLen, uint8_t* dst, size_t maxOut,
311 std::string const& file, uint64_t fileOffset)
312{
313 auto n = std::min<size_t>(objLen, maxOut);
314 PayloadDecompressor d{src, objLen, file, fileOffset};
315 d.readPrefix(dst, n);
316 return n;
317}
318
319uint64_t xxh64(std::span<const uint8_t> data)
320{
321 constexpr uint64_t P1 = 11400714785074694791ULL;
322 constexpr uint64_t P2 = 14029467366897019727ULL;
323 constexpr uint64_t P3 = 1609587929392839161ULL;
324 constexpr uint64_t P4 = 9650029242287828579ULL;
325 constexpr uint64_t P5 = 2870177450012600261ULL;
326 auto read64 = [](uint8_t const* p) {
327 uint64_t v;
328 std::memcpy(&v, p, 8);
329 return std::endian::native == std::endian::little ? v : bswap(v);
330 };
331 auto read32 = [](uint8_t const* p) {
332 uint32_t v;
333 std::memcpy(&v, p, 4);
334 return std::endian::native == std::endian::little ? v : bswap(v);
335 };
336 auto round = [](uint64_t acc, uint64_t input) {
337 acc += input * P2;
338 return std::rotl(acc, 31) * P1;
339 };
340 auto merge = [&round](uint64_t acc, uint64_t v) {
341 acc ^= round(0, v);
342 return acc * P1 + P4;
343 };
344
345 auto const* p = data.data();
346 auto const* end = p + data.size();
347 uint64_t h;
348 if (data.size() >= 32) {
349 uint64_t v1 = P1 + P2, v2 = P2, v3 = 0, v4 = -P1;
350 for (; end - p >= 32; p += 32) {
351 v1 = round(v1, read64(p));
352 v2 = round(v2, read64(p + 8));
353 v3 = round(v3, read64(p + 16));
354 v4 = round(v4, read64(p + 24));
355 }
356 h = std::rotl(v1, 1) + std::rotl(v2, 7) + std::rotl(v3, 12) + std::rotl(v4, 18);
357 h = merge(merge(merge(merge(h, v1), v2), v3), v4);
358 } else {
359 h = P5;
360 }
361 h += data.size();
362 for (; end - p >= 8; p += 8) {
363 h ^= round(0, read64(p));
364 h = std::rotl(h, 27) * P1 + P4;
365 }
366 if (end - p >= 4) {
367 h ^= (uint64_t)read32(p) * P1;
368 h = std::rotl(h, 23) * P2 + P3;
369 p += 4;
370 }
371 for (; p < end; ++p) {
372 h ^= *p * P5;
373 h = std::rotl(h, 11) * P1;
374 }
375 h ^= h >> 33;
376 h *= P2;
377 h ^= h >> 29;
378 h *= P3;
379 h ^= h >> 32;
380 return h;
381}
382
383} // namespace o2::aod::native
int32_t i
void merge(Options const &options)
Class for time synchronization of RawReader instances.
PayloadDecompressor(std::span< const uint8_t > src, uint32_t objLen, std::string const &file, uint64_t fileOffset)
Definition Codecs.cxx:59
void read(uint8_t *dst, size_t n)
Definition Codecs.cxx:289
void readPrefix(uint8_t *dst, size_t n)
Definition Codecs.cxx:280
GLdouble n
Definition glcorearb.h:1982
GLenum src
Definition glcorearb.h:1767
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLenum GLenum dst
Definition glcorearb.h:1767
GLboolean * data
Definition glcorearb.h:298
GLfloat GLfloat v1
Definition glcorearb.h:812
GLboolean r
Definition glcorearb.h:1233
GLfloat GLfloat GLfloat GLfloat v3
Definition glcorearb.h:814
GLfloat GLfloat GLfloat v2
Definition glcorearb.h:813
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
size_t decompressBounded(std::span< const uint8_t > src, uint32_t objLen, uint8_t *dst, size_t maxOut, std::string const &file="<memory>", uint64_t fileOffset=0)
Definition Codecs.cxx:310
constexpr T bswap(T v)
Definition Endian.h:22
uint64_t xxh64(std::span< const uint8_t > data)
XXH64 with seed 0, as used for the LZ4 chunk checksum.
Definition Codecs.cxx:319
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::map< std::string, ID > expected