Coverage Report

Created: 2025-08-11 09:23

/src/gdal/frmts/zarr/zarr_v3_codec.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Zarr driver
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "zarr.h"
14
15
#include "cpl_compressor.h"
16
17
/************************************************************************/
18
/*                          ZarrV3Codec()                               */
19
/************************************************************************/
20
21
30
ZarrV3Codec::ZarrV3Codec(const std::string &osName) : m_osName(osName)
22
30
{
23
30
}
24
25
/************************************************************************/
26
/*                         ~ZarrV3Codec()                               */
27
/************************************************************************/
28
29
30
ZarrV3Codec::~ZarrV3Codec() = default;
30
31
/************************************************************************/
32
/*                      ZarrV3CodecAbstractCompressor()                 */
33
/************************************************************************/
34
35
ZarrV3CodecAbstractCompressor::ZarrV3CodecAbstractCompressor(
36
    const std::string &osName)
37
18
    : ZarrV3Codec(osName)
38
18
{
39
18
}
40
41
/************************************************************************/
42
/*                 ZarrV3CodecAbstractCompressor::Encode()              */
43
/************************************************************************/
44
45
bool ZarrV3CodecAbstractCompressor::Encode(
46
    const ZarrByteVectorQuickResize &abySrc,
47
    ZarrByteVectorQuickResize &abyDst) const
48
0
{
49
0
    abyDst.resize(abyDst.capacity());
50
0
    void *pOutputData = abyDst.data();
51
0
    size_t nOutputSize = abyDst.size();
52
0
    bool bRet = m_pCompressor->pfnFunc(
53
0
        abySrc.data(), abySrc.size(), &pOutputData, &nOutputSize,
54
0
        m_aosCompressorOptions.List(), m_pCompressor->user_data);
55
0
    if (bRet)
56
0
    {
57
0
        abyDst.resize(nOutputSize);
58
0
    }
59
0
    else if (nOutputSize > abyDst.size())
60
0
    {
61
0
        CPLError(CE_Failure, CPLE_AppDefined,
62
0
                 "%s codec:Encode(): output buffer too small",
63
0
                 m_osName.c_str());
64
0
    }
65
0
    return bRet;
66
0
}
67
68
/************************************************************************/
69
/*                 ZarrV3CodecAbstractCompressor::Decode()              */
70
/************************************************************************/
71
72
bool ZarrV3CodecAbstractCompressor::Decode(
73
    const ZarrByteVectorQuickResize &abySrc,
74
    ZarrByteVectorQuickResize &abyDst) const
75
0
{
76
0
    abyDst.resize(abyDst.capacity());
77
0
    void *pOutputData = abyDst.data();
78
0
    size_t nOutputSize = abyDst.size();
79
0
    bool bRet = m_pDecompressor->pfnFunc(abySrc.data(), abySrc.size(),
80
0
                                         &pOutputData, &nOutputSize, nullptr,
81
0
                                         m_pDecompressor->user_data);
82
0
    if (bRet)
83
0
    {
84
0
        abyDst.resize(nOutputSize);
85
0
    }
86
0
    else if (nOutputSize > abyDst.size())
87
0
    {
88
0
        CPLError(CE_Failure, CPLE_AppDefined,
89
0
                 "%s codec:Decode(): output buffer too small",
90
0
                 m_osName.c_str());
91
0
    }
92
0
    return bRet;
93
0
}
94
95
/************************************************************************/
96
/*                        ZarrV3CodecGZip()                             */
97
/************************************************************************/
98
99
0
ZarrV3CodecGZip::ZarrV3CodecGZip() : ZarrV3CodecAbstractCompressor(NAME)
100
0
{
101
0
}
102
103
/************************************************************************/
104
/*                           GetConfiguration()                         */
105
/************************************************************************/
106
107
/* static */ CPLJSONObject ZarrV3CodecGZip::GetConfiguration(int nLevel)
108
0
{
109
0
    CPLJSONObject oConfig;
110
0
    oConfig.Add("level", nLevel);
111
0
    return oConfig;
112
0
}
113
114
/************************************************************************/
115
/*                   ZarrV3CodecGZip::InitFromConfiguration()           */
116
/************************************************************************/
117
118
bool ZarrV3CodecGZip::InitFromConfiguration(
119
    const CPLJSONObject &configuration,
120
    const ZarrArrayMetadata &oInputArrayMetadata,
121
    ZarrArrayMetadata &oOutputArrayMetadata)
122
0
{
123
0
    m_pCompressor = CPLGetCompressor("gzip");
124
0
    m_pDecompressor = CPLGetDecompressor("gzip");
125
0
    if (!m_pCompressor || !m_pDecompressor)
126
0
    {
127
0
        CPLError(CE_Failure, CPLE_AppDefined, "gzip compressor not available");
128
0
        return false;
129
0
    }
130
131
0
    m_oConfiguration = configuration.Clone();
132
0
    m_oInputArrayMetadata = oInputArrayMetadata;
133
    // byte->byte codec
134
0
    oOutputArrayMetadata = oInputArrayMetadata;
135
136
0
    int nLevel = 6;
137
138
0
    if (configuration.IsValid())
139
0
    {
140
0
        if (configuration.GetType() != CPLJSONObject::Type::Object)
141
0
        {
142
0
            CPLError(CE_Failure, CPLE_AppDefined,
143
0
                     "Codec gzip: configuration is not an object");
144
0
            return false;
145
0
        }
146
147
0
        for (const auto &oChild : configuration.GetChildren())
148
0
        {
149
0
            if (oChild.GetName() != "level")
150
0
            {
151
0
                CPLError(
152
0
                    CE_Failure, CPLE_AppDefined,
153
0
                    "Codec gzip: configuration contains a unhandled member: %s",
154
0
                    oChild.GetName().c_str());
155
0
                return false;
156
0
            }
157
0
        }
158
159
0
        const auto oLevel = configuration.GetObj("level");
160
0
        if (oLevel.IsValid())
161
0
        {
162
0
            if (oLevel.GetType() != CPLJSONObject::Type::Integer)
163
0
            {
164
0
                CPLError(CE_Failure, CPLE_AppDefined,
165
0
                         "Codec gzip: level is not an integer");
166
0
                return false;
167
0
            }
168
0
            nLevel = oLevel.ToInteger();
169
0
            if (nLevel < 0 || nLevel > 9)
170
0
            {
171
0
                CPLError(CE_Failure, CPLE_AppDefined,
172
0
                         "Codec gzip: invalid value for level: %d", nLevel);
173
0
                return false;
174
0
            }
175
0
        }
176
0
    }
177
178
0
    m_aosCompressorOptions.SetNameValue("LEVEL", CPLSPrintf("%d", nLevel));
179
180
0
    return true;
181
0
}
182
183
/************************************************************************/
184
/*                      ZarrV3CodecGZip::Clone()                        */
185
/************************************************************************/
186
187
std::unique_ptr<ZarrV3Codec> ZarrV3CodecGZip::Clone() const
188
0
{
189
0
    auto psClone = std::make_unique<ZarrV3CodecGZip>();
190
0
    ZarrArrayMetadata oOutputArrayMetadata;
191
0
    psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
192
0
                                   oOutputArrayMetadata);
