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 : }
|