Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/icechunk/icechunksnapshot.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Icechunk driver
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "icechunksnapshot.h"
14
#include "icechunkutils.h"
15
#include "icechunkdrivercore.h"
16
17
#include "cpl_vsi_virtual.h"
18
19
#include <algorithm>
20
#include <cinttypes>
21
#include <limits>
22
#if __cplusplus >= 202002L
23
#include <ranges>
24
#endif
25
26
/* ------------------------------------------------------------------------- */
27
28
#if defined(__GNUC__)
29
#pragma GCC diagnostic push
30
#pragma GCC diagnostic ignored "-Weffc++"
31
#pragma GCC diagnostic ignored "-Wnull-dereference"
32
#endif
33
34
#if defined(__clang__)
35
#pragma clang diagnostic push
36
#pragma clang diagnostic ignored "-Wweak-vtables"
37
#endif
38
39
#include "flatbuffers/flexbuffers.h"
40
#include "generated/snapshot_generated.h"
41
42
#if defined(__clang__)
43
#pragma clang diagnostic pop
44
#endif
45
46
#if defined(__GNUC__)
47
#pragma GCC diagnostic pop
48
#endif
49
50
/* ------------------------------------------------------------------------- */
51
52
using namespace flatbuffers;
53
using namespace generated;
54
55
namespace gdal::icechunk
56
{
57
0
IcechunkSnapshot::IcechunkSnapshot() = default;
58
59
0
IcechunkSnapshot::~IcechunkSnapshot() = default;
60
61
#if defined(__GNUC__)
62
#pragma GCC diagnostic push
63
#pragma GCC diagnostic ignored "-Wnull-dereference"
64
#endif
65
66
/************************************************************************/
67
/*                       IcechunkSnapshot::Open()                       */
68
/************************************************************************/
69
70
std::unique_ptr<IcechunkSnapshot>
71
IcechunkSnapshot::Open(const char *pszFilename)
72
0
{
73
0
    CPLDebugOnly("Icechunk", "Opening snapshot %s", pszFilename);
74
0
    auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
75
0
    if (!fp)
76
0
    {
77
0
        CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
78
0
        return nullptr;
79
0
    }
80
81
0
    int nVersion = 0;
82
0
    auto [buffer, size] =
83
0
        DecompressFile(pszFilename, fp.get(), FILE_TYPE_SNAPSHOT, &nVersion);
84
0
    if (!buffer)
85
0
        return nullptr;
86
87
0
    {
88
0
        Verifier verifier(buffer.get(), size);
89
0
        if (!VerifySnapshotBuffer(verifier))
90
0
        {
91
0
            CPLError(CE_Failure, CPLE_AppDefined,
92
0
                     "%s: invalid Snapshot Flatbuffer", pszFilename);
93
0
            return nullptr;
94
0
        }
95
0
    }
96
97
0
    auto snapshot = std::make_unique<IcechunkSnapshot>();
98
0
    snapshot->m_osFilename = pszFilename;
99
100
0
    const auto *fbsSnapshot = GetSnapshot(buffer.get());
101
0
    const auto *id = fbsSnapshot->id();
102
0
    CPLAssertNotNull(id);  // guaranteed by VerifySnapshotBuffer()
103
0
    const auto idBytes = id->bytes();
104
0
    CPLAssertNotNull(idBytes);  // guaranteed by VerifySnapshotBuffer()
105
0
    const std::string snapshotIdBase32 = CrockfordBase32Encode(*idBytes);
106
0
    if (snapshotIdBase32 != CPLGetFilename(pszFilename))
107
0
    {
108
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: id=%s != expected %s",
109
0
                 pszFilename, snapshotIdBase32.c_str(),
110
0
                 CPLGetFilename(pszFilename));
111
0
        return nullptr;
112
0
    }
113
114
0
    const auto *message = fbsSnapshot->message();
115
0
    CPLAssertNotNull(message);  // guaranteed by VerifySnapshotBuffer()
116
0
    snapshot->m_osCommitMessage = GetString(message);
117
0
    CPLDebugOnly("Icechunk", "snapshot %s: commit message: '%s'", pszFilename,
118
0
                 snapshot->m_osCommitMessage.c_str());
119
120
0
    snapshot->m_flushTimestamp = fbsSnapshot->flushed_at();
121
122
0
    {
123
0
        const auto *metadata = fbsSnapshot->metadata();
124
0
        if (metadata && nVersion == 2)
125
0
        {
126
0
            for (const auto &md : *metadata)
127
0
            {
128
0
                if (const auto value = md->value())
129
0
                {
130
0
                    if (!flexbuffers::VerifyBuffer(value->data(),
131
0
                                                   value->size()))
132
0
                    {
133
0
                        CPLError(CE_Failure, CPLE_AppDefined,
134
0
                                 "%s: flexbuffers::VerifyBuffer() failed",
135
0
                                 pszFilename);
136
0
                        return nullptr;
137
0
                    }
138
                    if constexpr (IS_DEBUG_BUILD)
139
                    {
140
                        std::string val;
141
                        flexbuffers::GetRoot(value->data(), value->size())
142
                            .ToString(true, true, val);
143
                        CPLDebugOnly("Icechunk", "snapshot %s: metadata %s=%s",
144
                                     snapshotIdBase32.c_str(),
145
                                     md->name() ? md->name()->c_str()
146
                                                : "(null)",
147
                                     val.c_str());
148
                    }
149
0
                }
150
0
            }
151
0
        }
152
0
    }
153
154
    /* --------------------------------------------------------------------*/
155
    /* Parse nodes[] array                                                 */
156
    /* --------------------------------------------------------------------*/
157
0
    const auto *nodes = fbsSnapshot->nodes();
158
0
    CPLAssertAlways(nodes);  // guaranteed by VerifySnapshotBuffer()
159
0
    snapshot->m_nodes.reserve(nodes->size());
160
0
    bool needSort = false;
161
0
    for (const auto *nodePtr : *nodes)
162
0
    {
163
0
        const auto *fbsNodeId = nodePtr->id();
164
0
        CPLAssertNotNull(fbsNodeId);  // guaranteed by VerifySnapshotBuffer()
165
0
        const auto fbsNodeIdBytes = fbsNodeId->bytes();
166
0
        CPLAssertAlways(
167
0
            fbsNodeIdBytes);  // guaranteed by VerifySnapshotBuffer()
168
169
0
        ObjectId8 nodeId;
170
0
        static_assert(sizeof(*fbsNodeIdBytes) == sizeof(nodeId));
171
0
        memcpy(nodeId.data(), fbsNodeIdBytes->data(), sizeof(nodeId));
172
173
0
        const auto *pathPtr = nodePtr->path();
174
0
        CPLAssertNotNull(pathPtr);  // guaranteed by VerifySnapshotBuffer()
175
176
0
        const std::string path = GetString(pathPtr);
177
0
        if (path.empty() || path[0] != '/' ||
178
0
            (path.size() > 1 && path.back() == '/'))
179
0
        {
180
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s: invalid node path '%s'",
181
0
                     pszFilename, path.c_str());
182
0
            return nullptr;
183
0
        }
184
185
        // Additional checks to avoid later issues. Might not be strictly
186
        // necessary, but if removing them, VSIIcechunkFileSystem should be
187
        // reviewed against risks of path traversal.
188
0
        if (path.find('\\') != std::string::npos ||
189
0
            path.find("/./") != std::string::npos ||
190
0
            path.find("/../") != std::string::npos ||
191
0
            cpl::ends_with(path, "/.."))
192
0
        {
193
0
            CPLError(CE_Failure, CPLE_AppDefined,
194
0
                     "%s: path traversal pattern in node path '%s'",
195
0
                     pszFilename, path.c_str());
196
0
            return nullptr;
197
0
        }
198
199
0
        needSort = needSort || (!snapshot->m_nodes.empty() &&
200
0
                                snapshot->m_nodes.back().path > path);
201
202
0
        Node node;
203
0
        node.id = nodeId;
204
0
        node.path = path;
205
206
0
        const auto *userData = nodePtr->user_data();
207
0
        CPLAssertAlways(userData);  // guaranteed by VerifySnapshotBuffer()
208
0
        node.content.assign(reinterpret_cast<const char *>(userData->data()),
209
0
                            userData->size());
210
211
0
        CPLDebugOnly("Icechunk", "snapshot %s, node %s, path %s, user_data %s",
212
0
                     snapshotIdBase32.c_str(),
213
0
                     CrockfordBase32Encode(*(fbsNodeId->bytes())).c_str(),
214
0
                     path.c_str(), node.content.c_str());
215
216
0
        if (const auto arrayData = nodePtr->node_data_as_Array())
217
0
        {
218
0
            node.isArray = true;
219
220
0
            uint64_t totalNumChunks = 1;
221
222
0
            if (nVersion == 2)
223
0
            {
224
0
                const auto *shape = arrayData->shape_v2();
225
0
                if (!shape)
226
0
                {
227
0
                    CPLError(CE_Failure, CPLE_AppDefined,
228
0
                             "%s: missing shape_v2 in ArrayData", pszFilename);
229
0
                    return nullptr;
230
0
                }
231
0
                for (const auto *dimShape : *shape)
232
0
                {
233
0
                    const uint32_t numChunks = dimShape->num_chunks();
234
0
                    if (numChunks == 0)
235
0
                    {
236
0
                        CPLError(CE_Failure, CPLE_AppDefined,
237
0
                                 "%s: numChunks == 0", pszFilename);
238
0
                        return nullptr;
239
0
                    }
240
0
                    node.numChunks.push_back(numChunks);
241
0
                    if (numChunks >
242
0
                        std::numeric_limits<uint64_t>::max() / totalNumChunks)
243
0
                    {
244
0
                        CPLError(CE_Failure, CPLE_AppDefined,
245
0
                                 "%s: too many chunks", pszFilename);
246
0
                        return nullptr;
247
0
                    }
248
0
                    totalNumChunks *= numChunks;
249
0
                }
250
0
            }
251
0
            else
252
0
            {
253
0
                const auto *shape = arrayData->shape();
254
0
                CPLAssertAlways(shape);  // guaranteed by VerifySnapshotBuffer()
255
0
                for (const auto *dimShape : *shape)
256
0
                {
257
0
                    const uint64_t array_length = dimShape->array_length();
258
0
                    const uint64_t chunk_length = dimShape->chunk_length();
259
0
                    if (!array_length || !chunk_length)
260
0
                    {
261
0
                        CPLError(CE_Failure, CPLE_AppDefined,
262
0
                                 "%s: invalid shape in ArrayData", pszFilename);
263
0
                        return nullptr;
264
0
                    }
265
0
                    const uint64_t numChunks64 =
266
0
                        cpl::div_round_up(array_length, chunk_length);
267
0
                    if (numChunks64 > std::numeric_limits<uint32_t>::max())
268
0
                    {
269
0
                        CPLError(
270
0
                            CE_Failure, CPLE_AppDefined,
271
0
                            "%s: invalid shape in ArrayData: too many chunks",
272
0
                            pszFilename);
273
0
                        return nullptr;
274
0
                    }
275
0
                    const uint32_t numChunks =
276
0
                        static_cast<uint32_t>(numChunks64);
277
0
                    node.numChunks.push_back(numChunks);
278
0
                    if (numChunks >
279
0
                        std::numeric_limits<uint64_t>::max() / totalNumChunks)
280
0
                    {
281
0
                        CPLError(CE_Failure, CPLE_AppDefined,
282
0
                                 "%s: too many chunks", pszFilename);
283
0
                        return nullptr;
284
0
                    }
285
0
                    totalNumChunks *= numChunks;
286
0
                }
287
0
            }
288
289
0
            const auto *manifests = arrayData->manifests();
290
0
            CPLAssertAlways(manifests);  // guaranteed by VerifySnapshotBuffer()
291
292
0
            uint64_t totalNumChunksFromManifests = 0;
293
0
            for (const auto *manifestPtr : *manifests)
294
0
            {
295
0
                const auto manifestId = manifestPtr->object_id();
296
0
                CPLAssertNotNull(
297
0
                    manifestId);  // guaranteed by VerifySnapshotBuffer()
298
0
                const auto manifestIdBytes = manifestId->bytes();
299
0
                CPLAssertAlways(
300
0
                    manifestIdBytes);  // guaranteed by VerifySnapshotBuffer()
301
302
0
                ManifestRef manifestRef;
303
0
                static_assert(sizeof(*manifestIdBytes) ==
304
0
                              sizeof(manifestRef.manifestId));
305
0
                memcpy(manifestRef.manifestId.data(), manifestIdBytes->data(),
306
0
                       sizeof(manifestRef.manifestId));
307
308
0
                const auto extents = manifestPtr->extents();
309
0
                CPLAssertAlways(
310
0
                    extents);  // guaranteed by VerifySnapshotBuffer()
311
312
0
                CPLDebugOnly("Icechunk", "snapshot %s, manifest ref %s:",
313
0
                             snapshotIdBase32.c_str(),
314
0
                             CrockfordBase32Encode(*manifestIdBytes).c_str());
315
316
0
                uint64_t chunkCountFromManifest = 1;
317
0
                if (node.numChunks.empty() && extents->size() == 1 &&
318
0
                    node.numChunks.empty())
319
0
                {
320
                    // Special case for scalar arrays such as "crs" written by Icechunk v0
321
0
                    const auto *extent = (*extents)[0];
322
0
                    if (extent->from() != 0 || extent->to() != 1)
323
0
                    {
324
0
                        CPLError(CE_Failure, CPLE_AppDefined,
325
0
                                 "%s: array %s: invalid manifest extent "
326
0
                                 "[%u, %u[ for dim %u",
327
0
                                 pszFilename, path.c_str(), extent->from(),
328
0
                                 extent->to(), 0);
329
0
                        return nullptr;
330
0
                    }
331
332
0
                    manifestRef.extents.emplace_back(extent->from(),
333
0
                                                     extent->to());
334
0
                    CPLDebugOnly("Icechunk",
335
0
                                 "snapshot %s,   from %" PRIu32 " to %" PRIu32,
336
0
                                 snapshotIdBase32.c_str(),
337
0
                                 manifestRef.extents.back().from,
338
0
                                 manifestRef.extents.back().to);
339
0
                }
340
0
                else
341
0
                {
342
0
                    if (node.numChunks.size() != extents->size())
343
0
                    {
344
0
                        CPLError(
345
0
                            CE_Failure, CPLE_AppDefined,
346
0
                            "%s: array %s: manifest extents has not expected "
347
0
                            "dimension count. Got %d from manifest extents, "
348
0
                            "expected %d from node shape",
349
0
                            pszFilename, path.c_str(),
350
0
                            static_cast<int>(extents->size()),
351
0
                            static_cast<int>(node.numChunks.size()));
352
0
                        return nullptr;
353
0
                    }
354
0
                    for (unsigned iDim = 0; iDim < extents->size(); ++iDim)
355
0
                    {
356
0
                        const auto *extent = (*extents)[iDim];
357
0
                        if (extent->from() >= extent->to() ||
358
0
                            extent->from() >= node.numChunks[iDim] ||
359
0
                            extent->to() > node.numChunks[iDim])
360
0
                        {
361
0
                            CPLError(CE_Failure, CPLE_AppDefined,
362
0
                                     "%s: array %s: invalid manifest extent "
363
0
                                     "[%u, %u[ for dim %u",
364
0
                                     pszFilename, path.c_str(), extent->from(),
365
0
                                     extent->to(), iDim);
366
0
                            return nullptr;
367
0
                        }
368
369
                        // Overflow cannot happen given the validation of extent
370
                        // w.r.t node.numChunks and the fact that
371
                        // times(node.numChunks) has been checked to fit on uint64_t
372
0
                        chunkCountFromManifest *= extent->to() - extent->from();
373
374
0
                        manifestRef.extents.emplace_back(extent->from(),
375
0
                                                         extent->to());
376
0
                        CPLDebugOnly("Icechunk",
377
0
                                     "snapshot %s,   from %" PRIu32
378
0
                                     " to %" PRIu32,
379
0
                                     snapshotIdBase32.c_str(),
380
0
                                     manifestRef.extents.back().from,
381
0
                                     manifestRef.extents.back().to);
382
0
                    }
383
0
                }
384
385
0
                node.manifestRefs.push_back(std::move(manifestRef));
386
387
0
                if (totalNumChunksFromManifests >
388
0
                    std::numeric_limits<uint64_t>::max() -
389
0
                        chunkCountFromManifest)
390
0
                {
391
0
                    totalNumChunksFromManifests =
392
0
                        std::numeric_limits<uint64_t>::max();
393
0
                    break;
394
0
                }
395
0
                totalNumChunksFromManifests += chunkCountFromManifest;
396
0
            }
397
398
            // Quick partial consistency check
399
0
            if (totalNumChunksFromManifests > totalNumChunks)
400
0
            {
401
0
                CPLError(CE_Failure, CPLE_AppDefined,
402
0
                         "%s: array %s: chunks referenced by manifest extents "
403
0
                         "= %" PRIu64 " > chunks in the array = %" PRIu64,
404
0
                         pszFilename, path.c_str(), totalNumChunksFromManifests,
405
0
                         totalNumChunks);
406
0
                return nullptr;
407
0
            }
408
409
            if constexpr (IS_DEBUG_BUILD)
410
            {
411
                // Check that all chunks of this array are referenced at most
412
                // once
413
                ChunkIdx anChunkIdx(node.numChunks.size());
414
                std::string chunkStr;
415
                for (uint64_t iChunk = 0; iChunk < totalNumChunks; ++iChunk)
416
                {
417
                    chunkStr = '[';
418
                    for (size_t iDim = 0; iDim < node.numChunks.size(); ++iDim)
419
                    {
420
                        if (iDim > 0)
421
                            chunkStr += ", ";
422
                        chunkStr += std::to_string(anChunkIdx[iDim]);
423
                    }
424
                    chunkStr += ']';
425
426
                    size_t counter = node.countManifestIdForChunk(anChunkIdx);
427
                    if (counter > 1)
428
                    {
429
                        CPLError(CE_Failure, CPLE_AppDefined,
430
                                 "%s: array %s: found more than one manifest "
431
                                 "ref for chunk %s",
432
                                 pszFilename, path.c_str(), chunkStr.c_str());
433
                        return nullptr;
434
                    }
435
436
                    if (iChunk + 1 < totalNumChunks)
437
                    {
438
                        // Increment anChunkIdx
439
                        for (size_t iDim = node.numChunks.size(); iDim > 0;
440
                             /* */)
441
                        {
442
                            --iDim;
443
                            ++anChunkIdx[iDim];
444
                            if (anChunkIdx[iDim] < node.numChunks[iDim])
445
                            {
446
                                break;
447
                            }
448
                            anChunkIdx[iDim] = 0;
449
                        }
450
                    }
451
                }
452
            }
453
0
        }
454
455
0
        snapshot->m_nodes.push_back(std::move(node));
456
0
    }