193
0
    return psClone;
194
0
}
195
196
/************************************************************************/
197
/*                        ZarrV3CodecZstd()                             */
198
/************************************************************************/
199
200
0
ZarrV3CodecZstd::ZarrV3CodecZstd() : ZarrV3CodecAbstractCompressor(NAME)
201
0
{
202
0
}
203
204
/************************************************************************/
205
/*                           GetConfiguration()                         */
206
/************************************************************************/
207
208
/* static */ CPLJSONObject ZarrV3CodecZstd::GetConfiguration(int nLevel,
209
                                                             bool checksum)
210
0
{
211
0
    CPLJSONObject oConfig;
212
0
    oConfig.Add("level", nLevel);
213
0
    oConfig.Add("checksum", checksum);
214
0
    return oConfig;
215
0
}
216
217
/************************************************************************/
218
/*                   ZarrV3CodecZstd::InitFromConfiguration()           */
219
/************************************************************************/
220
221
bool ZarrV3CodecZstd::InitFromConfiguration(
222
    const CPLJSONObject &configuration,
223
    const ZarrArrayMetadata &oInputArrayMetadata,
224
    ZarrArrayMetadata &oOutputArrayMetadata)
225
0
{
226
0
    m_pCompressor = CPLGetCompressor("zstd");
227
0
    m_pDecompressor = CPLGetDecompressor("zstd");
228
0
    if (!m_pCompressor || !m_pDecompressor)
229
0
    {
230
0
        CPLError(CE_Failure, CPLE_AppDefined, "zstd compressor not available");
231
0
        return false;
232
0
    }
233
234
0
    m_oConfiguration = configuration.Clone();
235
0
    m_oInputArrayMetadata = oInputArrayMetadata;
236
    // byte->byte codec
237
0
    oOutputArrayMetadata = oInputArrayMetadata;
238
239
0
    int nLevel = 13;
240
0
    bool bChecksum = false;
241
242
0
    if (configuration.IsValid())
243
0
    {
244
0
        if (configuration.GetType() != CPLJSONObject::Type::Object)
245
0
        {
246
0
            CPLError(CE_Failure, CPLE_AppDefined,
247
0
                     "Codec zstd: configuration is not an object");
248
0
            return false;
249
0
        }
250
251
0
        for (const auto &oChild : configuration.GetChildren())
252
0
        {
253
0
            if (oChild.GetName() != "level" && oChild.GetName() != "checksum")
254
0
            {
255
0
                CPLError(
256
0
                    CE_Failure, CPLE_AppDefined,
257
0
                    "Codec zstd: configuration contains a unhandled member: %s",
258
0
                    oChild.GetName().c_str());
259
0
                return false;
260
0
            }
261
0
        }
262
263
0
        const auto oLevel = configuration.GetObj("level");
264
0
        if (oLevel.IsValid())
265
0
        {
266
0
            if (oLevel.GetType() != CPLJSONObject::Type::Integer)
267
0
            {
268
0
                CPLError(CE_Failure, CPLE_AppDefined,
269
0
                         "Codec zstd: level is not an integer");
270
0
                return false;
271
0
            }
272
0
            nLevel = oLevel.ToInteger();
273
0
            if (nLevel < 0 || nLevel > 22)
274
0
            {
275
0
                CPLError(CE_Failure, CPLE_AppDefined,
276
0
                         "Codec zstd: invalid value for level: %d", nLevel);
277
0
                return false;
278
0
            }
279
0
        }
280
281
0
        const auto oChecksum = configuration.GetObj("checksum");
282
0
        if (oChecksum.IsValid())
283
0
        {
284
0
            if (oChecksum.GetType() != CPLJSONObject::Type::Boolean)
285
0
            {
286
0
                CPLError(CE_Failure, CPLE_AppDefined,
287
0
                         "Codec zstd: checksum is not a boolean");
288
0
                return false;
289
0
            }
290
0
            bChecksum = oChecksum.ToBool();
291
0
        }
292
0
    }
293
294
0
    m_aosCompressorOptions.SetNameValue("LEVEL", CPLSPrintf("%d", nLevel));
295
0
    if (bChecksum)
296
0
        m_aosCompressorOptions.SetNameValue("CHECKSUM", "YES");
297
298
0
    return true;
299
0
}
300
301
/************************************************************************/
302
/*                      ZarrV3CodecZstd::Clone()                        */
303
/************************************************************************/
304
305
std::unique_ptr<ZarrV3Codec> ZarrV3CodecZstd::Clone() const
306
0
{
307
0
    auto psClone = std::make_unique<ZarrV3CodecZstd>();
308
0
    ZarrArrayMetadata oOutputArrayMetadata;
309
0
    psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
310
0
                                   oOutputArrayMetadata);
311
0
    return psClone;
