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
|