457
458
0
    if (needSort)
459
0
    {
460
        // Nominally not needed but there has been a confusion in the spec
461
        // regarding sort order, so normalize things
462
        // Cf https://github.com/earth-mover/icechunk/issues/2183
463
0
        std::sort(snapshot->m_nodes.begin(), snapshot->m_nodes.end(),
464
0
                  [](const Node &a, const Node &b) { return a.path < b.path; });
465
0
    }
466
467
    /* --------------------------------------------------------------------*/
468
    /* Parse manifest_files_v2 / manifest_files[] array                    */
469
    /* --------------------------------------------------------------------*/
470
0
    if (nVersion == 2)
471
0
    {
472
0
        const auto manifests = fbsSnapshot->manifest_files_v2();
473
0
        if (manifests)
474
0
        {
475
0
            snapshot->m_manifestInfos.reserve(manifests->size());
476
0
            for (const auto *manifestPtr : *manifests)
477
0
            {
478
0
                const auto manifestId = manifestPtr->id();
479
0
                if (!manifestId || !manifestId->bytes())
480
0
                {
481
0
                    CPLError(CE_Failure, CPLE_AppDefined,
482
0
                             "%s: missing manifest id", pszFilename);
483
0
                    return nullptr;
484
0
                }
485
486
0
                ManifestInfo info;
487
0
                info.sizeBytes = manifestPtr->size_bytes();
488
0
                info.numChunkRefs = manifestPtr->num_chunk_refs();
489
0
                static_assert(sizeof(*(manifestId->bytes())) ==
490
0
                              sizeof(info.id));
491
0
                memcpy(info.id.data(), manifestId->bytes()->data(),
492
0
                       sizeof(info.id));
493
0
                info.strId = CrockfordBase32Encode(info.id);
494
495
0
                CPLDebugOnly("Icechunk",
496
0
                             "snapshot %s, manifest %s, size_bytes %" PRIu64
497
0
                             ", num_chunk_refs %u",
498
0
                             snapshotIdBase32.c_str(), info.strId.c_str(),
499
0
                             info.sizeBytes, info.numChunkRefs);
500
501
0
                if (!snapshot->m_manifestInfos.empty() &&
502
0
                    info.id <= snapshot->m_manifestInfos.back().id)
503
0
                {
504
0
                    CPLError(
505
0
                        CE_Failure, CPLE_AppDefined,
506
0
                        "%s: ManifestInfo array not sorted by increasing id",
507
0
                        pszFilename);
508
0
                    return nullptr;
509
0
                }
510
511
0
                snapshot->m_manifestInfos.push_back(std::move(info));
512
0
            }
513
0
        }
514
0
    }
