/src/c-blosc2/plugins/codecs/ndlz/ndlz4x4.c
Line | Count | Source |
1 | | /********************************************************************* |
2 | | Blosc - Blocked Shuffling and Compression Library |
3 | | |
4 | | Copyright (c) 2021 Blosc Development Team <blosc@blosc.org> |
5 | | https://blosc.org |
6 | | License: BSD 3-Clause (see LICENSE.txt) |
7 | | |
8 | | See LICENSE.txt for details about copyright and rights to use. |
9 | | **********************************************************************/ |
10 | | |
11 | | /********************************************************************* |
12 | | This codec is meant to leverage multidimensionality for getting |
13 | | better compression ratios. The idea is to look for similarities |
14 | | in places that are closer in a euclidean metric, not the typical |
15 | | linear one. |
16 | | **********************************************************************/ |
17 | | |
18 | | #include "ndlz4x4.h" |
19 | | #include "xxhash.h" |
20 | | #include "b2nd.h" |
21 | | |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | /* |
26 | | * Give hints to the compiler for branch prediction optimization. |
27 | | */ |
28 | | #if defined(__GNUC__) && (__GNUC__ > 2) |
29 | | #define NDLZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1)) |
30 | 3.74k | #define NDLZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0)) |
31 | | #else |
32 | | #define NDLZ_EXPECT_CONDITIONAL(c) (c) |
33 | | #define NDLZ_UNEXPECT_CONDITIONAL(c) (c) |
34 | | #endif |
35 | | |
36 | | /* |
37 | | * Use inlined functions for supported systems. |
38 | | */ |
39 | | #if defined(_MSC_VER) && !defined(__cplusplus) /* Visual Studio */ |
40 | | #define inline __inline /* Visual C is not C99, but supports some kind of inline */ |
41 | | #endif |
42 | | |
43 | | #define MAX_COPY 32U |
44 | 0 | #define MAX_DISTANCE 65535 |
45 | | |
46 | | |
47 | | #ifdef BLOSC_STRICT_ALIGN |
48 | | #define NDLZ_READU16(p) ((p)[0] | (p)[1]<<8) |
49 | | #define NDLZ_READU32(p) ((p)[0] | (p)[1]<<8 | (p)[2]<<16 | (p)[3]<<24) |
50 | | #else |
51 | | #define NDLZ_READU16(p) *((const uint16_t*)(p)) |
52 | | #define NDLZ_READU32(p) *((const uint32_t*)(p)) |
53 | | #endif |
54 | | |
55 | | #define HASH_LOG (12) |
56 | | |
57 | | |
58 | | int ndlz4_compress(const uint8_t *input, int32_t input_len, uint8_t *output, int32_t output_len, |
59 | 0 | uint8_t meta, blosc2_cparams *cparams) { |
60 | 0 | BLOSC_UNUSED_PARAM(meta); |
61 | 0 | BLOSC_ERROR_NULL(cparams, BLOSC2_ERROR_NULL_POINTER); |
62 | 0 | BLOSC_ERROR_NULL(cparams->schunk, BLOSC2_ERROR_NULL_POINTER); |
63 | 0 | uint8_t *smeta; |
64 | 0 | int32_t smeta_len; |
65 | |
|
66 | 0 | if (blosc2_meta_get(cparams->schunk, "b2nd", &smeta, &smeta_len) < 0) { |
67 | 0 | BLOSC_TRACE_ERROR("b2nd layer not found!"); |
68 | 0 | return BLOSC2_ERROR_FAILURE; |
69 | 0 | } |
70 | | |
71 | 0 | int8_t ndim; |
72 | 0 | int64_t *shape = malloc(B2ND_MAX_DIM * sizeof(int64_t)); |
73 | 0 | int32_t *chunkshape = malloc(B2ND_MAX_DIM * sizeof(int32_t)); |
74 | 0 | int32_t *blockshape = malloc(B2ND_MAX_DIM * sizeof(int32_t)); |
75 | 0 | int deserialize_rc = b2nd_deserialize_meta(smeta, smeta_len, &ndim, shape, chunkshape, blockshape, NULL, NULL); |
76 | 0 | free(smeta); |
77 | |
|
78 | 0 | if (deserialize_rc < 0 || ndim != 2) { |
79 | 0 | free(shape); |
80 | 0 | free(chunkshape); |
81 | 0 | free(blockshape); |
82 | 0 | BLOSC_TRACE_ERROR("This codec only works for ndim = 2"); |
83 | 0 | return BLOSC2_ERROR_FAILURE; |
84 | 0 | } |
85 | | |
86 | 0 | if (input_len != (blockshape[0] * blockshape[1])) { |
87 | 0 | free(shape); |
88 | 0 | free(chunkshape); |
89 | 0 | free(blockshape); |
90 | 0 | BLOSC_TRACE_ERROR("Length not equal to blocksize"); |
91 | 0 | return BLOSC2_ERROR_FAILURE; |
92 | 0 | } |
93 | | |
94 | 0 | if (NDLZ_UNEXPECT_CONDITIONAL(output_len < (int) (1 + ndim * sizeof(int32_t)))) { |
95 | 0 | free(shape); |
96 | 0 | free(chunkshape); |
97 | 0 | free(blockshape); |
98 | 0 | BLOSC_TRACE_ERROR("Output too small"); |
99 | 0 | return BLOSC2_ERROR_FAILURE; |
100 | 0 | } |
101 | | |
102 | 0 | uint8_t *ip = (uint8_t *) input; |
103 | 0 | uint8_t *op = (uint8_t *) output; |
104 | 0 | uint8_t *op_limit; |
105 | 0 | uint32_t hval, hash_cell; |
106 | 0 | uint32_t hash_triple[2] = {0}; |
107 | 0 | uint32_t hash_pair[3] = {0}; |
108 | 0 | uint8_t bufarea[16]; |
109 | 0 | uint8_t *buf_cell = bufarea; |
110 | 0 | uint8_t buf_triple[12]; |
111 | 0 | uint8_t buf_pair[8]; |
112 | 0 | uint8_t *buf_aux; |
113 | 0 | uint32_t tab_cell[1U << 12U] = {0}; |
114 | 0 | uint32_t tab_triple[1U << 12U] = {0}; |
115 | 0 | uint32_t tab_pair[1U << 12U] = {0}; |
116 | 0 | uint32_t update_triple[2] = {0}; |
117 | 0 | uint32_t update_pair[3] = {0}; |
118 | | |
119 | | // Minimum cratios before issuing and _early giveup_ |
120 | | // Remind that ndlz is not meant for cratios <= 2 (too costly to decompress) |
121 | |
|
122 | 0 | op_limit = op + output_len; |
123 | | |
124 | | // Initialize the hash table to distances of 0 |
125 | 0 | for (unsigned i = 0; i < (1U << 12U); i++) { |
126 | 0 | tab_cell[i] = 0; |
127 | 0 | } |
128 | | |
129 | | /* input and output buffer cannot be less than 16 and 66 bytes or we can get into trouble */ |
130 | 0 | int overhead = 17 + (blockshape[0] * blockshape[1] / 16 - 1) * 2; |
131 | 0 | if (input_len < 16 || output_len < overhead) { |
132 | 0 | free(shape); |
133 | 0 | free(chunkshape); |
134 | 0 | free(blockshape); |
135 | 0 | BLOSC_TRACE_ERROR("Incorrect length or maxout"); |
136 | 0 | return 0; |
137 | 0 | } |
138 | | |
139 | 0 | uint8_t *obase = op; |
140 | | |
141 | | /* we start with literal copy */ |
142 | 0 | *op++ = ndim; |
143 | 0 | memcpy(op, &blockshape[0], 4); |
144 | 0 | op += 4; |
145 | 0 | memcpy(op, &blockshape[1], 4); |
146 | 0 | op += 4; |
147 | |
|
148 | 0 | uint32_t i_stop[2]; |
149 | 0 | for (int i = 0; i < 2; ++i) { |
150 | 0 | i_stop[i] = (blockshape[i] + 3) / 4; |
151 | 0 | } |
152 | | |
153 | | /* main loop */ |
154 | 0 | uint32_t padding[2]; |
155 | 0 | uint32_t ii[2]; |
156 | 0 | for (ii[0] = 0; ii[0] < i_stop[0]; ++ii[0]) { |
157 | 0 | for (ii[1] = 0; ii[1] < i_stop[1]; ++ii[1]) { // for each cell |
158 | 0 | uint8_t token; |
159 | 0 | for (int h = 0; h < 2; h++) { // new cell -> new possible references |
160 | 0 | update_triple[h] = 0; |
161 | 0 | update_pair[h] = 0; |
162 | 0 | } |
163 | 0 | update_pair[2] = 0; |
164 | |
|
165 | 0 | if (NDLZ_UNEXPECT_CONDITIONAL(op + 16 + 1 > op_limit)) { |
166 | 0 | free(shape); |
167 | 0 | free(chunkshape); |
168 | 0 | free(blockshape); |
169 | 0 | return 0; |
170 | 0 | } |
171 | | |
172 | 0 | uint32_t orig = ii[0] * 4 * blockshape[1] + ii[1] * 4; |
173 | 0 | if (((blockshape[0] % 4 != 0) && (ii[0] == i_stop[0] - 1)) || |
174 | 0 | ((blockshape[1] % 4 != 0) && (ii[1] == i_stop[1] - 1))) { |
175 | 0 | token = 0; // padding -> literal copy |
176 | 0 | *op++ = token; |
177 | 0 | if (ii[0] == i_stop[0] - 1) { |
178 | 0 | padding[0] = (blockshape[0] % 4 == 0) ? 4 : blockshape[0] % 4; |
179 | 0 | } else { |
180 | 0 | padding[0] = 4; |
181 | 0 | } |
182 | 0 | if (ii[1] == i_stop[1] - 1) { |
183 | 0 | padding[1] = (blockshape[1] % 4 == 0) ? 4 : blockshape[1] % 4; |
184 | 0 | } else { |
185 | 0 | padding[1] = 4; |
186 | 0 | } |
187 | 0 | for (uint32_t i = 0; i < padding[0]; i++) { |
188 | 0 | memcpy(op, &ip[orig + i * blockshape[1]], padding[1]); |
189 | 0 | op += padding[1]; |
190 | 0 | } |
191 | 0 | } else { |
192 | 0 | for (uint64_t i = 0; i < 4; i++) { // fill cell buffer |
193 | 0 | uint64_t ind = orig + i * blockshape[1]; |
194 | 0 | memcpy(buf_cell, &ip[ind], 4); |
195 | 0 | buf_cell += 4; |
196 | 0 | } |
197 | 0 | buf_cell -= 16; |
198 | |
|
199 | 0 | const uint8_t *ref; |
200 | 0 | uint32_t distance; |
201 | 0 | uint8_t *anchor = op; /* comparison starting-point */ |
202 | | |
203 | | /* find potential match */ |
204 | 0 | hash_cell = XXH32(buf_cell, 16, 1); // calculate cell hash |
205 | 0 | hash_cell >>= 32U - 12U; |
206 | 0 | ref = obase + tab_cell[hash_cell]; |
207 | | |
208 | | /* calculate distance to the match */ |
209 | 0 | if (tab_cell[hash_cell] == 0) { |
210 | 0 | distance = 0; |
211 | 0 | } else { |
212 | 0 | bool same = true; |
213 | 0 | buf_aux = obase + tab_cell[hash_cell]; |
214 | 0 | for (int i = 0; i < 16; i++) { |
215 | 0 | if (buf_cell[i] != buf_aux[i]) { |
216 | 0 | same = false; |
217 | 0 | break; |
218 | 0 | } |
219 | 0 | } |
220 | 0 | if (same) { |
221 | 0 | distance = (int32_t) (anchor - ref); |
222 | 0 | } else { |
223 | 0 | distance = 0; |
224 | 0 | } |
225 | 0 | } |
226 | |
|
227 | 0 | bool alleq = true; |
228 | 0 | for (int i = 1; i < 16; i++) { |
229 | 0 | if (buf_cell[i] != buf_cell[0]) { |
230 | 0 | alleq = false; |
231 | 0 | break; |
232 | 0 | } |
233 | 0 | } |
234 | 0 | if (alleq) { // all elements of the cell equal |
235 | 0 | token = (uint8_t) (1U << 6U); |
236 | 0 | *op++ = token; |
237 | 0 | *op++ = buf_cell[0]; |
238 | |
|
239 | 0 | } else if (distance == 0 || (distance >= MAX_DISTANCE)) { // no cell match |
240 | 0 | bool literal = true; |
241 | | |
242 | | // 2 rows pairs matches |
243 | 0 | for (int j = 1; j < 4; j++) { |
244 | 0 | memcpy(buf_pair, buf_cell, 4); |
245 | 0 | memcpy(&buf_pair[4], &buf_cell[j * 4], 4); |
246 | 0 | hval = XXH32(buf_pair, 8, 1); // calculate rows pair hash |
247 | 0 | hval >>= 32U - 12U; |
248 | 0 | ref = obase + tab_pair[hval]; |
249 | | /* calculate distance to the match */ |
250 | 0 | bool same = true; |
251 | 0 | uint16_t offset; |
252 | 0 | if (tab_pair[hval] != 0) { |
253 | 0 | buf_aux = obase + tab_pair[hval]; |
254 | 0 | for (int k = 0; k < 8; k++) { |
255 | 0 | if (buf_pair[k] != buf_aux[k]) { |
256 | 0 | same = false; |
257 | 0 | break; |
258 | 0 | } |
259 | 0 | } |
260 | 0 | offset = (uint16_t) (anchor - obase - tab_pair[hval]); |
261 | 0 | } else { |
262 | 0 | same = false; |
263 | 0 | } |
264 | 0 | if (same) { |
265 | 0 | distance = (int32_t) (anchor - ref); |
266 | 0 | } else { |
267 | 0 | distance = 0; |
268 | 0 | } |
269 | 0 | if ((distance != 0) && (distance < MAX_DISTANCE)) { /* rows pair match */ |
270 | 0 | int k, m, l = -1; |
271 | 0 | for (k = 1; k < 4; k++) { |
272 | 0 | if (k != j) { |
273 | 0 | if (l == -1) { |
274 | 0 | l = k; |
275 | 0 | } else { |
276 | 0 | m = k; |
277 | 0 | } |
278 | 0 | } |
279 | 0 | } |
280 | 0 | memcpy(buf_pair, &buf_cell[l * 4], 4); |
281 | 0 | memcpy(&buf_pair[4], &buf_cell[m * 4], 4); |
282 | 0 | hval = XXH32(buf_pair, 8, 1); // calculate rows pair hash |
283 | 0 | hval >>= 32U - 12U; |
284 | 0 | ref = obase + tab_pair[hval]; |
285 | 0 | same = true; |
286 | 0 | if (tab_pair[hval] != 0) { |
287 | 0 | buf_aux = obase + tab_pair[hval]; |
288 | 0 | for (k = 0; k < 8; k++) { |
289 | 0 | if (buf_pair[k] != buf_aux[k]) { |
290 | 0 | same = false; |
291 | 0 | break; |
292 | 0 | } |
293 | 0 | } |
294 | 0 | } else { |
295 | 0 | same = false; |
296 | 0 | } |
297 | 0 | if (same) { |
298 | 0 | distance = (int32_t) (anchor + l * 4 - ref); |
299 | 0 | } else { |
300 | 0 | distance = 0; |
301 | 0 | } |
302 | 0 | if ((distance != 0) && (distance < MAX_DISTANCE)) { /* 2 pair matches */ |
303 | 0 | literal = false; |
304 | 0 | token = (uint8_t) ((1U << 5U) | (j << 3U)); |
305 | 0 | *op++ = token; |
306 | 0 | uint16_t offset_2 = (uint16_t) (anchor - obase - tab_pair[hval]); |
307 | 0 | *(uint16_t *) op = offset; |
308 | 0 | op += sizeof(offset); |
309 | 0 | *(uint16_t *) op = offset_2; |
310 | 0 | op += sizeof(offset_2); |
311 | 0 | goto match; |
312 | 0 | } |
313 | 0 | } |
314 | 0 | } |
315 | | |
316 | | // rows triples |
317 | 0 | for (int i = 0; i < 2; i++) { |
318 | 0 | memcpy(buf_triple, &buf_cell[i * 4], 4); |
319 | 0 | for (int j = i + 1; j < 3; j++) { |
320 | 0 | memcpy(&buf_triple[4], &buf_cell[j * 4], 4); |
321 | 0 | for (int k = j + 1; k < 4; k++) { |
322 | 0 | memcpy(&buf_triple[8], &buf_cell[k * 4], 4); |
323 | 0 | hval = XXH32(buf_triple, 12, 1); // calculate triple hash |
324 | 0 | hval >>= 32U - 12U; |
325 | | /* calculate distance to the match */ |
326 | 0 | bool same = true; |
327 | 0 | uint16_t offset; |
328 | 0 | if (tab_triple[hval] != 0) { |
329 | 0 | buf_aux = obase + tab_triple[hval]; |
330 | 0 | for (int l = 0; l < 12; l++) { |
331 | 0 | if (buf_triple[l] != buf_aux[l]) { |
332 | 0 | same = false; |
333 | 0 | break; |
334 | 0 | } |
335 | 0 | } |
336 | 0 | offset = (uint16_t) (anchor - obase - tab_triple[hval]); |
337 | 0 | } else { |
338 | 0 | same = false; |
339 | 0 | if ((j - i == 1) && (k - j == 1)) { |
340 | 0 | update_triple[i] = (uint32_t) (anchor + 1 + i * 4 - obase); /* update hash table */ |
341 | 0 | hash_triple[i] = hval; |
342 | 0 | } |
343 | 0 | } |
344 | 0 | ref = obase + tab_triple[hval]; |
345 | |
|
346 | 0 | if (same) { |
347 | 0 | distance = (int32_t) (anchor + i * 4 - ref); |
348 | 0 | } else { |
349 | 0 | distance = 0; |
350 | 0 | } |
351 | 0 | if ((distance != 0) && (distance < MAX_DISTANCE)) { |
352 | 0 | literal = false; |
353 | 0 | if (i == 1) { |
354 | 0 | token = (uint8_t) (7U << 5U); |
355 | 0 | } else { |
356 | 0 | token = (uint8_t) ((7U << 5U) | ((j + k - 2) << 3U)); |
357 | 0 | } |
358 | 0 | *op++ = token; |
359 | 0 | memcpy(op, &offset, 2); |
360 | 0 | op += 2; |
361 | 0 | for (int l = 0; l < 4; l++) { |
362 | 0 | if ((l != i) && (l != j) && (l != k)) { |
363 | 0 | memcpy(op, &buf_cell[4 * l], 4); |
364 | 0 | op += 4; |
365 | 0 | goto match; |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | 0 | } |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | // rows pairs |
374 | 0 | for (int i = 0; i < 3; i++) { |
375 | 0 | memcpy(buf_pair, &buf_cell[i * 4], 4); |
376 | 0 | for (int j = i + 1; j < 4; j++) { |
377 | 0 | memcpy(&buf_pair[4], &buf_cell[j * 4], 4); |
378 | 0 | hval = XXH32(buf_pair, 8, 1); // calculate rows pair hash |
379 | 0 | hval >>= 32U - 12U; |
380 | 0 | ref = obase + tab_pair[hval]; |
381 | | /* calculate distance to the match */ |
382 | 0 | bool same = true; |
383 | 0 | uint16_t offset; |
384 | 0 | if (tab_pair[hval] != 0) { |
385 | 0 | buf_aux = obase + tab_pair[hval]; |
386 | 0 | for (int k = 0; k < 8; k++) { |
387 | 0 | if (buf_pair[k] != buf_aux[k]) { |
388 | 0 | same = false; |
389 | 0 | break; |
390 | 0 | } |
391 | 0 | } |
392 | 0 | offset = (uint16_t) (anchor - obase - tab_pair[hval]); |
393 | 0 | } else { |
394 | 0 | same = false; |
395 | 0 | if (j - i == 1) { |
396 | 0 | update_pair[i] = (uint32_t) (anchor + 1 + i * 4 - obase); /* update hash table */ |
397 | 0 | hash_pair[i] = hval; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | if (same) { |
401 | 0 | distance = (int32_t) (anchor + i * 4 - ref); |
402 | 0 | } else { |
403 | 0 | distance = 0; |
404 | 0 | } |
405 | 0 | if ((distance != 0) && (distance < MAX_DISTANCE)) { /* rows pair match */ |
406 | 0 | literal = false; |
407 | 0 | if (i == 2) { |
408 | 0 | token = (uint8_t) (1U << 7U); |
409 | 0 | } else { |
410 | 0 | token = (uint8_t) ((1U << 7U) | (i << 5U) | (j << 3U)); |
411 | 0 | } |
412 | 0 | *op++ = token; |
413 | 0 | memcpy(op, &offset, 2); |
414 | 0 | op += 2; |
415 | 0 | for (int k = 0; k < 4; k++) { |
416 | 0 | if ((k != i) && (k != j)) { |
417 | 0 | memcpy(op, &buf_cell[4 * k], 4); |
418 | 0 | op += 4; |
419 | 0 | } |
420 | 0 | } |
421 | 0 | goto match; |
422 | 0 | } |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | 0 | match: |
427 | 0 | if (literal) { |
428 | 0 | tab_cell[hash_cell] = (uint32_t) (anchor + 1 - obase); /* update hash tables */ |
429 | 0 | if (update_triple[0] != 0) { |
430 | 0 | for (int h = 0; h < 2; h++) { |
431 | 0 | tab_triple[hash_triple[h]] = update_triple[h]; |
432 | 0 | } |
433 | 0 | } |
434 | 0 | if (update_pair[0] != 0) { |
435 | 0 | for (int h = 0; h < 3; h++) { |
436 | 0 | tab_pair[hash_pair[h]] = update_pair[h]; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | token = 0; |
440 | 0 | *op++ = token; |
441 | 0 | memcpy(op, buf_cell, 16); |
442 | 0 | op += 16; |
443 | 0 | } |
444 | |
|
445 | 0 | } else { // cell match |
446 | 0 | token = (uint8_t) ((1U << 7U) | (1U << 6U)); |
447 | 0 | *op++ = token; |
448 | 0 | uint16_t offset = (uint16_t) (anchor - obase - tab_cell[hash_cell]); |
449 | 0 | memcpy(op, &offset, 2); |
450 | 0 | op += 2; |
451 | 0 | } |
452 | |
|
453 | 0 | } |
454 | 0 | if ((op - obase) > input_len) { |
455 | 0 | BLOSC_TRACE_ERROR("Compressed data is bigger than input!"); |
456 | 0 | return 0; |
457 | 0 | } |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | 0 | free(shape); |
462 | 0 | free(chunkshape); |
463 | 0 | free(blockshape); |
464 | |
|
465 | 0 | return (int) (op - obase); |
466 | 0 | } |
467 | | |
468 | | |
469 | | // See https://habr.com/en/company/yandex/blog/457612/ |
470 | | #ifdef __AVX2__ |
471 | | |
472 | | #if defined(_MSC_VER) |
473 | | #define ALIGNED_(x) __declspec(align(x)) |
474 | | #else |
475 | | #if defined(__GNUC__) |
476 | | #define ALIGNED_(x) __attribute__ ((aligned(x))) |
477 | | #endif |
478 | | #endif |
479 | | #define ALIGNED_TYPE_(t, x) t ALIGNED_(x) |
480 | | |
481 | | static unsigned char* copy_match_16(unsigned char *op, const unsigned char *match, int32_t len) |
482 | | { |
483 | | size_t offset = op - match; |
484 | | while (len >= 16) { |
485 | | |
486 | | static const ALIGNED_TYPE_(uint8_t, 16) masks[] = |
487 | | { |
488 | | 0, 1, 2, 1, 4, 1, 4, 2, 8, 7, 6, 5, 4, 3, 2, 1, // offset = 0, not used as mask, but for shift |
489 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // offset = 1 |
490 | | 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, |
491 | | 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, |
492 | | 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, |
493 | | 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, |
494 | | 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, |
495 | | 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, |
496 | | 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, |
497 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, |
498 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, |
499 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, |
500 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, |
501 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, |
502 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1, |
503 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, |
504 | | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // offset = 16 |
505 | | }; |
506 | | |
507 | | _mm_storeu_si128((__m128i *)(op), |
508 | | _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)(match)), |
509 | | _mm_load_si128((const __m128i *)(masks) + offset))); |
510 | | |
511 | | match += masks[offset]; |
512 | | |
513 | | op += 16; |
514 | | len -= 16; |
515 | | } |
516 | | // Deal with remainders |
517 | | for (; len > 0; len--) { |
518 | | *op++ = *match++; |
519 | | } |
520 | | return op; |
521 | | } |
522 | | #endif |
523 | | |
524 | | |
525 | | int ndlz4_decompress(const uint8_t *input, int32_t input_len, uint8_t *output, int32_t output_len, |
526 | 713 | uint8_t meta, blosc2_dparams *dparams) { |
527 | 713 | BLOSC_UNUSED_PARAM(meta); |
528 | 713 | BLOSC_UNUSED_PARAM(dparams); |
529 | 713 | BLOSC_ERROR_NULL(input, BLOSC2_ERROR_NULL_POINTER); |
530 | 713 | BLOSC_ERROR_NULL(output, BLOSC2_ERROR_NULL_POINTER); |
531 | | |
532 | 713 | uint8_t *ip = (uint8_t *) input; |
533 | 713 | uint8_t *op = (uint8_t *) output; |
534 | 713 | uint8_t ndim; |
535 | 713 | int32_t blockshape[2]; |
536 | 713 | int32_t eshape[2]; |
537 | 713 | uint8_t *buffercpy; |
538 | 713 | uint8_t local_buffer[16]; |
539 | 713 | uint8_t token; |
540 | | // The fixed header is 1 (ndim) + 2 * int32 (blockshape) = 9 bytes. Reject any |
541 | | // input too short to hold it -- the previous `< 8` check let an 8-byte input |
542 | | // through and the header parse below then read a 9th byte. The cast keeps the |
543 | | // comparison signed so a negative `input_len` is rejected here, before the |
544 | | // `ip + input_len` pointer arithmetic (which would otherwise be UB) is reached. |
545 | 713 | if (NDLZ_UNEXPECT_CONDITIONAL(input_len < (int32_t) (1 + 2 * sizeof(int32_t)))) { |
546 | 40 | return 0; |
547 | 40 | } |
548 | 673 | uint8_t *ip_limit = ip + input_len; |
549 | | |
550 | | // Validate that a read of `len` bytes starting at byte offset `pos` (measured |
551 | | // from the start of `input`) lies fully within the compressed buffer. A |
552 | | // crafted token `offset` otherwise makes a back-reference point before |
553 | | // `input`, and the cell-fill memcpy then leaks out-of-bounds heap memory into |
554 | | // the (attacker-visible) output buffer -- CWE-125. The per-cell `ip > ip_limit` |
555 | | // check is not enough because each cell reads a 2-byte offset and literal bytes |
556 | | // past that point. All arithmetic is done on integer offsets so we never form |
557 | | // an out-of-bounds pointer (which is undefined behaviour in C, even when the |
558 | | // pointer is only compared and not dereferenced). |
559 | 673 | #define NDLZ4_CHECK_RANGE(pos, len) \ |
560 | 7.58k | do { \ |
561 | 7.58k | int64_t _pos = (int64_t) (pos); \ |
562 | 7.58k | int64_t _len = (int64_t) (len); \ |
563 | 7.58k | if (_pos < 0 || _len < 0 || _pos > (int64_t) input_len - _len) { \ |
564 | 171 | BLOSC_TRACE_ERROR("ndlz4: out-of-bounds reference in compressed stream"); \ |
565 | 171 | return BLOSC2_ERROR_FAILURE; \ |
566 | 171 | } \ |
567 | 7.58k | } while (0) |
568 | | |
569 | | /* we start with literal copy */ |
570 | 673 | ndim = *ip; |
571 | 673 | ip++; |
572 | 673 | if (ndim != 2) { |
573 | 3 | BLOSC_TRACE_ERROR("This codec only works for ndim = 2"); |
574 | 3 | return BLOSC2_ERROR_FAILURE; |
575 | 3 | } |
576 | 670 | memcpy(&blockshape[0], ip, 4); |
577 | 670 | ip += 4; |
578 | 670 | memcpy(&blockshape[1], ip, 4); |
579 | 670 | ip += 4; |
580 | | |
581 | | // Sanity check. See https://www.cve.org/CVERecord?id=CVE-2024-3204 |
582 | 670 | if (output_len < 0 || blockshape[0] < 0 || blockshape[1] < 0) { |
583 | 86 | BLOSC_TRACE_ERROR("Output length or blockshape is negative"); |
584 | 86 | return BLOSC2_ERROR_FAILURE; |
585 | 86 | } |
586 | | |
587 | 584 | eshape[0] = ((blockshape[0] + 3) / 4) * 4; |
588 | 584 | eshape[1] = ((blockshape[1] + 3) / 4) * 4; |
589 | | |
590 | 584 | if (NDLZ_UNEXPECT_CONDITIONAL((int64_t)output_len < (int64_t)blockshape[0] * (int64_t)blockshape[1])) { |
591 | 135 | BLOSC_TRACE_ERROR("The blockshape is bigger than the output buffer"); |
592 | 135 | return 0; |
593 | 135 | } |
594 | 449 | memset(op, 0, blockshape[0] * blockshape[1]); |
595 | | |
596 | 449 | uint32_t i_stop[2]; |
597 | 1.34k | for (int i = 0; i < 2; ++i) { |
598 | 898 | i_stop[i] = eshape[i] / 4; |
599 | 898 | } |
600 | | |
601 | | /* main loop */ |
602 | 449 | uint32_t ii[2]; |
603 | 449 | uint32_t padding[2] = {0}; |
604 | 449 | uint32_t ind = 0; |
605 | 449 | uint8_t cell_aux[16]; |
606 | 6.23G | for (ii[0] = 0; ii[0] < i_stop[0]; ++ii[0]) { |
607 | 6.23G | for (ii[1] = 0; ii[1] < i_stop[1]; ++ii[1]) { // for each cell |
608 | 2.44k | if (NDLZ_UNEXPECT_CONDITIONAL(ip > ip_limit)) { |
609 | 0 | BLOSC_TRACE_ERROR("Exceeding input length"); |
610 | 0 | return BLOSC2_ERROR_FAILURE; |
611 | 0 | } |
612 | 2.44k | if (ii[0] == i_stop[0] - 1) { |
613 | 868 | padding[0] = (blockshape[0] % 4 == 0) ? 4 : blockshape[0] % 4; |
614 | 1.57k | } else { |
615 | 1.57k | padding[0] = 4; |
616 | 1.57k | } |
617 | 2.44k | if (ii[1] == i_stop[1] - 1) { |
618 | 1.78k | padding[1] = (blockshape[1] % 4 == 0) ? 4 : blockshape[1] % 4; |
619 | 1.78k | } else { |
620 | 658 | padding[1] = 4; |
621 | 658 | } |
622 | 2.44k | int64_t cur = ip - (const uint8_t *) input; // in-range: ip is within [input, ip_limit] |
623 | 2.44k | NDLZ4_CHECK_RANGE(cur, 1); |
624 | 2.44k | token = *ip++; |
625 | 2.44k | if (token == 0) { // no match |
626 | 1.05k | NDLZ4_CHECK_RANGE(cur + 1, (int64_t) padding[0] * padding[1]); |
627 | 1.04k | buffercpy = ip; |
628 | 1.04k | ip += padding[0] * padding[1]; |
629 | 1.38k | } else if (token == (uint8_t) ((1U << 7U) | (1U << 6U))) { // cell match |
630 | 185 | NDLZ4_CHECK_RANGE(cur + 1, 2); |
631 | 184 | uint16_t offset; |
632 | 184 | memcpy(&offset, ip, sizeof(offset)); |
633 | 184 | int64_t bpos = (cur + 1) - (int64_t) offset - 1; // back-reference start offset |
634 | 184 | NDLZ4_CHECK_RANGE(bpos, (int64_t) padding[0] * padding[1]); |
635 | 177 | buffercpy = (uint8_t *) input + bpos; |
636 | 177 | ip += 2; |
637 | 1.20k | } else if (token == (uint8_t) (1U << 6U)) { // whole cell of same element |
638 | 198 | NDLZ4_CHECK_RANGE(cur + 1, 1); |
639 | 196 | buffercpy = cell_aux; |
640 | 196 | memset(buffercpy, *ip, 16); |
641 | 196 | ip++; |
642 | 1.00k | } else if (token >= 224) { // three rows match |
643 | 282 | buffercpy = local_buffer; |
644 | 282 | NDLZ4_CHECK_RANGE(cur + 1, 2); |
645 | 280 | uint16_t offset; |
646 | 280 | memcpy(&offset, ip, sizeof(offset)); |
647 | 280 | offset += 3; |
648 | 280 | ip += 2; |
649 | 280 | int i, j, k; |
650 | 280 | if ((token >> 3U) == 28) { |
651 | 26 | i = 1; |
652 | 26 | j = 2; |
653 | 26 | k = 3; |
654 | 254 | } else { |
655 | 254 | i = 0; |
656 | 254 | if ((token >> 3U) < 30) { |
657 | 34 | j = 1; |
658 | 34 | k = 2; |
659 | 220 | } else { |
660 | 220 | k = 3; |
661 | 220 | if ((token >> 3U) == 30) { |
662 | 45 | j = 1; |
663 | 175 | } else { |
664 | 175 | j = 2; |
665 | 175 | } |
666 | 220 | } |
667 | 254 | } |
668 | 280 | int64_t bpos = (cur + 3) - (int64_t) offset; // == (ip - offset) as an offset |
669 | 280 | NDLZ4_CHECK_RANGE(bpos, 12); |
670 | 237 | const uint8_t *ref = (const uint8_t *) input + bpos; |
671 | 237 | memcpy(&buffercpy[i * 4], ref, 4); |
672 | 237 | memcpy(&buffercpy[j * 4], ref + 4, 4); |
673 | 237 | memcpy(&buffercpy[k * 4], ref + 8, 4); |
674 | 557 | for (int l = 0; l < 4; l++) { |
675 | 557 | if ((l != i) && (l != j) && (l != k)) { |
676 | 237 | NDLZ4_CHECK_RANGE(ip - (const uint8_t *) input, 4); |
677 | 235 | memcpy(&buffercpy[l * 4], ip, 4); |
678 | 235 | ip += 4; |
679 | 235 | break; |
680 | 237 | } |
681 | 557 | } |
682 | | |
683 | 723 | } else if ((token >= 128) && (token <= 191)) { // rows pair match |
684 | 515 | buffercpy = local_buffer; |
685 | 515 | NDLZ4_CHECK_RANGE(cur + 1, 2); |
686 | 514 | uint16_t offset; |
687 | 514 | memcpy(&offset, ip, sizeof(offset)); |
688 | 514 | offset += 3; |
689 | 514 | ip += 2; |
690 | 514 | int i, j; |
691 | 514 | if (token == 128) { |
692 | 76 | i = 2; |
693 | 76 | j = 3; |
694 | 438 | } else { |
695 | 438 | i = (token - 128) >> 5U; |
696 | 438 | j = ((token - 128) >> 3U) - (i << 2U); |
697 | 438 | } |
698 | 514 | int64_t bpos = (cur + 3) - (int64_t) offset; |
699 | 514 | NDLZ4_CHECK_RANGE(bpos, 8); |
700 | 477 | const uint8_t *ref = (const uint8_t *) input + bpos; |
701 | 477 | memcpy(&buffercpy[i * 4], ref, 4); |
702 | 477 | memcpy(&buffercpy[j * 4], ref + 4, 4); |
703 | 2.37k | for (int k = 0; k < 4; k++) { |
704 | 1.90k | if ((k != i) && (k != j)) { |
705 | 1.04k | NDLZ4_CHECK_RANGE(ip - (const uint8_t *) input, 4); |
706 | 1.03k | memcpy(&buffercpy[k * 4], ip, 4); |
707 | 1.03k | ip += 4; |
708 | 1.03k | } |
709 | 1.90k | } |
710 | 477 | } else if ((token >= 40) && (token <= 63)) { // 2 rows pair matches |
711 | 176 | buffercpy = local_buffer; |
712 | 176 | NDLZ4_CHECK_RANGE(cur + 1, 2); |
713 | 174 | uint16_t offset_1; |
714 | 174 | memcpy(&offset_1, ip, sizeof(offset_1)); |
715 | 174 | offset_1 += 5; |
716 | 174 | ip += 2; |
717 | 174 | NDLZ4_CHECK_RANGE(cur + 3, 2); |
718 | 165 | uint16_t offset_2; |
719 | 165 | memcpy(&offset_2, ip, sizeof(offset_2)); |
720 | 165 | offset_2 += 5; |
721 | 165 | ip += 2; |
722 | 165 | int i, j, k, l, m; |
723 | 165 | i = 0; |
724 | 165 | j = ((token - 32) >> 3U); |
725 | 165 | l = -1; |
726 | 660 | for (k = 1; k < 4; k++) { |
727 | 495 | if ((k != i) && (k != j)) { |
728 | 330 | if (l == -1) { |
729 | 165 | l = k; |
730 | 165 | } else { |
731 | 165 | m = k; |
732 | 165 | } |
733 | 330 | } |
734 | 495 | } |
735 | 165 | int64_t bpos1 = (cur + 5) - (int64_t) offset_1; |
736 | 165 | NDLZ4_CHECK_RANGE(bpos1, 8); |
737 | 133 | const uint8_t *ref1 = (const uint8_t *) input + bpos1; |
738 | 133 | memcpy(&buffercpy[i * 4], ref1, 4); |
739 | 133 | memcpy(&buffercpy[j * 4], ref1 + 4, 4); |
740 | 133 | int64_t bpos2 = (cur + 5) - (int64_t) offset_2; |
741 | 133 | NDLZ4_CHECK_RANGE(bpos2, 8); |
742 | 114 | const uint8_t *ref2 = (const uint8_t *) input + bpos2; |
743 | 114 | memcpy(&buffercpy[l * 4], ref2, 4); |
744 | 114 | memcpy(&buffercpy[m * 4], ref2 + 4, 4); |
745 | | |
746 | 114 | } else { |
747 | 32 | BLOSC_TRACE_ERROR("Invalid token: %u at cell [%d, %d]\n", token, ii[0], ii[1]); |
748 | 32 | return BLOSC2_ERROR_FAILURE; |
749 | 32 | } |
750 | | // fill op with buffercpy |
751 | 2.24k | uint32_t orig = ii[0] * 4 * blockshape[1] + ii[1] * 4; |
752 | 11.2k | for (uint32_t i = 0; i < 4; i++) { |
753 | 8.96k | if (i < padding[0]) { |
754 | 7.03k | ind = orig + i * blockshape[1]; |
755 | 7.03k | memcpy(&op[ind], buffercpy, padding[1]); |
756 | | // Only advance over rows we actually consume. For a literal cell |
757 | | // (token == 0) buffercpy points into the input and only holds |
758 | | // padding[0] * padding[1] bytes; advancing unconditionally for the |
759 | | // trailing padding rows would form an out-of-bounds pointer (UB) |
760 | | // for the block's last cell. The skipped rows are never read. |
761 | 7.03k | buffercpy += padding[1]; |
762 | 7.03k | } |
763 | 8.96k | } |
764 | 2.24k | if (ind > (uint32_t) output_len) { |
765 | 0 | BLOSC_TRACE_ERROR("Exceeding output size"); |
766 | 0 | return BLOSC2_ERROR_FAILURE; |
767 | 0 | } |
768 | 2.24k | } |
769 | 6.23G | } |
770 | 246 | ind += padding[1]; |
771 | | |
772 | 246 | if ((int32_t)ind != (blockshape[0] * blockshape[1])) { |
773 | 0 | BLOSC_TRACE_ERROR("Output size is not compatible with embedded blockshape"); |
774 | 0 | return BLOSC2_ERROR_FAILURE; |
775 | 0 | } |
776 | 246 | if (ind > (uint32_t) output_len) { |
777 | 0 | BLOSC_TRACE_ERROR("Exceeding output size"); |
778 | 0 | return BLOSC2_ERROR_FAILURE; |
779 | 0 | } |
780 | | |
781 | 246 | return (int) ind; |
782 | 246 | #undef NDLZ4_CHECK_RANGE |
783 | 246 | } |