Coverage Report

Created: 2026-09-03 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}