Project
Loading...
Searching...
No Matches
test_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 "RootFraming.h"
15#include <catch_amalgamated.hpp>
16#include <numeric>
17#include <random>
18
19using namespace o2::aod::native;
20using namespace o2::aod::native::test;
21
22namespace
23{
24std::vector<uint8_t> sampleData(size_t n)
25{
26 // Compressible, but not trivially: a ramp with some noise.
27 std::vector<uint8_t> data(n);
28 std::mt19937 rng(42);
29 for (size_t i = 0; i < n; ++i) {
30 data[i] = (uint8_t)(i / 7 + (rng() % 4));
31 }
32 return data;
33}
34
35auto const allAlgos = {Algo::ZSTD, Algo::ZLIB, Algo::LZMA, Algo::LZ4};
36} // namespace
37
38TEST_CASE("xxh64 matches the reference implementation")
39{
40 std::vector<uint8_t> big(768);
41 for (size_t i = 0; i < big.size(); ++i) {
42 big[i] = i % 256;
43 }
44 CHECK(xxh64({}) == 0xef46db3751d8e999ULL);
45 CHECK(xxh64(std::vector<uint8_t>{'a'}) == 0xd24ec4f1a98c6e5bULL);
46 CHECK(xxh64(std::vector<uint8_t>{'a', 'b', 'c'}) == 0x44bc2cf5ad770999ULL);
47 CHECK(xxh64(big) == 0x8e03c838c596036fULL);
48}
49
50TEST_CASE("each codec round-trips a single chunk")
51{
52 auto data = sampleData(100000);
53 for (auto algo : allAlgos) {
54 auto payload = rootCompress(algo, data);
55 std::vector<uint8_t> out(data.size());
56 REQUIRE(decompressBounded(payload, data.size(), out.data(), out.size()) == data.size());
57 CHECK(out == data);
58 }
59}
60
61TEST_CASE("each codec handles several chunks")
62{
63 auto data = sampleData(50000);
64 for (auto algo : allAlgos) {
65 auto payload = rootCompress(algo, data, 12345);
66 std::vector<uint8_t> out(data.size());
67 decompressBounded(payload, data.size(), out.data(), out.size());
68 CHECK(out == data);
69 }
70}
71
72TEST_CASE("bounded decompression stops exactly at maxOut")
73{
74 auto data = sampleData(40000);
75 for (auto algo : allAlgos) {
76 for (size_t maxOut : {size_t(1), size_t(9999), size_t(10000), size_t(25001), size_t(39999)}) {
77 auto payload = rootCompress(algo, data, 10000);
78 // One guard byte after the bound must stay untouched.
79 std::vector<uint8_t> out(maxOut + 1, 0xAB);
80 REQUIRE(decompressBounded(payload, data.size(), out.data(), maxOut) == maxOut);
81 CHECK(std::equal(out.begin(), out.begin() + maxOut, data.begin()));
82 CHECK(out[maxOut] == 0xAB);
83 }
84 }
85}
86
87TEST_CASE("prefix reads of large chunks stop exactly at the bound")
88{
89 // Several zstd blocks (at most 128 KiB each) per chunk, so that the bound
90 // falls before, at and after block boundaries, and in the last block.
91 auto data = sampleData(1 << 20);
92 constexpr size_t block = 128 * 1024;
93 for (auto algo : allAlgos) {
94 auto payload = rootCompress(algo, data, 600000);
95 for (size_t maxOut : {size_t(1), block - 1, block, block + 1, 2 * block + 17, size_t(599999), size_t(600000), size_t(600001), data.size() - 1}) {
96 INFO("algorithm " << (int)algo << ", bound " << maxOut);
97 std::vector<uint8_t> out(maxOut + 64, 0xAB);
98 PayloadDecompressor d{payload, (uint32_t)data.size(), "memory", 0};
99 d.readPrefix(out.data(), maxOut);
100 CHECK(std::equal(out.begin(), out.begin() + maxOut, data.begin()));
101 CHECK(std::all_of(out.begin() + maxOut, out.end(), [](uint8_t b) { return b == 0xAB; }));
102 }
103 }
104}
105
106TEST_CASE("no read can follow a prefix read")
107{
108 auto data = sampleData(1000);
109 auto payload = rootCompress(Algo::ZSTD, data);
110 std::vector<uint8_t> out(data.size());
111 PayloadDecompressor d{payload, (uint32_t)data.size(), "memory", 0};
112 d.readPrefix(out.data(), 10);
113 CHECK_THROWS_AS(d.read(out.data(), 10), std::logic_error);
114}
115
116TEST_CASE("incremental reads of odd sizes produce the same bytes")
117{
118 auto data = sampleData(70000);
119 for (auto algo : allAlgos) {
120 auto payload = rootCompress(algo, data, 16384);
121 PayloadDecompressor d{payload, (uint32_t)data.size(), "memory", 0};
122 std::vector<uint8_t> out(data.size());
123 size_t pos = 0;
124 for (size_t step = 1; pos < data.size(); step = step * 3 + 1) {
125 auto n = std::min(step, data.size() - pos);
126 d.read(out.data() + pos, n);
127 pos += n;
128 }
129 CHECK(out == data);
130 CHECK(d.produced() == data.size());
131 }
132}
133
134TEST_CASE("truncated payloads raise CorruptData")
135{
136 auto data = sampleData(30000);
137 for (auto algo : allAlgos) {
138 auto payload = rootCompress(algo, data, 10000);
139 std::vector<uint8_t> out(data.size());
140 for (size_t keep : {size_t(0), size_t(5), payload.size() / 2, payload.size() - 1}) {
141 std::vector<uint8_t> truncated(payload.begin(), payload.begin() + keep);
142 CHECK_THROWS_AS(decompressBounded(truncated, data.size(), out.data(), out.size()), CorruptData);
143 }
144 }
145}
146
147TEST_CASE("corrupt payloads raise CorruptData")
148{
149 auto data = sampleData(30000);
150 for (auto algo : allAlgos) {
151 auto payload = rootCompress(algo, data);
152 std::vector<uint8_t> out(data.size());
153 // Flip bits in the middle of the compressed stream.
154 auto corrupt = payload;
155 for (size_t i = corrupt.size() / 3; i < corrupt.size() / 3 + 16; ++i) {
156 corrupt[i] ^= 0x5A;
157 }
158 // zlib and zstd may decode garbage of the right size without noticing; what
159 // matters is that nothing crashes and that detected errors are CorruptData.
160 try {
161 decompressBounded(corrupt, data.size(), out.data(), out.size());
162 } catch (CorruptData const&) {
163 }
164 // A wrong uncompressed size in the header is always detected.
165 auto wrongSize = payload;
166 wrongSize[6] ^= 0x01;
167 CHECK_THROWS_AS(decompressBounded(wrongSize, data.size(), out.data(), out.size()), CorruptData);
168 }
169}
170
171TEST_CASE("LZ4 checksum mismatch is detected")
172{
173 auto data = sampleData(5000);
174 auto payload = rootCompress(Algo::LZ4, data);
175 payload[9] ^= 0xFF; // first checksum byte
176 std::vector<uint8_t> out(data.size());
177 CHECK_THROWS_AS(decompressBounded(payload, data.size(), out.data(), out.size()), CorruptData);
178}
179
180TEST_CASE("legacy and unknown algorithms are unsupported")
181{
182 auto data = sampleData(100);
183 auto payload = rootCompress(Algo::ZLIB, data);
184 std::vector<uint8_t> out(data.size());
185 payload[0] = 'C';
186 payload[1] = 'S';
187 CHECK_THROWS_AS(decompressBounded(payload, data.size(), out.data(), out.size()), UnsupportedLayout);
188 payload[0] = 'Q';
189 payload[1] = 'Q';
190 CHECK_THROWS_AS(decompressBounded(payload, data.size(), out.data(), out.size()), UnsupportedLayout);
191}
192
193TEST_CASE("chunks larger than the object length are rejected")
194{
195 auto data = sampleData(1000);
196 auto payload = rootCompress(Algo::ZSTD, data);
197 std::vector<uint8_t> out(data.size());
198 CHECK_THROWS_AS(decompressBounded(payload, 500, out.data(), out.size()), CorruptData);
199}
int32_t i
uint16_t pos
Definition RawData.h:3
void readPrefix(uint8_t *dst, size_t n)
Definition Codecs.cxx:280
#define CHECK
GLdouble n
Definition glcorearb.h:1982
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLboolean * data
Definition glcorearb.h:298
std::vector< uint8_t > rootCompress(Algo algo, std::span< const uint8_t > in, size_t chunkSize=0xffffff)
Definition RootFraming.h:76
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
uint64_t xxh64(std::span< const uint8_t > data)
XXH64 with seed 0, as used for the LZ4 chunk checksum.
Definition Codecs.cxx:319
uint8_t itsSharedClusterMap uint8_t
TEST_CASE("xxh64 matches the reference implementation")