515
0
    else
516
0
    {
517
0
        const auto manifests = fbsSnapshot->manifest_files();
518
0
        CPLAssertAlways(manifests);  // guaranteed by VerifySnapshotBuffer()
519
520
0
        snapshot->m_manifestInfos.reserve(manifests->size());
521
0
        for (const auto *manifestPtr : *manifests)
522
0
        {
523
0
            const auto manifestId = manifestPtr->id();
524
525
0
            ManifestInfo info;
526
0
            info.sizeBytes = manifestPtr->size_bytes();
527
0
            info.numChunkRefs = manifestPtr->num_chunk_refs();
528
0
            static_assert(sizeof(*(manifestId.bytes())) == sizeof(info.id));
529
0
            memcpy(info.id.data(), manifestId.bytes()->data(), sizeof(info.id));
530
0
            info.strId = CrockfordBase32Encode(info.id);
531
532
0
            CPLDebugOnly("Icechunk",
533
0
                         "snapshot %s, manifest %s, size_bytes %" PRIu64
534
0
                         ", num_chunk_refs %u",
535
0
                         snapshotIdBase32.c_str(), info.strId.c_str(),
536
0
                         info.sizeBytes, info.numChunkRefs);
537
538
0
            if (!snapshot->m_manifestInfos.empty() &&
539
0
                info.id <= snapshot->m_manifestInfos.back().id)
540
0
            {
541
0
                CPLError(CE_Failure, CPLE_AppDefined,
542
0
                         "%s: ManifestInfo array not sorted by increasing id",
543
0
                         pszFilename);
544
0
                return nullptr;
545
0
            }
546
547
0
            snapshot->m_manifestInfos.push_back(std::move(info));
548
0
        }
549
0
    }
