LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_v3_codec_vlen_utf8.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 75 105 71.4 %
Date: 2026-03-26 23:25:44 Functions: 4 5 80.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver, "vlen-utf8" codec
       5             :  * Author:   Wietze Suijker
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Wietze Suijker
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr_v3_codec.h"
      14             : 
      15             : #include <algorithm>
      16             : #include <cinttypes>
      17             : #include <cstring>
      18             : #include <limits>
      19             : 
      20             : // Implements the vlen-utf8 codec from zarr-extensions:
      21             : // https://github.com/zarr-developers/zarr-extensions/tree/main/codecs/vlen-utf8
      22             : //
      23             : // Binary format (all integers little-endian u32):
      24             : //   [item_count] [len_0][data_0] [len_1][data_1] ...
      25             : //
      26             : // Decode produces a flat buffer of nItems * nSlotSize bytes where
      27             : // nSlotSize = m_oInputArrayMetadata.oElt.nativeSize (set from
      28             : // ZARR_VLEN_STRING_MAX_LENGTH, default 256).  Strings exceeding
      29             : // nSlotSize bytes are truncated.
      30             : 
      31             : /************************************************************************/
      32             : /*                        ZarrV3CodecVLenUTF8()                         */
      33             : /************************************************************************/
      34             : 
      35          14 : ZarrV3CodecVLenUTF8::ZarrV3CodecVLenUTF8() : ZarrV3Codec(NAME)
      36             : {
      37          14 : }
      38             : 
      39             : /************************************************************************/
      40             : /*             ZarrV3CodecVLenUTF8::InitFromConfiguration()             */
      41             : /************************************************************************/
      42             : 
      43          14 : bool ZarrV3CodecVLenUTF8::InitFromConfiguration(
      44             :     const CPLJSONObject &configuration,
      45             :     const ZarrArrayMetadata &oInputArrayMetadata,
      46             :     ZarrArrayMetadata &oOutputArrayMetadata, bool /* bEmitWarnings */)
      47             : {
      48          14 :     m_oConfiguration = configuration.Clone();
      49          14 :     m_oInputArrayMetadata = oInputArrayMetadata;
      50          14 :     oOutputArrayMetadata = oInputArrayMetadata;
      51          14 :     return true;
      52             : }
      53             : 
      54             : /************************************************************************/
      55             : /*                     ZarrV3CodecVLenUTF8::Clone()                     */
      56             : /************************************************************************/
      57             : 
      58           0 : std::unique_ptr<ZarrV3Codec> ZarrV3CodecVLenUTF8::Clone() const
      59             : {
      60           0 :     auto psClone = std::make_unique<ZarrV3CodecVLenUTF8>();
      61           0 :     ZarrArrayMetadata oOutputArrayMetadata;
      62           0 :     psClone->InitFromConfiguration(m_oConfiguration, m_oInputArrayMetadata,
      63             :                                    oOutputArrayMetadata,
      64             :                                    /* bEmitWarnings = */ false);
      65           0 :     return psClone;
      66             : }
      67             : 
      68             : /************************************************************************/
      69             : /*                    ZarrV3CodecVLenUTF8::Encode()                     */
      70             : /************************************************************************/
      71             : 
      72           4 : bool ZarrV3CodecVLenUTF8::Encode(const ZarrByteVectorQuickResize &abySrc,
      73             :                                  ZarrByteVectorQuickResize &abyDst) const
      74             : {
      75           4 :     const size_t nSlotSize = m_oInputArrayMetadata.oElt.nativeSize;
      76             : 
      77           4 :     if (nSlotSize == 0)
      78             :     {
      79           0 :         CPLError(CE_Failure, CPLE_AppDefined, "vlen-utf8: invalid slot size");
      80           0 :         return false;
      81             :     }
      82             : 
      83             :     const uint32_t nItems = static_cast<uint32_t>(
      84           4 :         MultiplyElements(m_oInputArrayMetadata.anBlockSizes));
      85             : 
      86           4 :     if (nItems > std::numeric_limits<size_t>::max() / nSlotSize)
      87             :     {
      88           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      89             :                  "vlen-utf8: buffer size overflow");
      90           0 :         return false;
      91             :     }
      92             : 
      93           4 :     if (abySrc.size() != static_cast<size_t>(nItems) * nSlotSize)
      94             :     {
      95           0 :         CPLError(
      96             :             CE_Failure, CPLE_AppDefined,
      97             :             "vlen-utf8: input buffer size %u != expected %u",
      98           0 :             static_cast<unsigned>(abySrc.size()),
      99             :             static_cast<unsigned>(static_cast<size_t>(nItems) * nSlotSize));
     100           0 :         return false;
     101             :     }
     102             : 
     103           4 :     const GByte *pSrc = abySrc.data();
     104             : 
     105             :     // First pass: compute total encoded size.
     106           4 :     size_t nDstSize = 4;  // item_count header
     107          15 :     for (uint32_t i = 0; i < nItems; ++i)
     108             :     {
     109          11 :         const char *pSlot = reinterpret_cast<const char *>(
     110          11 :             pSrc + static_cast<size_t>(i) * nSlotSize);
     111          11 :         const void *pNull = memchr(pSlot, 0, nSlotSize);
     112          11 :         const size_t nLen =
     113             :             pNull
     114          11 :                 ? static_cast<size_t>(static_cast<const char *>(pNull) - pSlot)
     115             :                 : nSlotSize;
     116          11 :         nDstSize += 4 + nLen;
     117             :     }
     118             : 
     119             :     try
     120             :     {
     121           4 :         abyDst.resize(nDstSize);
     122             :     }
     123           0 :     catch (const std::bad_alloc &)
     124             :     {
     125           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     126             :                  "vlen-utf8: cannot allocate %" PRIu64
     127             :                  " bytes for encoded buffer",
     128             :                  static_cast<uint64_t>(nDstSize));
     129           0 :         return false;
     130             :     }
     131             : 
     132           4 :     GByte *pDst = abyDst.data();
     133             : 
     134             :     // Write item count (little-endian u32)
     135           4 :     uint32_t nItemsLE = nItems;
     136           4 :     CPL_LSBPTR32(&nItemsLE);
     137           4 :     memcpy(pDst, &nItemsLE, 4);
     138           4 :     size_t nOffset = 4;
     139             : 
     140             :     // Write each string: [u32 len][data]
     141          15 :     for (uint32_t i = 0; i < nItems; ++i)
     142             :     {
     143          11 :         const char *pSlot = reinterpret_cast<const char *>(
     144          11 :             pSrc + static_cast<size_t>(i) * nSlotSize);
     145          11 :         const void *pNull = memchr(pSlot, 0, nSlotSize);
     146          11 :         const uint32_t nLen = static_cast<uint32_t>(
     147             :             pNull
     148          10 :                 ? static_cast<size_t>(static_cast<const char *>(pNull) - pSlot)
     149             :                 : nSlotSize);
     150             : 
     151          11 :         uint32_t nLenLE = nLen;
     152          11 :         CPL_LSBPTR32(&nLenLE);
     153          11 :         memcpy(pDst + nOffset, &nLenLE, 4);
     154          11 :         nOffset += 4;
     155             : 
     156          11 :         memcpy(pDst + nOffset, pSlot, nLen);
     157          11 :         nOffset += nLen;
     158             :     }
     159             : 
     160           4 :     return true;
     161             : }
     162             : 
     163             : /************************************************************************/
     164             : /*                    ZarrV3CodecVLenUTF8::Decode()                     */
     165             : /************************************************************************/
     166             : 
     167           6 : bool ZarrV3CodecVLenUTF8::Decode(const ZarrByteVectorQuickResize &abySrc,
     168             :                                  ZarrByteVectorQuickResize &abyDst) const
     169             : {
     170           6 :     const size_t nSrcSize = abySrc.size();
     171           6 :     const size_t nSlotSize = m_oInputArrayMetadata.oElt.nativeSize;
     172             : 
     173           6 :     if (nSlotSize == 0)
     174             :     {
     175           0 :         CPLError(CE_Failure, CPLE_AppDefined, "vlen-utf8: invalid slot size");
     176           0 :         return false;
     177             :     }
     178             : 
     179             :     // Minimum: 4 bytes for item_count
     180           6 :     if (nSrcSize < 4)
     181             :     {
     182           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     183             :                  "vlen-utf8: buffer too small for header");
     184           0 :         return false;
     185             :     }
     186             : 
     187           6 :     const GByte *pSrc = abySrc.data();
     188             : 
     189             :     // Read item count (little-endian u32)
     190           6 :     uint32_t nItems = 0;
     191           6 :     memcpy(&nItems, pSrc, 4);
     192           6 :     CPL_LSBPTR32(&nItems);
     193             : 
     194             :     const size_t nExpected =
     195           6 :         MultiplyElements(m_oInputArrayMetadata.anBlockSizes);
     196           6 :     if (nItems != nExpected)
     197             :     {
     198           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     199             :                  "vlen-utf8: item_count %u != expected %u from block shape",
     200             :                  nItems, static_cast<uint32_t>(nExpected));
     201           0 :         return false;
     202             :     }
     203             : 
     204             :     // Allocate output: nItems * nSlotSize null-padded slots
     205           6 :     if (nItems > std::numeric_limits<size_t>::max() / nSlotSize)
     206             :     {
     207           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     208             :                  "vlen-utf8: buffer size overflow");
     209           0 :         return false;
     210             :     }
     211           6 :     const size_t nDstSize = static_cast<size_t>(nItems) * nSlotSize;
     212             :     try
     213             :     {
     214           6 :         abyDst.resize(nDstSize);
     215             :     }
     216           0 :     catch (const std::bad_alloc &)
     217             :     {
     218           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     219             :                  "vlen-utf8: cannot allocate %" PRIu64
     220             :                  " bytes for decoded buffer",
     221             :                  static_cast<uint64_t>(nDstSize));
     222           0 :         return false;
     223             :     }
     224           6 :     memset(abyDst.data(), 0, nDstSize);
     225             : 
     226             :     // Parse interleaved format and copy strings into fixed-size slots.
     227             :     // Strings exceeding nSlotSize bytes are truncated.
     228           6 :     size_t nOffset = 4;
     229           6 :     GByte *pDst = abyDst.data();
     230           6 :     bool bTruncated = false;
     231          23 :     for (uint32_t i = 0; i < nItems; ++i)
     232             :     {
     233          17 :         if (nOffset + 4 > nSrcSize)
     234             :         {
     235           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     236             :                      "vlen-utf8: truncated buffer at string %u", i);
     237           0 :             return false;
     238             :         }
     239          17 :         uint32_t nLen = 0;
     240          17 :         memcpy(&nLen, pSrc + nOffset, 4);
     241          17 :         CPL_LSBPTR32(&nLen);
     242          17 :         nOffset += 4;
     243             : 
     244          17 :         if (nOffset + nLen > nSrcSize)
     245             :         {
     246           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     247             :                      "vlen-utf8: truncated buffer at string %u data", i);
     248           0 :             return false;
     249             :         }
     250             : 
     251          17 :         const size_t nCopy = std::min(static_cast<size_t>(nLen), nSlotSize);
     252          17 :         if (nLen > nSlotSize)
     253           1 :             bTruncated = true;
     254          17 :         memcpy(pDst, pSrc + nOffset, nCopy);
     255             :         // Slot is already zero-filled, so null terminator is implicit
     256          17 :         nOffset += nLen;
     257          17 :         pDst += nSlotSize;
     258             :     }
     259             : 
     260           6 :     if (bTruncated)
     261             :     {
     262           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     263             :                  "vlen-utf8: one or more strings truncated to %u bytes. "
     264             :                  "Increase ZARR_VLEN_STRING_MAX_LENGTH to read longer strings.",
     265             :                  static_cast<unsigned>(nSlotSize));
     266             :     }
     267             : 
     268           6 :     return true;
     269             : }

Generated by: LCOV version 1.14