312
0
}
313
314
/************************************************************************/
315
/*                       ZarrV3CodecBlosc()                             */
316
/************************************************************************/
317
318
18
ZarrV3CodecBlosc::ZarrV3CodecBlosc() : ZarrV3CodecAbstractCompressor(NAME)
319
18
{
320
18
}
321
322
/************************************************************************/
323
/*                           GetConfiguration()                         */
324
/************************************************************************/
325
326
/* static */ CPLJSONObject
327
ZarrV3CodecBlosc::GetConfiguration(const char *cname, int clevel,
328
                                   const char *shuffle, int typesize,
329
                                   int blocksize)
330
0
{
331
0
    CPLJSONObject oConfig;
332
0
    oConfig.Add("cname", cname);
333
0
    oConfig.Add("clevel", clevel);
334
0
    oConfig.Add("shuffle", shuffle);
335
0
    if (strcmp(shuffle, "noshuffle") != 0)
336
0
        oConfig.Add("typesize", typesize);
337
0
    oConfig.Add("blocksize", blocksize);
338
0
    return oConfig;
339
0
}
340
341
/************************************************************************/
342
/*                   ZarrV3CodecBlosc::InitFromConfiguration()           */
343
/************************************************************************/
344
345
bool ZarrV3CodecBlosc::InitFromConfiguration(
346
    const CPLJSONObject &configuration,
347
    const ZarrArrayMetadata &oInputArrayMetadata,
348
    ZarrArrayMetadata &oOutputArrayMetadata)
349
18
{
350
18
    m_pCompressor = CPLGetCompressor("blosc");
351
18
    m_pDecompressor = CPLGetDecompressor("blosc");
352
18
    if (!m_pCompressor || !m_pDecompressor)
353
18
    {
354
18
        CPLError(CE_Failure, CPLE_AppDefined, "blosc compressor not available");
355
18
        return false;
356
18
    }
357
358
0
    m_oConfiguration = configuration.Clone();
359
0
    m_oInputArrayMetadata = oInputArrayMetadata;
360
    // byte->byte codec
361
0
    oOutputArrayMetadata = oInputArrayMetadata;
362
363
0
    if (!configuration.IsValid() ||
364
0
        configuration.GetType() != CPLJSONObject::Type::Object)
365
0
    {
366
0
        CPLError(CE_Failure, CPLE_AppDefined,
367
0
                 "Codec blosc: configuration missing or not an object");
368
0
        return false;
369
0
    }
370
371
0
    for (const auto &oChild : configuration.GetChildren())
372
0
    {
373
0
        const auto osName = oChild.GetName();
374
0
        if (osName != "cname" && osName != "clevel" && osName != "shuffle" &&
375
0
            osName != "typesize" && osName != "blocksize")
376
0
        {
377
0
            CPLError(
378
0
                CE_Failure, CPLE_AppDefined,
379
0
                "Codec blosc: configuration contains a unhandled member: %s",
380
0
                osName.c_str());
381
0
            return false;
382
0
        }
383
0
    }
384
385
0
    const auto oCname = configuration.GetObj("cname");
386
0
    if (oCname.GetType() != CPLJSONObject::Type::String)
387
0
    {
388
0
        CPLError(CE_Failure, CPLE_AppDefined,
389
0
                 "Codec blosc: cname is missing or not a string");
390
0
        return false;
391
0
    }
392
0
    m_aosCompressorOptions.SetNameValue("CNAME", oCname.ToString().c_str());
393
394
0
    const auto oLevel = configuration.GetObj("clevel");
395
0
    if (oLevel.IsValid())
396
0
    {
397
0
        if (oLevel.GetType() != CPLJSONObject::Type::Integer)
398
0
        {
399
0
            CPLError(CE_Failure, CPLE_AppDefined,
400
0
                     "Codec blosc: clevel is not an integer");
401
0
            return false;
402
0
        }
403
0
        const int nLevel = oLevel.ToInteger();
404
0
        if (nLevel < 0 || nLevel > 9)
405
0
        {
406
0
            CPLError(CE_Failure, CPLE_AppDefined,
407
0
                     "Codec blosc: clevel value for level: %d", nLevel);
408
0
            return false;
409
0
        }
410
0
        m_aosCompressorOptions.SetNameValue("CLEVEL", CPLSPrintf("%d", nLevel));
411
0
    }
412
413
0
    const auto oShuffle = configuration.GetObj("shuffle");
414
0
    if (oShuffle.GetType() != CPLJSONObject::Type::String)
415
0
    {
416
0
        CPLError(CE_Failure, CPLE_AppDefined,
417
0
                 "Codec blosc: shuffle is missing or not a string");
418
0
        return false;
419
0
    }
420
0
    if (oShuffle.ToString() == "noshuffle")
421
0
        m_aosCompressorOptions.SetNameValue("SHUFFLE", "NONE");
422
0
    else if (oShuffle.ToString() == "shuffle")
423
0
        m_aosCompressorOptions.SetNameValue("SHUFFLE", "BYTE");
424
0
    else if (oShuffle.ToString() == "bitshuffle")
425
0
        m_aosCompressorOptions.SetNameValue("SHUFFLE", "BIT");
426
0
    else
427
0
    {
428
0
        CPLError(CE_Failure, CPLE_AppDefined,
429
0
                 "Codec blosc: Invalid value for shuffle");
430
0
        return false;
431
0
    }
432
433
0
    const auto oTypesize = configuration.GetObj("typesize");
434
0
    if (oTypesize.IsValid())
435
0
    {
436
0
        if (oTypesize.GetType() != CPLJSONObject::Type::Integer)
437
0
        {
438
0
            CPLError(CE_Failure, CPLE_AppDefined,
439
0
                     "Codec blosc: typesize is not an integer");
440
0
            return false;
441
0
        }
442
0
        const int nTypeSize = oTypesize.ToInteger();
443
0
        m_aosCompressorOptions.SetNameValue("TYPESIZE",
444
0
                                            CPLSPrintf("%d", nTypeSize));
445
0
    }
446
447
0
    const auto oBlocksize = configuration.GetObj("blocksize");
448
0
    if (oBlocksize.IsValid())
449
0
    {
450
0
        if (oBlocksize.GetType() != CPLJSONObject::Type::Integer)
451
0
        {
452
0
            CPLError(CE_Failure, CPLE_AppDefined,
453
0
                     "Codec blosc: blocksize is not an integer");
454
0
            return false;
455
0
        }
456
0
        const int nBlocksize = oBlocksize.ToInteger();
457
0
        m_aosCompressorOptions.SetNameValue("BLOCKSIZE",
458
0
                                            CPLSPrintf("%d", nBlocksize));
459
0
    }
460
461
0
    return true;
462
0
}
463
464
/************************************************************************/
465
/*                      ZarrV3CodecBlosc::Clone()                        */
466
/************************************************************************/
467
468
std::unique_ptr<ZarrV3Codec> ZarrV3CodecBlosc::Clone() const
469
0
{
470
0
    auto psClone = std::make_unique<ZarrV3CodecBlosc>();
471
0
    ZarrArrayMetadata oOutputArrayMetadata;
472
0
    psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
473
0
                                   oOutputArrayMetadata);