550
551
0
    return snapshot;
552
0
}
553
554
#if defined(__GNUC__)
555
#pragma GCC diagnostic pop
556
#endif
557
558
/************************************************************************/
559
/*              IcechunkSnapshot::GetManifestInfoFromId()               */
560
/************************************************************************/
561
562
const IcechunkSnapshot::ManifestInfo *
563
IcechunkSnapshot::GetManifestInfoFromId(const ObjectId12 &id) const
564
0
{
565
#if __cplusplus >= 202002L
566
    const auto iter =
567
        std::ranges::lower_bound(m_manifestInfos, id, {}, &ManifestInfo::id);
568
#else
569
0
    ManifestInfo lookup;
570
0
    lookup.id = id;
571
0
    const auto iter =
572
0
        std::lower_bound(m_manifestInfos.begin(), m_manifestInfos.end(), lookup,
573
0
                         [](const ManifestInfo &a, const ManifestInfo &b)
574
0
                         { return a.id < b.id; });
575
0
#endif
576
0
    if (iter != m_manifestInfos.end() && iter->id == id)
577
0
        return &(*iter);
578
0
    return nullptr;
579
0
}
580
581
/************************************************************************/
582
/*                 IcechunkSnapshot::GetNodeFromPath()                  */
583
/************************************************************************/
584
585
const IcechunkSnapshot::Node *
586
IcechunkSnapshot::GetNodeFromPath(const std::string &path) const
587
0
{
588
#if __cplusplus >= 202002L
589
    const auto iter = std::ranges::lower_bound(m_nodes, path, {}, &Node::path);
590
#else
591
0
    Node lookup;
592
0
    lookup.path = path;
593
0
    const auto iter = std::lower_bound(m_nodes.begin(), m_nodes.end(), lookup,
594
0
                                       [](const Node &a, const Node &b)
595
0
                                       { return a.path < b.path; });
596
0
#endif
597
0
    if (iter != m_nodes.end() && iter->path == path)
598
0
        return &(*iter);
599
0
    return nullptr;
600
0
}
601
602
/************************************************************************/
603
/*                     DoRefExtentsMatchChunkIdx()                      */
604
/************************************************************************/
605
606
inline bool DoRefExtentsMatchChunkIdx(
607
    const std::vector<IcechunkSnapshot::ChunkIndexRange> &extents,
608
    const ChunkIdx &anChunkIdx)
