LCOV - code coverage report
Current view: top level - frmts/icechunk - icechunksnapshot.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 246 265 92.8 %
Date: 2026-06-19 21:24:00 Functions: 8 8 100.0 %

          Line data    Source code
       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             : IcechunkSnapshot::IcechunkSnapshot() = default;
      58             : 
      59             : 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        2193 : IcechunkSnapshot::Open(const char *pszFilename)
      72             : {
      73        2193 :     CPLDebugOnly("Icechunk", "Opening snapshot %s", pszFilename);
      74        4386 :     auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
      75        2193 :     if (!fp)
      76             :     {
      77           2 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
      78           2 :         return nullptr;
      79             :     }
      80             : 
      81        2191 :     int nVersion = 0;
      82        2191 :     auto [buffer, size] =
      83        4382 :         DecompressFile(pszFilename, fp.get(), FILE_TYPE_SNAPSHOT, &nVersion);
      84        2191 :     if (!buffer)
      85           1 :         return nullptr;
      86             : 
      87             :     {
      88        2190 :         Verifier verifier(buffer.get(), size);
      89        2190 :         if (!VerifySnapshotBuffer(verifier))
      90             :         {
      91           1 :             CPLError(CE_Failure, CPLE_AppDefined,
      92             :                      "%s: invalid Snapshot Flatbuffer", pszFilename);
      93           1 :             return nullptr;
      94             :         }
      95             :     }
      96             : 
      97        4378 :     auto snapshot = std::make_unique<IcechunkSnapshot>();
      98        2189 :     snapshot->m_osFilename = pszFilename;
      99             : 
     100        2189 :     const auto *fbsSnapshot = GetSnapshot(buffer.get());
     101        2189 :     const auto *id = fbsSnapshot->id();
     102        2189 :     CPLAssertNotNull(id);  // guaranteed by VerifySnapshotBuffer()
     103        2189 :     const auto idBytes = id->bytes();
     104        2189 :     CPLAssertNotNull(idBytes);  // guaranteed by VerifySnapshotBuffer()
     105        4378 :     const std::string snapshotIdBase32 = CrockfordBase32Encode(*idBytes);
     106        2189 :     if (snapshotIdBase32 != CPLGetFilename(pszFilename))
     107             :     {
     108           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: id=%s != expected %s",
     109             :                  pszFilename, snapshotIdBase32.c_str(),
     110             :                  CPLGetFilename(pszFilename));
     111           1 :         return nullptr;
     112             :     }
     113             : 
     114        2188 :     const auto *message = fbsSnapshot->message();
     115        2188 :     CPLAssertNotNull(message);  // guaranteed by VerifySnapshotBuffer()
     116        2188 :     snapshot->m_osCommitMessage = GetString(message);
     117        2188 :     CPLDebugOnly("Icechunk", "snapshot %s: commit message: '%s'", pszFilename,
     118             :                  snapshot->m_osCommitMessage.c_str());
     119             : 
     120        2188 :     snapshot->m_flushTimestamp = fbsSnapshot->flushed_at();
     121             : 
     122             :     {
     123        2188 :         const auto *metadata = fbsSnapshot->metadata();
     124        2188 :         if (metadata && nVersion == 2)
     125             :         {
     126        2179 :             for (const auto &md : *metadata)
     127             :             {
     128           8 :                 if (const auto value = md->value())
     129             :                 {
     130           8 :                     if (!flexbuffers::VerifyBuffer(value->data(),
     131           8 :                                                    value->size()))
     132             :                     {
     133           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     134             :                                  "%s: flexbuffers::VerifyBuffer() failed",
     135             :                                  pszFilename);
     136           0 :                         return nullptr;
     137             :                     }
     138             :                     if constexpr (IS_DEBUG_BUILD)
     139             :                     {
     140          16 :                         std::string val;
     141           8 :                         flexbuffers::GetRoot(value->data(), value->size())
     142           8 :                             .ToString(true, true, val);
     143           8 :                         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             :                 }
     150             :             }
     151             :         }
     152             :     }
     153             : 
     154             :     /* --------------------------------------------------------------------*/
     155             :     /* Parse nodes[] array                                                 */
     156             :     /* --------------------------------------------------------------------*/
     157        2188 :     const auto *nodes = fbsSnapshot->nodes();
     158        2188 :     CPLAssertAlways(nodes);  // guaranteed by VerifySnapshotBuffer()
     159        2188 :     snapshot->m_nodes.reserve(nodes->size());
     160        2188 :     bool needSort = false;
     161        6583 :     for (const auto *nodePtr : *nodes)
     162             :     {
     163        4408 :         const auto *fbsNodeId = nodePtr->id();
     164        4408 :         CPLAssertNotNull(fbsNodeId);  // guaranteed by VerifySnapshotBuffer()
     165        4408 :         const auto fbsNodeIdBytes = fbsNodeId->bytes();
     166        4408 :         CPLAssertAlways(
     167             :             fbsNodeIdBytes);  // guaranteed by VerifySnapshotBuffer()
     168             : 
     169             :         ObjectId8 nodeId;
     170             :         static_assert(sizeof(*fbsNodeIdBytes) == sizeof(nodeId));
     171        4408 :         memcpy(nodeId.data(), fbsNodeIdBytes->data(), sizeof(nodeId));
     172             : 
     173        4408 :         const auto *pathPtr = nodePtr->path();
     174        4408 :         CPLAssertNotNull(pathPtr);  // guaranteed by VerifySnapshotBuffer()
     175             : 
     176        4408 :         const std::string path = GetString(pathPtr);
     177       11056 :         if (path.empty() || path[0] != '/' ||
     178        6648 :             (path.size() > 1 && path.back() == '/'))
     179             :         {
     180           1 :             CPLError(CE_Failure, CPLE_AppDefined, "%s: invalid node path '%s'",
     181             :                      pszFilename, path.c_str());
     182           1 :             return nullptr;
     183             :         }
     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        8814 :         if (path.find('\\') != std::string::npos ||
     189        8814 :             path.find("/./") != std::string::npos ||
     190       13221 :             path.find("/../") != std::string::npos ||
     191        4406 :             cpl::ends_with(path, "/.."))
     192             :         {
     193           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     194             :                      "%s: path traversal pattern in node path '%s'",
     195             :                      pszFilename, path.c_str());
     196           1 :             return nullptr;
     197             :         }
     198             : 
     199        6632 :         needSort = needSort || (!snapshot->m_nodes.empty() &&
     200        2226 :                                 snapshot->m_nodes.back().path > path);
     201             : 
     202        4406 :         Node node;
     203        4406 :         node.id = nodeId;
     204        4406 :         node.path = path;
     205             : 
     206        4406 :         const auto *userData = nodePtr->user_data();
     207        4406 :         CPLAssertAlways(userData);  // guaranteed by VerifySnapshotBuffer()
     208        8812 :         node.content.assign(reinterpret_cast<const char *>(userData->data()),
     209        4406 :                             userData->size());
     210             : 
     211        4406 :         CPLDebugOnly("Icechunk", "snapshot %s, node %s, path %s, user_data %s",
     212             :                      snapshotIdBase32.c_str(),
     213             :                      CrockfordBase32Encode(*(fbsNodeId->bytes())).c_str(),
     214             :                      path.c_str(), node.content.c_str());
     215             : 
     216        4406 :         if (const auto arrayData = nodePtr->node_data_as_Array())
     217             :         {
     218        2238 :             node.isArray = true;
     219             : 
     220        2238 :             uint64_t totalNumChunks = 1;
     221             : 
     222        2238 :             if (nVersion == 2)
     223             :             {
     224        2213 :                 const auto *shape = arrayData->shape_v2();
     225        2213 :                 if (!shape)
     226             :                 {
     227           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     228             :                              "%s: missing shape_v2 in ArrayData", pszFilename);
     229           1 :                     return nullptr;
     230             :                 }
     231        4496 :                 for (const auto *dimShape : *shape)
     232             :                 {
     233        2286 :                     const uint32_t numChunks = dimShape->num_chunks();
     234        2286 :                     if (numChunks == 0)
     235             :                     {
     236           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
     237             :                                  "%s: numChunks == 0", pszFilename);
     238           2 :                         return nullptr;
     239             :                     }
     240        2285 :                     node.numChunks.push_back(numChunks);
     241        4570 :                     if (numChunks >
     242        2285 :                         std::numeric_limits<uint64_t>::max() / totalNumChunks)
     243             :                     {
     244           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
     245             :                                  "%s: too many chunks", pszFilename);
     246           1 :                         return nullptr;
     247             :                     }
     248        2284 :                     totalNumChunks *= numChunks;
     249             :                 }
     250             :             }
     251             :             else
     252             :             {
     253          25 :                 const auto *shape = arrayData->shape();
     254          25 :                 CPLAssertAlways(shape);  // guaranteed by VerifySnapshotBuffer()
     255          41 :                 for (const auto *dimShape : *shape)
     256             :                 {
     257          19 :                     const uint64_t array_length = dimShape->array_length();
     258          19 :                     const uint64_t chunk_length = dimShape->chunk_length();
     259          19 :                     if (!array_length || !chunk_length)
     260             :                     {
     261           2 :                         CPLError(CE_Failure, CPLE_AppDefined,
     262             :                                  "%s: invalid shape in ArrayData", pszFilename);
     263           3 :                         return nullptr;
     264             :                     }
     265             :                     const uint64_t numChunks64 =
     266          17 :                         cpl::div_round_up(array_length, chunk_length);
     267          17 :                     if (numChunks64 > std::numeric_limits<uint32_t>::max())
     268             :                     {
     269           1 :                         CPLError(
     270             :                             CE_Failure, CPLE_AppDefined,
     271             :                             "%s: invalid shape in ArrayData: too many chunks",
     272             :                             pszFilename);
     273           1 :                         return nullptr;
     274             :                     }
     275          16 :                     const uint32_t numChunks =
     276             :                         static_cast<uint32_t>(numChunks64);
     277          16 :                     node.numChunks.push_back(numChunks);
     278          32 :                     if (numChunks >
     279          16 :                         std::numeric_limits<uint64_t>::max() / totalNumChunks)
     280             :                     {
     281           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     282             :                                  "%s: too many chunks", pszFilename);
     283           0 :                         return nullptr;
     284             :                     }
     285          16 :                     totalNumChunks *= numChunks;
     286             :                 }
     287             :             }
     288             : 
     289        2232 :             const auto *manifests = arrayData->manifests();
     290        2232 :             CPLAssertAlways(manifests);  // guaranteed by VerifySnapshotBuffer()
     291             : 
     292        2232 :             uint64_t totalNumChunksFromManifests = 0;
     293        4464 :             for (const auto *manifestPtr : *manifests)
     294             :             {
     295        2236 :                 const auto manifestId = manifestPtr->object_id();
     296        2236 :                 CPLAssertNotNull(
     297             :                     manifestId);  // guaranteed by VerifySnapshotBuffer()
     298        2236 :                 const auto manifestIdBytes = manifestId->bytes();
     299        2236 :                 CPLAssertAlways(
     300             :                     manifestIdBytes);  // guaranteed by VerifySnapshotBuffer()
     301             : 
     302        2236 :                 ManifestRef manifestRef;
     303             :                 static_assert(sizeof(*manifestIdBytes) ==
     304             :                               sizeof(manifestRef.manifestId));
     305        2236 :                 memcpy(manifestRef.manifestId.data(), manifestIdBytes->data(),
     306             :                        sizeof(manifestRef.manifestId));
     307             : 
     308        2236 :                 const auto extents = manifestPtr->extents();
     309        2236 :                 CPLAssertAlways(
     310             :                     extents);  // guaranteed by VerifySnapshotBuffer()
     311             : 
     312        2236 :                 CPLDebugOnly("Icechunk", "snapshot %s, manifest ref %s:",
     313             :                              snapshotIdBase32.c_str(),
     314             :                              CrockfordBase32Encode(*manifestIdBytes).c_str());
     315             : 
     316        2236 :                 uint64_t chunkCountFromManifest = 1;
     317        2236 :                 if (node.numChunks.empty() && extents->size() == 1 &&
     318           0 :                     node.numChunks.empty())
     319             :                 {
     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             :                     {
     324           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     325             :                                  "%s: array %s: invalid manifest extent "
     326             :                                  "[%u, %u[ for dim %u",
     327             :                                  pszFilename, path.c_str(), extent->from(),
     328             :                                  extent->to(), 0);
     329           0 :                         return nullptr;
     330             :                     }
     331             : 
     332           0 :                     manifestRef.extents.emplace_back(extent->from(),
     333           0 :                                                      extent->to());
     334           0 :                     CPLDebugOnly("Icechunk",
     335             :                                  "snapshot %s,   from %" PRIu32 " to %" PRIu32,
     336             :                                  snapshotIdBase32.c_str(),
     337             :                                  manifestRef.extents.back().from,
     338             :                                  manifestRef.extents.back().to);
     339             :                 }
     340             :                 else
     341             :                 {
     342        2236 :                     if (node.numChunks.size() != extents->size())
     343             :                     {
     344           1 :                         CPLError(
     345             :                             CE_Failure, CPLE_AppDefined,
     346             :                             "%s: array %s: manifest extents has not expected "
     347             :                             "dimension count. Got %d from manifest extents, "
     348             :                             "expected %d from node shape",
     349             :                             pszFilename, path.c_str(),
     350           1 :                             static_cast<int>(extents->size()),
     351           1 :                             static_cast<int>(node.numChunks.size()));
     352           1 :                         return nullptr;
     353             :                     }
     354        4550 :                     for (unsigned iDim = 0; iDim < extents->size(); ++iDim)
     355             :                     {
     356        2318 :                         const auto *extent = (*extents)[iDim];
     357        2318 :                         if (extent->from() >= extent->to() ||
     358        4634 :                             extent->from() >= node.numChunks[iDim] ||
     359        2316 :                             extent->to() > node.numChunks[iDim])
     360             :                         {
     361           3 :                             CPLError(CE_Failure, CPLE_AppDefined,
     362             :                                      "%s: array %s: invalid manifest extent "
     363             :                                      "[%u, %u[ for dim %u",
     364             :                                      pszFilename, path.c_str(), extent->from(),
     365             :                                      extent->to(), iDim);
     366           3 :                             return nullptr;
     367             :                         }
     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        2315 :                         chunkCountFromManifest *= extent->to() - extent->from();
     373             : 
     374           0 :                         manifestRef.extents.emplace_back(extent->from(),
     375        2315 :                                                          extent->to());
     376        2315 :                         CPLDebugOnly("Icechunk",
     377             :                                      "snapshot %s,   from %" PRIu32
     378             :                                      " to %" PRIu32,
     379             :                                      snapshotIdBase32.c_str(),
     380             :                                      manifestRef.extents.back().from,
     381             :                                      manifestRef.extents.back().to);
     382             :                     }
     383             :                 }
     384             : 
     385        2232 :                 node.manifestRefs.push_back(std::move(manifestRef));
     386             : 
     387        2232 :                 if (totalNumChunksFromManifests >
     388        2232 :                     std::numeric_limits<uint64_t>::max() -
     389             :                         chunkCountFromManifest)
     390             :                 {
     391             :                     totalNumChunksFromManifests =
     392           0 :                         std::numeric_limits<uint64_t>::max();
     393           0 :                     break;
     394             :                 }
     395        2232 :                 totalNumChunksFromManifests += chunkCountFromManifest;
     396             :             }
     397             : 
     398             :             // Quick partial consistency check
     399        2228 :             if (totalNumChunksFromManifests > totalNumChunks)
     400             :             {
     401           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     402             :                          "%s: array %s: chunks referenced by manifest extents "
     403             :                          "= %" PRIu64 " > chunks in the array = %" PRIu64,
     404             :                          pszFilename, path.c_str(), totalNumChunksFromManifests,
     405             :                          totalNumChunks);
     406           1 :                 return nullptr;
     407             :             }
     408             : 
     409             :             if constexpr (IS_DEBUG_BUILD)
     410             :             {
     411             :                 // Check that all chunks of this array are referenced at most
     412             :                 // once
     413        2227 :                 ChunkIdx anChunkIdx(node.numChunks.size());
     414        2227 :                 std::string chunkStr;
     415     8090740 :                 for (uint64_t iChunk = 0; iChunk < totalNumChunks; ++iChunk)
     416             :                 {
     417     8088510 :                     chunkStr = '[';
     418    32326500 :                     for (size_t iDim = 0; iDim < node.numChunks.size(); ++iDim)
     419             :                     {
     420    24238000 :                         if (iDim > 0)
     421    16149500 :                             chunkStr += ", ";
     422    24238000 :                         chunkStr += std::to_string(anChunkIdx[iDim]);
     423             :                     }
     424     8088510 :                     chunkStr += ']';
     425             : 
     426     8088510 :                     size_t counter = node.countManifestIdForChunk(anChunkIdx);
     427     8088510 :                     if (counter > 1)
     428             :                     {
     429           0 :                         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           0 :                         return nullptr;
     434             :                     }
     435             : 
     436     8088510 :                     if (iChunk + 1 < totalNumChunks)
     437             :                     {
     438             :                         // Increment anChunkIdx
     439     8095850 :                         for (size_t iDim = node.numChunks.size(); iDim > 0;
     440             :                              /* */)
     441             :                         {
     442     8095850 :                             --iDim;
     443     8095850 :                             ++anChunkIdx[iDim];
     444     8095850 :                             if (anChunkIdx[iDim] < node.numChunks[iDim])
     445             :                             {
     446     8086280 :                                 break;
     447             :                             }
     448        9564 :                             anChunkIdx[iDim] = 0;
     449             :                         }
     450             :                     }
     451             :                 }
     452             :             }
     453             :         }
     454             : 
     455        4395 :         snapshot->m_nodes.push_back(std::move(node));
     456             :     }
     457             : 
     458        2175 :     if (needSort)
     459             :     {
     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          43 :         std::sort(snapshot->m_nodes.begin(), snapshot->m_nodes.end(),
     464          43 :                   [](const Node &a, const Node &b) { return a.path < b.path; });
     465             :     }
     466             : 
     467             :     /* --------------------------------------------------------------------*/
     468             :     /* Parse manifest_files_v2 / manifest_files[] array                    */
     469             :     /* --------------------------------------------------------------------*/
     470        2175 :     if (nVersion == 2)
     471             :     {
     472        2161 :         const auto manifests = fbsSnapshot->manifest_files_v2();
     473        2161 :         if (manifests)
     474             :         {
     475        2161 :             snapshot->m_manifestInfos.reserve(manifests->size());
     476        4373 :             for (const auto *manifestPtr : *manifests)
     477             :             {
     478        2214 :                 const auto manifestId = manifestPtr->id();
     479        2214 :                 if (!manifestId || !manifestId->bytes())
     480             :                 {
     481           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     482             :                              "%s: missing manifest id", pszFilename);
     483           2 :                     return nullptr;
     484             :                 }
     485             : 
     486        2213 :                 ManifestInfo info;
     487        2213 :                 info.sizeBytes = manifestPtr->size_bytes();
     488        2213 :                 info.numChunkRefs = manifestPtr->num_chunk_refs();
     489             :                 static_assert(sizeof(*(manifestId->bytes())) ==
     490             :                               sizeof(info.id));
     491        2213 :                 memcpy(info.id.data(), manifestId->bytes()->data(),
     492             :                        sizeof(info.id));
     493        2213 :                 info.strId = CrockfordBase32Encode(info.id);
     494             : 
     495        2213 :                 CPLDebugOnly("Icechunk",
     496             :                              "snapshot %s, manifest %s, size_bytes %" PRIu64
     497             :                              ", num_chunk_refs %u",
     498             :                              snapshotIdBase32.c_str(), info.strId.c_str(),
     499             :                              info.sizeBytes, info.numChunkRefs);
     500             : 
     501        2272 :                 if (!snapshot->m_manifestInfos.empty() &&
     502          59 :                     info.id <= snapshot->m_manifestInfos.back().id)
     503             :                 {
     504           1 :                     CPLError(
     505             :                         CE_Failure, CPLE_AppDefined,
     506             :                         "%s: ManifestInfo array not sorted by increasing id",
     507             :                         pszFilename);
     508           1 :                     return nullptr;
     509             :                 }
     510             : 
     511        2212 :                 snapshot->m_manifestInfos.push_back(std::move(info));
     512             :             }
     513             :         }
     514             :     }
     515             :     else
     516             :     {
     517          14 :         const auto manifests = fbsSnapshot->manifest_files();
     518          14 :         CPLAssertAlways(manifests);  // guaranteed by VerifySnapshotBuffer()
     519             : 
     520          14 :         snapshot->m_manifestInfos.reserve(manifests->size());
     521          34 :         for (const auto *manifestPtr : *manifests)
     522             :         {
     523          21 :             const auto manifestId = manifestPtr->id();
     524             : 
     525          21 :             ManifestInfo info;
     526          21 :             info.sizeBytes = manifestPtr->size_bytes();
     527          21 :             info.numChunkRefs = manifestPtr->num_chunk_refs();
     528             :             static_assert(sizeof(*(manifestId.bytes())) == sizeof(info.id));
     529          21 :             memcpy(info.id.data(), manifestId.bytes()->data(), sizeof(info.id));
     530          21 :             info.strId = CrockfordBase32Encode(info.id);
     531             : 
     532          21 :             CPLDebugOnly("Icechunk",
     533             :                          "snapshot %s, manifest %s, size_bytes %" PRIu64
     534             :                          ", num_chunk_refs %u",
     535             :                          snapshotIdBase32.c_str(), info.strId.c_str(),
     536             :                          info.sizeBytes, info.numChunkRefs);
     537             : 
     538          28 :             if (!snapshot->m_manifestInfos.empty() &&
     539           7 :                 info.id <= snapshot->m_manifestInfos.back().id)
     540             :             {
     541           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     542             :                          "%s: ManifestInfo array not sorted by increasing id",
     543             :                          pszFilename);
     544           1 :                 return nullptr;
     545             :             }
     546             : 
     547          20 :             snapshot->m_manifestInfos.push_back(std::move(info));
     548             :         }
     549             :     }
     550             : 
     551        2172 :     return snapshot;
     552             : }
     553             : 
     554             : #if defined(__GNUC__)
     555             : #pragma GCC diagnostic pop
     556             : #endif
     557             : 
     558             : /************************************************************************/
     559             : /*              IcechunkSnapshot::GetManifestInfoFromId()               */
     560             : /************************************************************************/
     561             : 
     562             : const IcechunkSnapshot::ManifestInfo *
     563        8102 : IcechunkSnapshot::GetManifestInfoFromId(const ObjectId12 &id) const
     564             : {
     565             : #if __cplusplus >= 202002L
     566             :     const auto iter =
     567             :         std::ranges::lower_bound(m_manifestInfos, id, {}, &ManifestInfo::id);
     568             : #else
     569       16204 :     ManifestInfo lookup;
     570        8102 :     lookup.id = id;
     571             :     const auto iter =
     572             :         std::lower_bound(m_manifestInfos.begin(), m_manifestInfos.end(), lookup,
     573        8182 :                          [](const ManifestInfo &a, const ManifestInfo &b)
     574       16284 :                          { return a.id < b.id; });
     575             : #endif
     576        8102 :     if (iter != m_manifestInfos.end() && iter->id == id)
     577        8101 :         return &(*iter);
     578           1 :     return nullptr;
     579             : }
     580             : 
     581             : /************************************************************************/
     582             : /*                 IcechunkSnapshot::GetNodeFromPath()                  */
     583             : /************************************************************************/
     584             : 
     585             : const IcechunkSnapshot::Node *
     586        8373 : IcechunkSnapshot::GetNodeFromPath(const std::string &path) const
     587             : {
     588             : #if __cplusplus >= 202002L
     589             :     const auto iter = std::ranges::lower_bound(m_nodes, path, {}, &Node::path);
     590             : #else
     591       16746 :     Node lookup;
     592        8373 :     lookup.path = path;
     593             :     const auto iter = std::lower_bound(m_nodes.begin(), m_nodes.end(), lookup,
     594       16739 :                                        [](const Node &a, const Node &b)
     595       25112 :                                        { return a.path < b.path; });
     596             : #endif
     597        8373 :     if (iter != m_nodes.end() && iter->path == path)
     598        8303 :         return &(*iter);
     599          70 :     return nullptr;
     600             : }
     601             : 
     602             : /************************************************************************/
     603             : /*                     DoRefExtentsMatchChunkIdx()                      */
     604             : /************************************************************************/
     605             : 
     606        8246 : inline bool DoRefExtentsMatchChunkIdx(
     607             :     const std::vector<IcechunkSnapshot::ChunkIndexRange> &extents,
     608             :     const ChunkIdx &anChunkIdx)
     609             : {
     610        8246 :     CPLAssert(anChunkIdx.size() == extents.size());
     611       16464 :     for (size_t iDim = 0; iDim < extents.size(); ++iDim)
     612             :     {
     613       16674 :         if (anChunkIdx[iDim] < extents[iDim].from ||
     614        8330 :             anChunkIdx[iDim] >= extents[iDim].to)
     615             :         {
     616         126 :             return false;
     617             :         }
     618             :     }
     619        8120 :     return true;
     620             : }
     621             : 
     622             : /************************************************************************/
     623             : /*              IcechunkSnapshot::findManifestIdForChunk()              */
     624             : /************************************************************************/
     625             : 
     626             : const ObjectId12 *
     627        8121 : IcechunkSnapshot::Node::findManifestIdForChunk(const ChunkIdx &anChunkIdx) const
     628             : {
     629        8121 :     CPLAssert(anChunkIdx.size() == numChunks.size());
     630             : 
     631             :     // Special case for scalar arrays such as "crs" written by Icechunk v0
     632        8139 :     if (anChunkIdx.empty() && manifestRefs.size() == 1 &&
     633           9 :         manifestRefs[0].extents.size() == 1 &&
     634        8130 :         manifestRefs[0].extents[0].from == 0 &&
     635           0 :         manifestRefs[0].extents[0].to == 1)
     636             :     {
     637           0 :         return &(manifestRefs[0].manifestId);
     638             :     }
     639             : 
     640             :     // Heuristics to find more quickly the chunk, assuming the passed chunk
     641             :     // index is contained in the last ChunkRef.
     642             :     thread_local const Node *lastNode = nullptr;
     643             :     thread_local size_t lastRefsIdx = 0;
     644        8121 :     if (lastNode == this && lastRefsIdx < manifestRefs.size())
     645             :     {
     646          97 :         const auto &ref = manifestRefs[lastRefsIdx];
     647          97 :         const bool match = DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx);
     648          97 :         CPLDebugOnly("Icechunk", "findManifestIdForChunk() guess: %s",
     649             :                      match ? "success" : "missed");
     650          97 :         if (match)
     651             :         {
     652          62 :             return &(ref.manifestId);
     653             :         }
     654             :     }
     655             : 
     656             :     // Note: if there are many manifestRefs in a node, this linear search could
     657             :     // become a bottleneck. Consider RTree / KDTree maybe.
     658        8150 :     for (const auto &ref : manifestRefs)
     659             :     {
     660        8149 :         if (DoRefExtentsMatchChunkIdx(ref.extents, anChunkIdx))
     661             :         {
     662        8058 :             lastNode = this;
     663        8058 :             lastRefsIdx = &ref - manifestRefs.data();
     664        8058 :             return &(ref.manifestId);
     665             :         }
     666             :     }
     667             : 
     668           1 :     return nullptr;
     669             : }
     670             : 
     671             : }  // namespace gdal::icechunk

Generated by: LCOV version 1.14