474
0
    return psClone;
475
0
}
476
477
/************************************************************************/
478
/*                       ZarrV3CodecBytes()                            */
479
/************************************************************************/
480
481
12
ZarrV3CodecBytes::ZarrV3CodecBytes() : ZarrV3Codec(NAME)
482
12
{
483
12
}
484
485
/************************************************************************/
486
/*                           GetConfiguration()                         */
487
/************************************************************************/
488
489
/* static */ CPLJSONObject ZarrV3CodecBytes::GetConfiguration(bool bLittle)
490
0
{
491
0
    CPLJSONObject oConfig;
492
0
    oConfig.Add("endian", bLittle ? "little" : "big");
493
0
    return oConfig;
494
0
}
495
496
/************************************************************************/
497
/*                 ZarrV3CodecBytes::InitFromConfiguration()            */
498
/************************************************************************/
499
500
bool ZarrV3CodecBytes::InitFromConfiguration(
501
    const CPLJSONObject &configuration,
502
    const ZarrArrayMetadata &oInputArrayMetadata,
503
    ZarrArrayMetadata &oOutputArrayMetadata)
504
12
{
505
12
    m_oConfiguration = configuration.Clone();
506
12
    m_bLittle = true;
507
12
    m_oInputArrayMetadata = oInputArrayMetadata;
508
12
    oOutputArrayMetadata = oInputArrayMetadata;
509
510
12
    if (configuration.IsValid())
511
0
    {
512
0
        if (configuration.GetType() != CPLJSONObject::Type::Object)
513
0
        {
514
0
            CPLError(CE_Failure, CPLE_AppDefined,
515
0
                     "Codec endian: configuration is not an object");
516
0
            return false;
517
0
        }
518
519
0
        for (const auto &oChild : configuration.GetChildren())
520
0
        {
521
0
            if (oChild.GetName() != "endian")
522
0
            {
523
0
                CPLError(CE_Failure, CPLE_AppDefined,
524
0
                         "Codec endian: configuration contains a unhandled "
525
0
                         "member: %s",
526
0
                         oChild.GetName().c_str());
527
0
                return false;
528
0
            }
529
0
        }
530
531
0
        const auto oEndian = configuration.GetObj("endian");
532
0
        if (oEndian.IsValid())
533
0
        {
534
0
            if (oEndian.GetType() != CPLJSONObject::Type::String)
535
0
            {
536
0
                CPLError(CE_Failure, CPLE_AppDefined,
537
0
                         "Codec gzip: endian is not a string");
538
0
                return false;
539
0
            }
540
0
            if (oEndian.ToString() == "little")
541
0
                m_bLittle = true;
542
0
            else if (oEndian.ToString() == "big")
543
0
                m_bLittle = false;
544
0
            else
545
0
            {
546
0
                CPLError(CE_Failure, CPLE_AppDefined,
547
0
                         "Codec gzip: invalid value for endian");
548
0
                return false;
549
0
            }
550
0
        }
551
0
    }
552
553
12
    return true;
554
12
}
555
556
/************************************************************************/
557
/*                     ZarrV3CodecBytes::Clone()                        */
558
/************************************************************************/
559
560
std::unique_ptr<ZarrV3Codec> ZarrV3CodecBytes::Clone() const
561
0
{
562
0
    auto psClone = std::make_unique<ZarrV3CodecBytes>();
563
0
    ZarrArrayMetadata oOutputArrayMetadata;
564
0
    psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
565
0
                                   oOutputArrayMetadata);
566
0
    return psClone;
567
0
}
568
569
/************************************************************************/
570
/*                      ZarrV3CodecBytes::Encode()                      */
571
/************************************************************************/
572
573
bool ZarrV3CodecBytes::Encode(const ZarrByteVectorQuickResize &abySrc,
574
                              ZarrByteVectorQuickResize &abyDst) const
