LCOV - code coverage report
Current view: top level - frmts/icechunk - icechunkmanifest.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 165 171 96.5 %
Date: 2026-06-19 21:24:00 Functions: 7 7 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 "icechunkmanifest.h"
      14             : #include "icechunkutils.h"
      15             : #include "icechunkdrivercore.h"
      16             : 
      17             : /* ------------------------------------------------------------------------- */
      18             : 
      19             : #if defined(__clang__)
      20             : #pragma clang diagnostic push
      21             : #pragma clang diagnostic ignored "-Wdocumentation"
      22             : #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
      23             : #endif
      24             : #include <zstd.h>
      25             : #if defined(__clang__)
      26             : #pragma clang diagnostic pop
      27             : #endif
      28             : 
      29             : /* ------------------------------------------------------------------------- */
      30             : 
      31             : #include <algorithm>
      32             : #include <cinttypes>
      33             : #include <limits>
      34             : #if __cplusplus >= 202002L
      35             : #include <ranges>
      36             : #endif
      37             : 
      38             : /* ------------------------------------------------------------------------- */
      39             : 
      40             : #if defined(__GNUC__)
      41             : #pragma GCC diagnostic push
      42             : #pragma GCC diagnostic ignored "-Weffc++"
      43             : #pragma GCC diagnostic ignored "-Wnull-dereference"
      44             : #endif
      45             : 
      46             : #if defined(__clang__)
      47             : #pragma clang diagnostic push
      48             : #pragma clang diagnostic ignored "-Wweak-vtables"
      49             : #endif
      50             : 
      51             : #include "generated/manifest_generated.h"
      52             : 
      53             : #if defined(__clang__)
      54             : #pragma clang diagnostic pop
      55             : #endif
      56             : 
      57             : #if defined(__GNUC__)
      58             : #pragma GCC diagnostic pop
      59             : #endif
      60             : 
      61             : /* ------------------------------------------------------------------------- */
      62             : 
      63             : using namespace flatbuffers;
      64             : using namespace generated;
      65             : 
      66             : namespace gdal::icechunk
      67             : {
      68             : IcechunkManifest::IcechunkManifest() = default;
      69             : 
      70             : IcechunkManifest::~IcechunkManifest() = default;
      71             : 
      72             : #if defined(__GNUC__)
      73             : #pragma GCC diagnostic push
      74             : #pragma GCC diagnostic ignored "-Wnull-dereference"
      75             : #endif
      76             : 
      77             : /************************************************************************/
      78             : /*                       IcechunkManifest::Open()                       */
      79             : /************************************************************************/
      80             : 
      81             : std::unique_ptr<IcechunkManifest>
      82        2097 : IcechunkManifest::Open(const char *pszFilename)
      83             : {
      84        2097 :     CPLDebugOnly("Icechunk", "Opening manifest %s", pszFilename);
      85        4194 :     auto fp = VSIFilesystemHandler::OpenStatic(pszFilename, "rb");
      86        2097 :     if (!fp)
      87             :     {
      88           1 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", pszFilename);
      89           1 :         return nullptr;
      90             :     }
      91             : 
      92        2096 :     int nVersion = 0;
      93        2096 :     auto [buffer, size] =
      94        4192 :         DecompressFile(pszFilename, fp.get(), FILE_TYPE_MANIFEST, &nVersion);
      95        2096 :     if (!buffer)
      96           1 :         return nullptr;
      97             : 
      98             :     {
      99        2095 :         Verifier verifier(buffer.get(), size);
     100        2095 :         if (!VerifyManifestBuffer(verifier))
     101             :         {
     102           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     103             :                      "%s: invalid Manifest Flatbuffer", pszFilename);
     104           1 :             return nullptr;
     105             :         }
     106             :     }
     107             : 
     108        2094 :     const auto *fbsManifest = GetManifest(buffer.get());
     109        2094 :     const auto *id = fbsManifest->id();
     110        2094 :     CPLAssertNotNull(id);  // guaranteed by VerifyManifestBuffer()
     111        2094 :     const auto idBytes = id->bytes();
     112        2094 :     CPLAssertNotNull(idBytes);  // guaranteed by VerifyManifestBuffer()
     113        4188 :     const std::string idBase32 = CrockfordBase32Encode(*idBytes);
     114        2094 :     if (idBase32 != CPLGetFilename(pszFilename))
     115             :     {
     116           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: id=%s != expected %s",
     117             :                  pszFilename, idBase32.c_str(), CPLGetFilename(pszFilename));
     118           1 :         return nullptr;
     119             :     }
     120             : 
     121        4186 :     auto manifest = std::make_unique<IcechunkManifest>();
     122        2093 :     manifest->m_osFilename = pszFilename;
     123             : 
     124        2093 :     constexpr int COMPRESSION_ALG_NONE = 0;
     125        2093 :     constexpr int COMPRESSION_ALG_ZSTD_DICT = 1;
     126        2093 :     const int nCompressionAlg = nVersion == 1
     127        2093 :                                     ? COMPRESSION_ALG_NONE
     128        2091 :                                     : fbsManifest->compression_algorithm();
     129        2093 :     if (nCompressionAlg != COMPRESSION_ALG_NONE &&
     130             :         nCompressionAlg != COMPRESSION_ALG_ZSTD_DICT)
     131             :     {
     132           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     133             :                  "%s: invalid compression_algorithm = %d", pszFilename,
     134             :                  nCompressionAlg);
     135           1 :         return nullptr;
     136             :     }
     137             : 
     138             :     struct ZSTDContextFreer
     139             :     {
     140          13 :         void operator()(ZSTD_DCtx *ctx)
     141             :         {
     142          13 :             ZSTD_freeDCtx(ctx);
     143          13 :         }
     144             :     };
     145             : 
     146        2092 :     std::unique_ptr<ZSTD_DCtx, ZSTDContextFreer> dctx;
     147        2092 :     if (nCompressionAlg == COMPRESSION_ALG_ZSTD_DICT)
     148             :     {
     149          13 :         dctx.reset(ZSTD_createDCtx());
     150          13 :         if (!dctx)
     151           0 :             return nullptr;
     152          13 :         if (const auto location_dictionary = fbsManifest->location_dictionary())
     153             :         {
     154             : #if (ZSTD_VERSION_MAJOR > 1) ||                                                \
     155             :     (ZSTD_VERSION_MAJOR == 1 && ZSTD_VERSION_MINOR >= 4)
     156           2 :             CPLDebugOnly("Icechunk", "%s: ZSTD dictionary of size %u",
     157             :                          pszFilename,
     158             :                          static_cast<uint32_t>(location_dictionary->size()));
     159           2 :             if (ZSTD_isError(ZSTD_DCtx_loadDictionary(
     160           2 :                     dctx.get(), location_dictionary->data(),
     161           4 :                     location_dictionary->size())))
     162             :             {
     163           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     164             :                          "%s: ZSTD_DCtx_loadDictionary() failed", pszFilename);
     165           1 :                 return nullptr;
     166             :             }
     167             : #else
     168             : #error "ZSTD_DCtx_loadDictionary() requires libzstd >= 1.4"
     169             : #endif
     170             :         }
     171             :     }
     172             : 
     173             :     // 1024 should be sufficiently large for any practical purpose
     174        4182 :     std::vector<char> achTempDecompressedLocation(1024);
     175             : 
     176        2091 :     const auto *fbsArrays = fbsManifest->arrays();
     177        2091 :     CPLAssertAlways(fbsArrays);  // guaranteed by VerifyManifestBuffer()
     178        2091 :     manifest->m_arrayManifests.reserve(fbsArrays->size());
     179             : 
     180        4175 :     for (const auto *arrayManifestFbs : *fbsArrays)
     181             :     {
     182        2092 :         const auto *fbsNodeId = arrayManifestFbs->node_id();
     183        2092 :         CPLAssertNotNull(fbsNodeId);  // guaranteed by VerifyManifestBuffer()
     184        2092 :         const auto fbsNodeIdBytes = fbsNodeId->bytes();
     185        2092 :         CPLAssertAlways(
     186             :             fbsNodeIdBytes);  // guaranteed by VerifyManifestBuffer()
     187             : 
     188        2092 :         ArrayManifest arrayManifest;
     189        2092 :         ObjectId8 &nodeId = arrayManifest.nodeId;
     190             :         static_assert(sizeof(*fbsNodeIdBytes) == sizeof(nodeId));
     191        2092 :         memcpy(nodeId.data(), fbsNodeIdBytes->data(), sizeof(nodeId));
     192             : 
     193             :         // We rely on that order, required by the spec, in GetChunkRef()
     194        2093 :         if (!manifest->m_arrayManifests.empty() &&
     195           1 :             nodeId <= manifest->m_arrayManifests.back().nodeId)
     196             :         {
     197           1 :             CPLError(
     198             :                 CE_Failure, CPLE_AppDefined,
     199             :                 "%s: arrayManifests array not sorted by increasing node id",
     200             :                 pszFilename);
     201           1 :             return nullptr;
     202             :         }
     203             : 
     204        2096 :         const auto GetNodeIdStr = [fbsNodeIdBytes]()
     205        2096 :         { return CrockfordBase32Encode(*fbsNodeIdBytes); };
     206             : 
     207        2091 :         CPLDebugOnly("Icechunk", "%s: manifest nodeId %s", pszFilename,
     208             :                      GetNodeIdStr().c_str());
     209        2091 :         const auto *refs = arrayManifestFbs->refs();
     210        2091 :         CPLAssertAlways(refs);  // guaranteed by VerifyManifestBuffer()
     211        2091 :         arrayManifest.chunkRefs.reserve(refs->size());
     212             : 
     213        2091 :         manifest->m_chunkRefsCount += refs->size();
     214             : 
     215       14730 :         for (const auto *ref : *refs)
     216             :         {
     217       12646 :             const auto *index = ref->index();
     218       12646 :             CPLAssertAlways(index);  // guaranteed by VerifyManifestBuffer()
     219             : 
     220       12646 :             ChunkRef chunkRef;
     221             : 
     222       12646 :             chunkRef.idx = ChunkIdx(index->begin(), index->end());
     223       12646 :             if (!arrayManifest.chunkRefs.empty())
     224             :             {
     225       10556 :                 const auto &prevChunkRef = arrayManifest.chunkRefs.back();
     226             : 
     227             :                 // Not formally needed by the spec, but cannot hurt
     228       10556 :                 if (chunkRef.idx.size() != prevChunkRef.idx.size())
     229             :                 {
     230           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     231             :                              "%s: chunkRefs array for node %s: chunk index do "
     232             :                              "not have the same dimension",
     233           2 :                              pszFilename, GetNodeIdStr().c_str());
     234           1 :                     return nullptr;
     235             :                 }
     236             : 
     237             :                 // We rely on that order, required by the spec, in GetChunkRef()
     238       10555 :                 if (chunkRef.idx <= prevChunkRef.idx)
     239             :                 {
     240           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     241             :                              "%s: chunkRefs array for node %s: not sorted by "
     242             :                              "increasing chunk index",
     243           2 :                              pszFilename, GetNodeIdStr().c_str());
     244           1 :                     return nullptr;
     245             :                 }
     246             :             }
     247             : 
     248       12644 :             chunkRef.offset = ref->offset();
     249       12644 :             chunkRef.length = ref->length();
     250       25288 :             if (chunkRef.offset >
     251       12644 :                 std::numeric_limits<uint64_t>::max() - chunkRef.length)
     252             :             {
     253           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     254             :                          "%s: chunkRef: invalid offset/size", pszFilename);
     255           1 :                 return nullptr;
     256             :             }
     257             : 
     258             : #ifdef DEBUG
     259       12643 :             chunkRef.checksumLastModified = ref->checksum_last_modified();
     260             : 
     261       12643 :             if (const auto checksumEtag = ref->checksum_etag())
     262             :             {
     263           0 :                 chunkRef.checksumEtag = GetString(checksumEtag);
     264             :             }
     265             : #endif
     266             : 
     267       12643 :             int nAlternativeCount = 0;
     268       12643 :             if (const auto inlineContent = ref->inline_())
     269             :             {
     270        2100 :                 ++nAlternativeCount;
     271        2100 :                 chunkRef.inlineContent.insert(chunkRef.inlineContent.end(),
     272        2100 :                                               inlineContent->begin(),
     273        4200 :                                               inlineContent->end());
     274             : 
     275        2100 :                 if (chunkRef.offset != 0 || chunkRef.length != 0)
     276             :                 {
     277           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     278             :                              "%s: chunkRef: offset/size != 0 found with inline "
     279             :                              "content",
     280             :                              pszFilename);
     281           1 :                     return nullptr;
     282             :                 }
     283             :             }
     284             : 
     285       12642 :             if (const auto chunk_id = ref->chunk_id())
     286             :             {
     287         258 :                 ++nAlternativeCount;
     288         258 :                 CPLAssertAlways(
     289             :                     chunk_id->bytes());  // guaranteed by VerifyManifestBuffer()
     290         258 :                 chunkRef.chunkId = CrockfordBase32Encode(*(chunk_id->bytes()));
     291             :             }
     292             : 
     293       12642 :             if (const auto location = ref->location())
     294             :             {
     295       10273 :                 ++nAlternativeCount;
     296       10273 :                 chunkRef.location = GetString(location);
     297             :             }
     298             : 
     299       12642 :             if (const auto compressed_location = ref->compressed_location())
     300             :             {
     301          12 :                 ++nAlternativeCount;
     302          12 :                 if (nCompressionAlg == COMPRESSION_ALG_NONE)
     303             :                 {
     304             :                     // Code path likely not possible when using Icechunk writer
     305             :                     const char *pchLocation = reinterpret_cast<const char *>(
     306           1 :                         compressed_location->data());
     307             :                     chunkRef.location.insert(
     308           0 :                         chunkRef.location.end(), pchLocation,
     309           1 :                         pchLocation + compressed_location->size());
     310             :                 }
     311             :                 else
     312             :                 {
     313          11 :                     CPLAssert(nCompressionAlg == COMPRESSION_ALG_ZSTD_DICT);
     314             : 
     315          11 :                     const size_t nStatus = ZSTD_decompressDCtx(
     316          11 :                         dctx.get(), achTempDecompressedLocation.data(),
     317             :                         achTempDecompressedLocation.size(),
     318          11 :                         compressed_location->data(),
     319          11 :                         compressed_location->size());
     320          11 :                     if (ZSTD_isError(nStatus))
     321             :                     {
     322           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
     323             :                                  "%s: chunkRef node_id %s: "
     324             :                                  "ZSTD_decompressDCtx() failed",
     325           2 :                                  pszFilename, GetNodeIdStr().c_str());
     326           1 :                         return nullptr;
     327             :                     }
     328          10 :                     chunkRef.location.assign(achTempDecompressedLocation.data(),
     329          10 :                                              nStatus);
     330             :                 }
     331             :             }
     332             : 
     333       12641 :             if (nAlternativeCount == 0)
     334             :             {
     335           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     336             :                          "%s: chunkRef node_id %s: not inline, chunk or "
     337             :                          "virtual location",
     338           2 :                          pszFilename, GetNodeIdStr().c_str());
     339           1 :                 return nullptr;
     340             :             }
     341       12640 :             else if (nAlternativeCount > 1)
     342             :             {
     343           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     344             :                          "%s: chunkRef node_id %s: more than one method among "
     345             :                          "inline, chunk or virtual location found. "
     346             :                          "inlineContent.size() = %" PRIu64 ", offset = %" PRIu64
     347             :                          ", length = %" PRIu64 ", chunkId=%s, location=%s",
     348           2 :                          pszFilename, GetNodeIdStr().c_str(),
     349           1 :                          static_cast<uint64_t>(chunkRef.inlineContent.size()),
     350             :                          chunkRef.offset, chunkRef.length,
     351             :                          chunkRef.chunkId.c_str(), chunkRef.location.c_str());
     352           1 :                 return nullptr;
     353             :             }
     354             : 
     355       12639 :             arrayManifest.chunkRefs.push_back(std::move(chunkRef));
     356             :         }
     357             : 
     358        2084 :         manifest->m_arrayManifests.push_back(std::move(arrayManifest));
     359             :     }
     360             : 
     361        2083 :     return manifest;
     362             : }
     363             : 
     364             : #if defined(__GNUC__)
     365             : #pragma GCC diagnostic pop
     366             : #endif
     367             : 
     368             : /************************************************************************/
     369             : /*                 IcechunkManifest::GetChunkFilename()                 */
     370             : /************************************************************************/
     371             : 
     372           4 : std::string IcechunkManifest::GetChunkFilename(const std::string &chunkId) const
     373             : {
     374             :     return CPLFormFilenameSafe(
     375           8 :         CPLFormFilenameSafe(
     376           8 :             CPLGetDirnameSafe(CPLGetDirnameSafe(m_osFilename.c_str()).c_str())
     377             :                 .c_str(),
     378             :             "chunks", nullptr)
     379             :             .c_str(),
     380          12 :         chunkId.c_str(), nullptr);
     381             : }
     382             : 
     383             : /************************************************************************/
     384             : /*                   IcechunkManifest::GetChunkRef()                    */
     385             : /************************************************************************/
     386             : 
     387             : const IcechunkManifest::ChunkRef *
     388        8086 : IcechunkManifest::GetChunkRef(const ObjectId8 &nodeId,
     389             :                               const ChunkIdx &idx) const
     390             : {
     391             : #if __cplusplus >= 202002L
     392             :     const auto iterArrayManifests = std::ranges::lower_bound(
     393             :         m_arrayManifests, nodeId, {}, &ArrayManifest::nodeId);
     394             : #else
     395       16172 :     ArrayManifest arrayManifestLookup;
     396        8086 :     arrayManifestLookup.nodeId = nodeId;
     397             :     const auto iterArrayManifests = std::lower_bound(
     398             :         m_arrayManifests.begin(), m_arrayManifests.end(), arrayManifestLookup,
     399        8086 :         [](const ArrayManifest &a, const ArrayManifest &b)
     400       16172 :         { return a.nodeId < b.nodeId; });
     401             : #endif
     402       16172 :     if (iterArrayManifests == m_arrayManifests.end() ||
     403        8086 :         iterArrayManifests->nodeId != nodeId)
     404             :     {
     405           0 :         return nullptr;
     406             :     }
     407        8086 :     const auto &arrayManifest = *iterArrayManifests;
     408             : 
     409        8104 :     if (idx.empty() && arrayManifest.chunkRefs.size() == 1 &&
     410        8104 :         arrayManifest.chunkRefs[0].idx.size() == 1 &&
     411           0 :         arrayManifest.chunkRefs[0].idx[0] == 0)
     412             :     {
     413             :         // Special case for scalar arrays such as "crs" written by Icechunk v0
     414           0 :         return &(arrayManifest.chunkRefs[0]);
     415             :     }
     416             : 
     417             : #if __cplusplus >= 202002L
     418             :     const auto iterChunkRefs = std::ranges::lower_bound(
     419             :         arrayManifest.chunkRefs, idx, {}, &ChunkRef::idx);
     420             : #else
     421       16172 :     ChunkRef chunkRefLookup;
     422        8086 :     chunkRefLookup.idx = idx;
     423             :     const auto iterChunkRefs = std::lower_bound(
     424             :         arrayManifest.chunkRefs.begin(), arrayManifest.chunkRefs.end(),
     425             :         chunkRefLookup,
     426       32293 :         [](const ChunkRef &a, const ChunkRef &b) { return a.idx < b.idx; });
     427             : #endif
     428       16172 :     if (iterChunkRefs == arrayManifest.chunkRefs.end() ||
     429        8086 :         iterChunkRefs->idx != idx)
     430             :     {
     431           6 :         if (!arrayManifest.chunkRefs.empty() &&
     432           3 :             idx.size() != arrayManifest.chunkRefs.front().idx.size())
     433             :         {
     434           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     435             :                      "GetChunkRef(%s): querying with index of dimension %u "
     436             :                      "whereas chunk refs have dimension %u",
     437           2 :                      CrockfordBase32Encode(nodeId).c_str(),
     438           1 :                      static_cast<unsigned>(idx.size()),
     439             :                      static_cast<unsigned>(
     440           1 :                          arrayManifest.chunkRefs.front().idx.size()));
     441             :         }
     442             : 
     443           3 :         return nullptr;
     444             :     }
     445             : 
     446        8083 :     return &(*iterChunkRefs);
     447             : }
     448             : 
     449             : }  // namespace gdal::icechunk

Generated by: LCOV version 1.14