609
0
{
610
0
    CPLAssert(anChunkIdx.size() == extents.size());
611
0
    for (size_t iDim = 0; iDim < extents.size(); ++iDim)
612
0
    {
613
0
        if (anChunkIdx[iDim] < extents[iDim].from ||
614
0
            anChunkIdx[iDim] >= extents[iDim].to)
615
0
        {
616
0
            return false;
617
0
        }
618
0
    }
619
0
    return true;
620
0
}
621
622
/************************************************************************/
623
/*              IcechunkSnapshot::findManifestIdForChunk()              */
624
/************************************************************************/
625
626
const ObjectId12 *
627
IcechunkSnapshot::Node::findManifestIdForChunk(const ChunkIdx &anChunkIdx) const
628
0
{
629
0
    CPLAssert(anChunkIdx.size() == numChunks.size());
630
631
    // Special case for scalar arrays such as "crs" written by Icechunk v0
632
0
    if (anChunkIdx.empty() && manifestRefs.size() == 1 &&
633
0
        manifestRefs[0].extents.size() == 1 &&
634
0
        manifestRefs[0].extents[0].from == 0 &&
635
0
        manifestRefs[0].extents[0].to == 1)
636
0
    {
637
0
        return &(manifestRefs[0].manifestId);
638
0
    }
639
640
    // Heuristics to find more quickly the chunk, assuming the passed chunk
641
    // index is contained in the last ChunkRef.
642
0
    thread_local const Node *lastNode = nullptr;
643
0
    thread_local size_t lastRefsIdx = 0;
644
0
    if (lastNode == this && lastRefsIdx < manifestRefs.size())
645
0
    {
646
0
        const auto &ref = manifestRefs[lastRefsIdx];
647
0
        const bool match = DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx);
648
0
        CPLDebugOnly("Icechunk", "findManifestIdForChunk() guess: %s",
649
0
                     match ? "success" : "missed");
650
0
        if (match)
651
0
        {
652
0
            return &(ref.manifestId);
653
0
        }
654
0
    }
655
656
    // Note: if there are many manifestRefs in a node, this linear search could
657
    // become a bottleneck. Consider RTree / KDTree maybe.
658
0
    for (const auto &ref : manifestRefs)
659
0
    {
660
0
        if (DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx))
661
0
        {
662
0
            lastNode = this;
663
0
            lastRefsIdx = &ref - manifestRefs.data();
664
0
            return &(ref.manifestId);
665
0
        }
666
0
    }
667
668
0
    return nullptr;
669
0
}
670
671
}  // namespace gdal::icechunk