575
0
{
576
0
    CPLAssert(!IsNoOp());
577
578
0
    size_t nEltCount = m_oInputArrayMetadata.GetEltCount();
579
0
    size_t nNativeSize = m_oInputArrayMetadata.oElt.nativeSize;
580
0
    if (abySrc.size() < nEltCount * nNativeSize)
581
0
    {
582
0
        CPLError(CE_Failure, CPLE_AppDefined,
583
0
                 "ZarrV3CodecTranspose::Encode(): input buffer too small");
584
0
        return false;
585
0
    }
586
0
    CPLAssert(abySrc.size() >= nEltCount * nNativeSize);
587
0
    abyDst.resize(nEltCount * nNativeSize);
588
589
0
    const GByte *pabySrc = abySrc.data();
590
0
    GByte *pabyDst = abyDst.data();
591
592
0
    if (m_oInputArrayMetadata.oElt.nativeType ==
593
0
        DtypeElt::NativeType::COMPLEX_IEEEFP)
594
0
    {
595
0
        nEltCount *= 2;
596
0
        nNativeSize /= 2;
597
0
    }
598
0
    if (nNativeSize == 2)
599
0
    {
600
0
        for (size_t i = 0; i < nEltCount; ++i)
601
0
        {
602
0
            const uint16_t val = CPL_SWAP16(*reinterpret_cast<const uint16_t *>(
603
0
                pabySrc + sizeof(uint16_t) * i));
604
0
            memcpy(pabyDst + sizeof(uint16_t) * i, &val, sizeof(val));
605
0
        }
606
0
    }
607
0
    else if (nNativeSize == 4)
608
0
    {
609
0
        for (size_t i = 0; i < nEltCount; ++i)
610
0
        {
611
0
            const uint32_t val = CPL_SWAP32(*reinterpret_cast<const uint32_t *>(
612
0
                pabySrc + sizeof(uint32_t) * i));
613
0
            memcpy(pabyDst + sizeof(uint32_t) * i, &val, sizeof(val));
614
0
        }
615
0
    }
616
0
    else if (nNativeSize == 8)
617
0
    {
618
0
        for (size_t i = 0; i < nEltCount; ++i)
619
0
        {
620
0
            const uint64_t val = CPL_SWAP64(*reinterpret_cast<const uint64_t *>(
621
0
                pabySrc + sizeof(uint64_t) * i));
622
0
            memcpy(pabyDst + sizeof(uint64_t) * i, &val, sizeof(val));
623
0
        }
624
0
    }
625
0
    else
626
0
    {
627
0
        CPLAssert(false);
628
0
    }
629
0
    return true;
630
0
}
631
632
/************************************************************************/
633
/*                      ZarrV3CodecBytes::Decode()                      */
634
/************************************************************************/
635
636
bool ZarrV3CodecBytes::Decode(const ZarrByteVectorQuickResize &abySrc,
637
                              ZarrByteVectorQuickResize &abyDst) const
638
0
{
639
0
    return Encode(abySrc, abyDst);
640
0
}
641
642
/************************************************************************/
643
/*                       ZarrV3CodecTranspose()                         */
644
/************************************************************************/
645
646
0
ZarrV3CodecTranspose::ZarrV3CodecTranspose() : ZarrV3Codec(NAME)
647
0
{
648
0
}
649
650
/************************************************************************/
651
/*                             IsNoOp()                                 */
652
/************************************************************************/
653
654
bool ZarrV3CodecTranspose::IsNoOp() const
655
0
{
656
0
    for (int i = 0; i < static_cast<int>(m_anOrder.size()); ++i)
657
0
    {
658
0
        if (m_anOrder[i] != i)
659
0
            return false;
660
0
    }
661
0
    return true;
662
0
}
663
664
/************************************************************************/
665
/*                           GetConfiguration()                         */
666
/************************************************************************/
667
668
/* static */ CPLJSONObject
669
ZarrV3CodecTranspose::GetConfiguration(const std::vector<int> &anOrder)
670
0
{
671
0
    CPLJSONObject oConfig;
672
0
    CPLJSONArray oOrder;
673
0
    for (const auto nVal : anOrder)
674
0
        oOrder.Add(nVal);
675
0
    oConfig.Add("order", oOrder);
676
0
    return oConfig;
677
0
}
678
679
/************************************************************************/
680
/*                           GetConfiguration()                         */
681
/************************************************************************/
682
683
/* static */ CPLJSONObject
684
ZarrV3CodecTranspose::GetConfiguration(const std::string &osOrder)
685
0
{
686
0
    CPLJSONObject oConfig;
687
0
    CPLJSONArray oOrder;
688
0
    oConfig.Add("order", osOrder);
689
0
    return oConfig;
690
0
}
691
692
/************************************************************************/
693
/*                ZarrV3CodecTranspose::InitFromConfiguration()         */
694
/************************************************************************/
695
696
bool ZarrV3CodecTranspose::InitFromConfiguration(
697
    const CPLJSONObject &configuration,
698
    const ZarrArrayMetadata &oInputArrayMetadata,
699
    ZarrArrayMetadata &oOutputArrayMetadata)
700
0
{
701
0
    m_oConfiguration = configuration.Clone();
702
0
    m_oInputArrayMetadata = oInputArrayMetadata;
703
0
    oOutputArrayMetadata = oInputArrayMetadata;
704
705
0
    if (!configuration.IsValid() &&
706
0
        configuration.GetType() != CPLJSONObject::Type::Object)
707
0
    {
708
0
        CPLError(CE_Failure, CPLE_AppDefined,
709
0
                 "Codec transpose: configuration missing or not an object");
710
0
        return false;
711
0
    }
712
713
0
    for (const auto &oChild : configuration.GetChildren())
714
0
    {
715
0
        if (oChild.GetName() != "order")
716
0
        {
717
0
            CPLError(CE_Failure, CPLE_AppDefined,
718
0
                     "Codec transpose: configuration contains a unhandled "
719
0
                     "member: %s",
720
0
                     oChild.GetName().c_str());
721
0
            return false;
722
0
        }
723
0
    }
724
725
0
    const auto oOrder = configuration.GetObj("order");
726
0
    const int nDims = static_cast<int>(oInputArrayMetadata.anBlockSizes.size());
727
0
    if (oOrder.GetType() == CPLJSONObject::Type::String)
728
0
    {
729
0
        const auto osOrder = oOrder.ToString();
730
0
        if (osOrder == "C")
731
0
        {
732
0
            for (int i = 0; i < nDims; ++i)
733
0
            {
734
0
                m_anOrder.push_back(i);
735
0
            }
736
0
        }
737
0
        else if (osOrder == "F")
738
0
        {
739
0
            for (int i = 0; i < nDims; ++i)
740
0
            {
741
0
                m_anOrder.push_back(nDims - 1 - i);
742
0
                oOutputArrayMetadata.anBlockSizes[i] =
743
0
                    oInputArrayMetadata.anBlockSizes[nDims - 1 - i];
744
0
            }
745
0
        }
746
0
        else
747
0
        {
748
0
            CPLError(CE_Failure, CPLE_AppDefined,
749
0
                     "Codec transpose: invalid value for order");
750
0
            return false;
751
0
        }
752
0
    }
753
0
    else if (oOrder.GetType() == CPLJSONObject::Type::Array)
754
0
    {
755
0
        const auto oOrderArray = oOrder.ToArray();
756
0
        if (oOrderArray.Size() != nDims)
757
0
        {
758
0
            CPLError(CE_Failure, CPLE_AppDefined,
759
0
                     "Codec transpose: order[] does not have the expected "
760
0
                     "number of elements");
761
0
            return false;
762
0
        }
763
0
        std::vector<int> oSet(nDims);
764
0
        oOutputArrayMetadata.anBlockSizes.clear();
765
0
        for (const auto &oVal : oOrderArray)
766
0
        {
767
0
            const int nVal = oVal.ToInteger();
768
0
            if (nVal < 0 || nVal >= nDims || oSet[nVal])
769
0
            {
770
0
                CPLError(CE_Failure, CPLE_AppDefined,
771
0
                         "Codec transpose: order[] does not define a valid "
772
0
                         "transposition");
773
0
                return false;
774
0
            }
775
0
            oSet[nVal] = true;
776
0
            m_anOrder.push_back(nVal);
777
0
            oOutputArrayMetadata.anBlockSizes.push_back(
778
0
                oInputArrayMetadata.anBlockSizes[nVal]);
779
0
        }
780
0
    }
781
0
    else
782
0
    {
783
0
        CPLError(CE_Failure, CPLE_AppDefined,
784
0
                 "Codec transpose: invalid value for order");
785
0
        return false;
786
0
    }
787
788
0
    int i = 0;
789
0
    m_anReverseOrder.resize(m_anOrder.size());
790
0
    for (const auto nVal : m_anOrder)
791
0
    {
792
0
        m_anReverseOrder[nVal] = i;
793
0
        ++i;
794
0
    }
795
796
0
    return true;
797
0
}
798
799
/************************************************************************/
800
/*                   ZarrV3CodecTranspose::Clone()                      */
801
/************************************************************************/
802
803
std::unique_ptr<ZarrV3Codec> ZarrV3CodecTranspose::Clone() const
804
0
{
805
0
    auto psClone = std::make_unique<ZarrV3CodecTranspose>();
806
0
    ZarrArrayMetadata oOutputArrayMetadata;
807
0
    psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
808
0
                                   oOutputArrayMetadata);
