LCOV - code coverage report
Current view: top level - frmts/icechunk - icechunkutils.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 94 101 93.1 %
Date: 2026-07-24 18:27:47 Functions: 3 3 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 "icechunkutils.h"
      14             : #include "icechunkdrivercore.h"
      15             : 
      16             : #include "cpl_compressor.h"
      17             : 
      18             : #include <limits>
      19             : 
      20             : namespace gdal::icechunk
      21             : {
      22             : 
      23             : /************************************************************************/
      24             : /*                     GetFilenameFromDatasetName()                     */
      25             : /************************************************************************/
      26             : 
      27        8513 : std::string GetFilenameFromDatasetName(const std::string &osDatasetName,
      28             :                                        std::string &osBranchName,
      29             :                                        std::string &osTagName,
      30             :                                        bool &ignoreTimestampEtag)
      31             : {
      32        8513 :     ignoreTimestampEtag = false;
      33       17026 :     std::string osFilename = osDatasetName;
      34        8513 :     if (STARTS_WITH_CI(osFilename.c_str(), ICECHUNK_PREFIX))
      35             :     {
      36          40 :         osFilename = osDatasetName.substr(strlen(ICECHUNK_PREFIX));
      37          40 :         const size_t nQuestionMarkPos = osFilename.find('?');
      38          40 :         if (nQuestionMarkPos != std::string::npos)
      39             :         {
      40             :             const std::string osSuffix =
      41          35 :                 osFilename.substr(nQuestionMarkPos + 1);
      42          35 :             osFilename.resize(nQuestionMarkPos);
      43             :             const CPLStringList aosTokens(
      44          35 :                 CSLTokenizeString2(osSuffix.c_str(), "&", 0));
      45          69 :             for (const char *pszToken : aosTokens)
      46             :             {
      47          35 :                 if (EQUAL(pszToken, "ignore-timestamp-etag=yes"))
      48             :                 {
      49           8 :                     ignoreTimestampEtag = true;
      50             :                 }
      51          27 :                 else if (STARTS_WITH(pszToken, "branch="))
      52             :                 {
      53           9 :                     osBranchName = pszToken + strlen("branch=");
      54             :                 }
      55          18 :                 else if (STARTS_WITH(pszToken, "tag="))
      56             :                 {
      57          17 :                     osTagName = pszToken + strlen("tag=");
      58             :                 }
      59             :                 else
      60             :                 {
      61           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
      62             :                              "Invalid Icechunk connection string");
      63           1 :                     return {};
      64             :                 }
      65             :             }
      66             :         }
      67             :     }
      68        8512 :     return osFilename;
      69             : }
      70             : 
      71             : /************************************************************************/
      72             : /*                           DecompressFile()                           */
      73             : /************************************************************************/
      74             : 
      75             : /** Read and decompress (if needed) the specified file.
      76             :  *
      77             :  * @param pszFilename Filename. Must NOT be null.
      78             :  * @param poFile Already opened file handle. Must NOT be null.
      79             :  * @param nExpectedFileType Expected file type.
      80             :  * @param[out] pnVersion File specification version. May be null
      81             :  *
      82             :  * @return tuple (pointer to content, size) or (nullptr, 0) in case of error.
      83             :  */
      84             : std::pair<std::unique_ptr<unsigned char, VSIFreeReleaser>, size_t>
      85       11942 : DecompressFile(const char *pszFilename, VSIVirtualHandle *poFile,
      86             :                int nExpectedFileType, int *pnVersion)
      87             : {
      88       11942 :     std::pair<std::unique_ptr<unsigned char, VSIFreeReleaser>, size_t> ret{
      89             :         nullptr, 0};
      90             : 
      91       11942 :     const CPLCompressor *psZSTDDecompressor = CPLGetDecompressor("zstd");
      92       11942 :     CPLAssert(psZSTDDecompressor);
      93             : 
      94       11942 :     poFile->Seek(0, SEEK_END);
      95       11942 :     const vsi_l_offset nSize64 = poFile->Tell();
      96       11942 :     if (nSize64 < HEADER_SIZE)
      97             :     {
      98           2 :         CPLError(CE_Failure, CPLE_NotSupported, "%s: too small file",
      99             :                  pszFilename);
     100           2 :         return ret;
     101             :     }
     102       11940 :     if (nSize64 > std::numeric_limits<size_t>::max() / 2)
     103             :     {
     104           0 :         CPLError(CE_Failure, CPLE_NotSupported, "%s: too large file",
     105             :                  pszFilename);
     106           0 :         return ret;
     107             :     }
     108             : 
     109       11940 :     const size_t nSize = static_cast<size_t>(nSize64);
     110       11940 :     ret.first.reset(static_cast<unsigned char *>(VSI_MALLOC_VERBOSE(nSize)));
     111       11940 :     if (!ret.first)
     112           0 :         return ret;
     113             : 
     114       11940 :     auto *pabyRaw = ret.first.get();
     115       11940 :     if (poFile->Seek(0, SEEK_SET) != 0 || poFile->Read(pabyRaw, nSize) != nSize)
     116             :     {
     117           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s: cannot ingest file",
     118             :                  pszFilename);
     119           0 :         return ret;
     120             :     }
     121             : 
     122             :     if constexpr (IS_DEBUG_BUILD)
     123             :     {
     124       11940 :         if (nExpectedFileType == FILE_TYPE_REPO_INFO)
     125             :         {
     126        8334 :             std::string osImplementationName;
     127             :             osImplementationName.assign(
     128        4167 :                 reinterpret_cast<const char *>(pabyRaw + SIG_SIZE),
     129        4167 :                 IMPLEMENTATION_NAME_SIZE);
     130        4167 :             osImplementationName.resize(strlen(osImplementationName.c_str()));
     131        4167 :             CPLDebugOnly("Icechunk", "Implementation name = '%s'",
     132             :                          osImplementationName.c_str());
     133             :         }
     134             :     }
     135             : 
     136       11940 :     if (memcmp(pabyRaw, abySIG, SIG_SIZE) != 0)
     137             :     {
     138           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     139             :                  "%s: Icechunk signature not found", pszFilename);
     140           2 :         return ret;
     141             :     }
     142             : 
     143       11938 :     const int nVersion = pabyRaw[SIG_SIZE + IMPLEMENTATION_NAME_SIZE];
     144       11938 :     if (nVersion != 1 && nVersion != 2)
     145             :     {
     146           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     147             :                  "%s: Icechunk version %d not supported", pszFilename,
     148             :                  nVersion);
     149           1 :         return ret;
     150             :     }
     151       11937 :     if (pnVersion)
     152       11937 :         *pnVersion = nVersion;
     153             : 
     154       11937 :     const int nFileType =
     155       11937 :         pabyRaw[SIG_SIZE + IMPLEMENTATION_NAME_SIZE + SPEC_VERSION_SIZE];
     156       11937 :     if (nFileType != nExpectedFileType)
     157             :     {
     158           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     159             :                  "%s: Got file type %d, expected %d", pszFilename, nFileType,
     160             :                  nExpectedFileType);
     161           1 :         return ret;
     162             :     }
     163             : 
     164       11936 :     const int nCompressionAlgo = pabyRaw[SIG_SIZE + IMPLEMENTATION_NAME_SIZE +
     165       11936 :                                          SPEC_VERSION_SIZE + FILE_TYPE_SIZE];
     166       11936 :     if (nCompressionAlgo != COMPRESSION_ALGO_NONE &&
     167             :         nCompressionAlgo != COMPRESSION_ALGO_ZSTD)
     168             :     {
     169           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     170             :                  "%s: Icechunk compression algorithm %d not supported",
     171             :                  pszFilename, nCompressionAlgo);
     172           0 :         return ret;
     173             :     }
     174             : 
     175       11936 :     const auto *pabyRawPastHeader = pabyRaw + HEADER_SIZE;
     176       11936 :     const auto nSizePastHeader = nSize - HEADER_SIZE;
     177             : 
     178       11936 :     if (nCompressionAlgo == COMPRESSION_ALGO_ZSTD)
     179             :     {
     180       11775 :         size_t nUncompressedSize = 0;
     181       11775 :         void *pabyUncompressed = nullptr;
     182       11775 :         if (!psZSTDDecompressor->pfnFunc(
     183             :                 pabyRawPastHeader, nSizePastHeader, &pabyUncompressed,
     184       11775 :                 &nUncompressedSize, nullptr, psZSTDDecompressor->user_data))
     185             :         {
     186           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     187             :                      "%s: ZSTD decompression failed", pszFilename);
     188           2 :             ret.first.reset();
     189           2 :             return ret;
     190             :         }
     191             : 
     192       11773 :         ret.first.reset(static_cast<unsigned char *>(pabyUncompressed));
     193       11773 :         ret.second = nUncompressedSize;
     194             :     }
     195             :     else
     196             :     {
     197         161 :         memmove(pabyRaw, pabyRawPastHeader, nSizePastHeader);
     198         161 :         ret.second = nSizePastHeader;
     199             :     }
     200             : 
     201       11934 :     return ret;
     202             : }
     203             : 
     204             : /************************************************************************/
     205             : /*                       CrockfordBase32Encode()                        */
     206             : /************************************************************************/
     207             : 
     208             : /** Encode the provided binary buffer as a Crockford Base32 string.
     209             :  *
     210             :  * Cf https://www.crockford.com/base32.html
     211             :  */
     212     1224490 : std::string CrockfordBase32Encode(const uint8_t *data, size_t size)
     213             : {
     214     1224490 :     std::string ret;
     215             :     // Omit I, L, O and U
     216     1224490 :     constexpr char szDict[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
     217             :     static_assert(sizeof(szDict) - 1 == 32);
     218     1224490 :     size_t i = 0;
     219     1224490 :     unsigned currentVal = 0;
     220     1224490 :     unsigned currentBitsCount = 0;
     221     1224490 :     constexpr unsigned SYMBOL_BITS = 5;
     222             :     while (true)
     223             :     {
     224    25642400 :         if (currentBitsCount < SYMBOL_BITS)
     225             :         {
     226             :             // Extra iteration when i == size is intentional
     227    17095000 :             if (i > size)
     228     1224490 :                 break;
     229    15870500 :             currentVal = (currentVal << 8) | (i < size ? data[i] : 0);
     230    15870500 :             ++i;
     231    15870500 :             currentBitsCount += 8;
     232             :         }
     233    24418000 :         const unsigned int rightShift = currentBitsCount - SYMBOL_BITS;
     234    24418000 :         const unsigned dictIdx = currentVal >> rightShift;
     235    24418000 :         CPLAssert(dictIdx < 32);
     236    24418000 :         ret += szDict[dictIdx];
     237             :         // Zero out the 5 left-most valid bits (that we just consumed)
     238    24418000 :         currentVal &= ~(31U << rightShift);
     239    24418000 :         currentBitsCount -= SYMBOL_BITS;
     240    24418000 :     }
     241     2448980 :     return ret;
     242             : }
     243             : 
     244             : }  // namespace gdal::icechunk

Generated by: LCOV version 1.14