809
0
    return psClone;
810
0
}
811
812
/************************************************************************/
813
/*                  ZarrV3CodecTranspose::Transpose()                   */
814
/************************************************************************/
815
816
bool ZarrV3CodecTranspose::Transpose(const ZarrByteVectorQuickResize &abySrc,
817
                                     ZarrByteVectorQuickResize &abyDst,
818
                                     bool bEncodeDirection) const
819
0
{
820
0
    CPLAssert(m_anOrder.size() == m_oInputArrayMetadata.anBlockSizes.size());
821
0
    CPLAssert(m_anReverseOrder.size() ==
822
0
              m_oInputArrayMetadata.anBlockSizes.size());
823
0
    const size_t nDims = m_anOrder.size();
824
0
    const size_t nSourceSize = m_oInputArrayMetadata.oElt.nativeSize;
825
0
    const auto &anBlockSizes = m_oInputArrayMetadata.anBlockSizes;
826
0
    CPLAssert(nDims > 0);
827
0
    if (abySrc.size() < m_oInputArrayMetadata.GetEltCount() * nSourceSize)
828
0
    {
829
0
        CPLError(CE_Failure, CPLE_AppDefined,
830
0
                 "ZarrV3CodecTranspose::Transpose(): input buffer too small");
831
0
        return false;
832
0
    }
833
0
    abyDst.resize(m_oInputArrayMetadata.GetEltCount() * nSourceSize);
834
835
0
    struct Stack
836
0
    {
837
0
        size_t nIters = 0;
838
0
        const GByte *src_ptr = nullptr;
839
0
        GByte *dst_ptr = nullptr;
840
0
        size_t src_inc_offset = 0;
841
0
        size_t dst_inc_offset = 0;
842
0
    };
843
844
0
    std::vector<Stack> stack(nDims);
845
0
    stack.emplace_back(
846
0
        Stack());  // to make gcc 9.3 -O2 -Wnull-dereference happy
847
848
0
    if (!bEncodeDirection)
849
0
    {
850
0
        stack[m_anReverseOrder[nDims - 1]].src_inc_offset = nSourceSize;
851
0
        size_t nStride = nSourceSize;
852
0
        for (size_t i = nDims - 1; i > 0;)
853
0
        {
854
0
            --i;
855
0
            nStride *=
856
0
                static_cast<size_t>(anBlockSizes[m_anReverseOrder[i + 1]]);
857
0
            stack[m_anReverseOrder[i]].src_inc_offset = nStride;
858
0
        }
859
860
0
        stack[nDims - 1].dst_inc_offset = nSourceSize;
861
0
        nStride = nSourceSize;
862
0
        for (size_t i = nDims - 1; i > 0;)
863
0
        {
864
0
            --i;
865
0
            nStride *= static_cast<size_t>(anBlockSizes[i + 1]);
866
0
            stack[i].dst_inc_offset = nStride;
867
0
        }
868
0
    }
869
0
    else
870
0
    {
871
0
        stack[m_anReverseOrder[nDims - 1]].dst_inc_offset = nSourceSize;
872
0
        size_t nStride = nSourceSize;
873
0
        for (size_t i = nDims - 1; i > 0;)
874
0
        {
875
0
            --i;
876
0
            nStride *=
877
0
                static_cast<size_t>(anBlockSizes[m_anReverseOrder[i + 1]]);
878
0
            stack[m_anReverseOrder[i]].dst_inc_offset = nStride;
879
0
        }
880
881
0
        stack[nDims - 1].src_inc_offset = nSourceSize;
882
0
        nStride = nSourceSize;
883
0
        for (size_t i = nDims - 1; i > 0;)
884
0
        {
885
0
            --i;
886
0
            nStride *= static_cast<size_t>(anBlockSizes[i + 1]);
887
0
            stack[i].src_inc_offset = nStride;
888
0
        }
889
0
    }
890
891
0
    stack[0].src_ptr = abySrc.data();
892
0
    stack[0].dst_ptr = &abyDst[0];
893
894
0
    size_t dimIdx = 0;
895
0
lbl_next_depth:
896
0
    if (dimIdx == nDims)
897
0
    {
898
0
        void *dst_ptr = stack[nDims].dst_ptr;
899
0
        const void *src_ptr = stack[nDims].src_ptr;
900
0
        if (nSourceSize == 1)
901
0
            *stack[nDims].dst_ptr = *stack[nDims].src_ptr;
902
0
        else if (nSourceSize == 2)
903
0
            *static_cast<uint16_t *>(dst_ptr) =
904
0
                *static_cast<const uint16_t *>(src_ptr);
905
0
        else if (nSourceSize == 4)
906
0
            *static_cast<uint32_t *>(dst_ptr) =
907
0
                *static_cast<const uint32_t *>(src_ptr);
908
0
        else if (nSourceSize == 8)
909
0
            *static_cast<uint64_t *>(dst_ptr) =
910
0
                *static_cast<const uint64_t *>(src_ptr);
911
0
        else
912
0
            memcpy(dst_ptr, src_ptr, nSourceSize);
913
0
    }
914
0
    else
915
0
    {
916
0
        stack[dimIdx].nIters = static_cast<size_t>(anBlockSizes[dimIdx]);
917
0
        while (true)
918
0
        {
919
0
            dimIdx++;
920
0
            stack[dimIdx].src_ptr = stack[dimIdx - 1].src_ptr;
921
0
            stack[dimIdx].dst_ptr = stack[dimIdx - 1].dst_ptr;
922
0
            goto lbl_next_depth;
923
0
        lbl_return_to_caller:
924
0
            dimIdx--;
925
0
            if ((--stack[dimIdx].nIters) == 0)
926
0
                break;
927
0
            stack[dimIdx].src_ptr += stack[dimIdx].src_inc_offset;
928
0
            stack[dimIdx].dst_ptr += stack[dimIdx].dst_inc_offset;
929
0
        }
930
0
    }
931
0
    if (dimIdx > 0)
932
0
        goto lbl_return_to_caller;
933
934
0
    return true;
935
0
}
936
937
/************************************************************************/
938
/*                    ZarrV3CodecTranspose::Encode()                    */
939
/************************************************************************/
940
941
bool ZarrV3CodecTranspose::Encode(const ZarrByteVectorQuickResize &abySrc,
942
                                  ZarrByteVectorQuickResize &abyDst) const
943
0
{
944
0
    CPLAssert(!IsNoOp());
945
946
0
    return Transpose(abySrc, abyDst, true);
947
0
}
948
949
/************************************************************************/
950
/*                    ZarrV3CodecTranspose::Decode()                    */
951
/************************************************************************/
952
953
bool ZarrV3CodecTranspose::Decode(const ZarrByteVectorQuickResize &abySrc,
954
                                  ZarrByteVectorQuickResize &abyDst) const
955
0
{
956
0
    CPLAssert(!IsNoOp());
957
958
0
    return Transpose(abySrc, abyDst, false);
959
0
}
960
961
/************************************************************************/
962
/*                    ZarrV3CodecSequence::Clone()                      */
963
/************************************************************************/
964
965
std::unique_ptr<ZarrV3CodecSequence> ZarrV3CodecSequence::Clone() const
966
0
{
967
0
    auto poClone = std::make_unique<ZarrV3CodecSequence>(m_oInputArrayMetadata);
968
0
    for (const auto &poCodec : m_apoCodecs)
969
0
        poClone->m_apoCodecs.emplace_back(poCodec->Clone());
970
0
    poClone->m_oCodecArray = m_oCodecArray.Clone();
971
0
    return poClone;
972
0
}
973
974
/************************************************************************/
975
/*                    ZarrV3CodecSequence::InitFromJson()               */
976
/************************************************************************/
977
978
bool ZarrV3CodecSequence::InitFromJson(const CPLJSONObject &oCodecs)
979
49
{
980
49
    if (oCodecs.GetType() != CPLJSONObject::Type::Array)
981
0
    {
982
0
        CPLError(CE_Failure, CPLE_AppDefined, "codecs is not an array");
983
0
        return false;
984
0
    }
985
49
    auto oCodecsArray = oCodecs.ToArray();
986
987
49
    ZarrArrayMetadata oInputArrayMetadata = m_oInputArrayMetadata;
988
49
    ZarrV3Codec::IOType eLastType = ZarrV3Codec::IOType::ARRAY;
989
49
    std::string osLastCodec;
990
991
49
    const auto InsertImplicitEndianCodecIfNeeded =
992
49
        [
993
#if !CPL_IS_LSB
994
            this,
995
#endif
996
49
            &oInputArrayMetadata, &eLastType, &osLastCodec]()
997
49
    {
998
30
        if (eLastType == ZarrV3Codec::IOType::ARRAY &&
999
30
            oInputArrayMetadata.oElt.nativeSize > 1)
1000
0
        {
1001
0
            CPLError(CE_Warning, CPLE_AppDefined,
1002
0
                     "'bytes' codec missing. Assuming little-endian storage, "
1003
0
                     "but such tolerance may be removed in future versions");
1004
0
            auto poEndianCodec = std::make_unique<ZarrV3CodecBytes>();
1005
0
            ZarrArrayMetadata oOutputArrayMetadata;
1006
0
            poEndianCodec->InitFromConfiguration(
1007
0
                ZarrV3CodecBytes::GetConfiguration(true), oInputArrayMetadata,
1008
0
                oOutputArrayMetadata);
1009
0
            oInputArrayMetadata = std::move(oOutputArrayMetadata);
1010
0
            eLastType = poEndianCodec->GetOutputType();
1011
0
            osLastCodec = poEndianCodec->GetName();
1012
#if !CPL_IS_LSB
1013
            // Insert a little endian codec if we are on a big endian target
1014
            m_apoCodecs.emplace_back(std::move(poEndianCodec));
1015
#endif
1016
0
        }
1017
30
    };
1018
1019
49
    for (const auto &oCodec : oCodecsArray)
1020
49
    {
1021
49
        if (oCodec.GetType() != CPLJSONObject::Type::Object)
1022
5
        {
1023
5
            CPLError(CE_Failure, CPLE_AppDefined, "codecs[] is not an array");
1024
5
            return false;
1025
5
        }
1026
44
        const auto osName = oCodec["name"].ToString();
1027
44
        std::unique_ptr<ZarrV3Codec> poCodec;
1028
44
        if (osName == "gzip")
1029
0
            poCodec = std::make_unique<ZarrV3CodecGZip>();
1030
44
        else if (osName == "blosc")
1031
18
            poCodec = std::make_unique<ZarrV3CodecBlosc>();
1032
26
        else if (osName == "zstd")
1033
0
            poCodec = std::make_unique<ZarrV3CodecZstd>();
1034
26
        else if (osName == "bytes" ||
1035
26
                 osName == "endian" /* endian is the old name */)
1036
12
            poCodec = std::make_unique<ZarrV3CodecBytes>();
1037
14
        else if (osName == "transpose")
1038
0
            poCodec = std::make_unique<ZarrV3CodecTranspose>();
1039
14
        else
1040
14
        {
1041
14
            CPLError(CE_Failure, CPLE_NotSupported, "Unsupported codec: %s",
1042
14
                     osName.c_str());
1043
14
            return false;
1044
14
        }
1045
1046
30
        if (poCodec->GetInputType() == ZarrV3Codec::IOType::ARRAY)
1047
12
        {
1048
12
            if (eLastType == ZarrV3Codec::IOType::BYTES)
1049
0
            {
1050
0
                CPLError(CE_Failure, CPLE_AppDefined,
1051
0
                         "Cannot chain codec %s with %s",
1052
0
                         poCodec->GetName().c_str(), osLastCodec.c_str());
1053
0
                return false;
1054
0
            }
1055
12
        }
1056
18
        else
1057
18
        {
1058
18
            InsertImplicitEndianCodecIfNeeded();
1059
18
        }
1060
1061
30
        ZarrArrayMetadata oOutputArrayMetadata;
1062
30
        if (!poCodec->InitFromConfiguration(oCodec["configuration"],
1063
30
                                            oInputArrayMetadata,
1064
30
                                            oOutputArrayMetadata))
1065
18
        {
1066
18
            return false;
1067
18
        }
1068
12
        oInputArrayMetadata = std::move(oOutputArrayMetadata);
1069
12
        eLastType = poCodec->GetOutputType();
1070
12
        osLastCodec = poCodec->GetName();
1071
1072
12
        if (!poCodec->IsNoOp())
1073
0
            m_apoCodecs.emplace_back(std::move(poCodec));
1074
12
    }
1075
1076
12
    InsertImplicitEndianCodecIfNeeded();
1077
1078
12
    m_oCodecArray = oCodecs.Clone();
1079
12
    return true;
1080
49
}
1081
1082
/************************************************************************/
1083
/*                  ZarrV3CodecBytes::AllocateBuffer()                 */
1084
/************************************************************************/
1085
1086
bool ZarrV3CodecSequence::AllocateBuffer(ZarrByteVectorQuickResize &abyBuffer)
1087
4
{
1088
4
    if (!m_apoCodecs.empty())
1089
0
    {
1090
0
        const size_t nRawSize = m_oInputArrayMetadata.GetEltCount() *
1091
0
                                m_oInputArrayMetadata.oElt.nativeSize;
1092
        // Grow the temporary buffer a bit beyond the uncompressed size
1093
0
        const size_t nMaxSize = nRawSize + nRawSize / 3 + 64;
1094
0
        try
1095
0
        {
1096
0
            m_abyTmp.resize(nMaxSize);
1097
0
        }
1098
0
        catch (const std::exception &e)
1099
0
        {
1100
0
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1101
0
            return false;
1102
0
        }
1103
0
        m_abyTmp.resize(nRawSize);
1104
1105
        // Grow the input/output buffer too if we have several steps
1106
0
        if (m_apoCodecs.size() >= 2 && abyBuffer.capacity() < nMaxSize)
1107
0
        {
1108
0
            const size_t nSize = abyBuffer.size();
1109
0
            try
1110
0
            {
1111
0
                abyBuffer.resize(nMaxSize);
1112
0
            }
1113
0
            catch (const std::exception &e)
1114
0
            {
1115
0
                CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
1116
0
                return false;
1117
0
            }
1118
0
            abyBuffer.resize(nSize);
1119
0
        }
1120
0
    }
1121
4
    return true;
1122
4
}
1123
1124
/************************************************************************/
1125
/*                    ZarrV3CodecSequence::Encode()                     */
1126
/************************************************************************/
1127
1128
bool ZarrV3CodecSequence::Encode(ZarrByteVectorQuickResize &abyBuffer)
1129
0
{
1130
0
    if (!AllocateBuffer(abyBuffer))
1131
0
        return false;
1132
0
    for (const auto &poCodec : m_apoCodecs)
1133
0
    {
1134
0
        if (!poCodec->Encode(abyBuffer, m_abyTmp))
1135
0
            return false;
1136
0
        std::swap(abyBuffer, m_abyTmp);
1137
0
    }
1138
0
    return true;
1139
0
}
1140
1141
/************************************************************************/
1142
/*                    ZarrV3CodecSequence::Decode()                     */
1143
/************************************************************************/
1144
1145
bool ZarrV3CodecSequence::Decode(ZarrByteVectorQuickResize &abyBuffer)
1146
4
{
1147
4
    if (!AllocateBuffer(abyBuffer))
1148
0
        return false;
1149
4
    for (auto iter = m_apoCodecs.rbegin(); iter != m_apoCodecs.rend(); ++iter)
1150
0
    {
1151
0
        const auto &poCodec = *iter;
1152
0
        if (!poCodec->Decode(abyBuffer, m_abyTmp))
1153
0
            return false;
1154
0
        std::swap(abyBuffer, m_abyTmp);
1155
0
    }
1156
4
    return true;
1157
4
}