Coverage Report

Created: 2025-06-09 08:44

/src/gdal/frmts/wms/minidriver_mrf.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  WMS Client Mini Driver
4
 * Purpose:  Implementation of Dataset and RasterBand classes for WMS
5
 *           and other similar services.
6
 * Author:   Lucian Plesea
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2016, Lucian Plesea
10
 *
11
 * Copyright 2016 Esri
12
 *
13
 * Licensed under the Apache License, Version 2.0 (the "License");
14
 * you may not use this file except in compliance with the License.
15
 * You may obtain a copy of the License at
16
 *
17
 * http://www.apache.org/licenses/LICENSE-2.0
18
 *
19
 * Unless required by applicable law or agreed to in writing, software
20
 * distributed under the License is distributed on an "AS IS" BASIS,
21
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
 * See the License for the specific language governing permissions and
23
 * limitations under the License.
24
 ****************************************************************************/
25
26
/*
27
 A WMS style minidriver that allows an MRF or an Esri bundle to be read from a
28
 URL, using one range request per tile All parameters have to be defined in the
29
 WMS file, especially for the MRF, so only simple MRF files work. For a bundle,
30
 the size is assumed to be 128 tiles of 256 pixels each, which is the standard
31
 size.
32
 */
33
34
#include "wmsdriver.h"
35
#include "minidriver_mrf.h"
36
37
using namespace WMSMiniDriver_MRF_ns;
38
39
// Copied from frmts/mrf
40
41
// A tile index record, 16 bytes, big endian
42
typedef struct
43
{
44
    GIntBig offset;
45
    GIntBig size;
46
} MRFIdx;
47
48
// Number of pages of size psz needed to hold n elements
49
static inline int pcount(const int n, const int sz)
50
0
{
51
0
    return 1 + (n - 1) / sz;
52
0
}
53
54
// Returns a pagecount per dimension, .l will have the total number
55
static inline const ILSize pcount(const ILSize &size, const ILSize &psz)
56
0
{
57
0
    ILSize count;
58
0
    count.x = pcount(size.x, psz.x);
59
0
    count.y = pcount(size.y, psz.y);
60
0
    count.z = pcount(size.z, psz.z);
61
0
    count.c = pcount(size.c, psz.c);
62
0
    count.l = static_cast<GIntBig>(count.x) * count.y * count.z * count.c;
63
0
    return count;
64
0
}
65
66
// End copied from frmts/mrf
67
68
// pread_t adapter for VSIL
69
static size_t pread_VSIL(void *user_data, void *buff, size_t count,
70
                         off_t offset)
71
0
{
72
0
    VSILFILE *fp = reinterpret_cast<VSILFILE *>(user_data);
73
0
    VSIFSeekL(fp, offset, SEEK_SET);
74
0
    return VSIFReadL(buff, 1, count, fp);
75
0
}
76
77
// pread_t adapter for curl.  We use the multi interface to get the same options
78
static size_t pread_curl(void *user_data, void *buff, size_t count,
79
                         off_t offset)
80
0
{
81
    // Use a copy of the provided request, which has the options and the URL
82
    // preset
83
0
    WMSHTTPRequest &request = *(reinterpret_cast<WMSHTTPRequest *>(user_data));
84
0
    request.Range.Printf(CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
85
0
                         static_cast<GUIntBig>(offset),
86
0
                         static_cast<GUIntBig>(offset + count - 1));
87
0
    WMSHTTPInitializeRequest(&request);
88
0
    if (WMSHTTPFetchMulti(&request) != CE_None)
89
0
    {
90
0
        CPLError(CE_Failure, CPLE_AppDefined,
91
0
                 "GDALWMS_MRF: failed to retrieve index data");
92
0
        return 0;
93
0
    }
94
95
0
    int success = (request.nStatus == 200) ||
96
0
                  (!request.Range.empty() && request.nStatus == 206);
97
0
    if (!success || request.pabyData == nullptr || request.nDataLen == 0)
98
0
    {
99
0
        CPLError(CE_Failure, CPLE_HttpResponse,
100
0
                 "GDALWMS: Unable to download data from %s",
101
0
                 request.URL.c_str());
102
0
        return 0;  // Error flag
103
0
    }
104
105
    // Might get less data than requested
106
0
    if (request.nDataLen < count)
107
0
        memset(buff, 0, count);
108
0
    memcpy(buff, request.pabyData, request.nDataLen);
109
0
    return request.nDataLen;
110
0
}
111
112
SectorCache::SectorCache(void *user_data, pread_t fn, unsigned int size,
113
                         unsigned int count)
114
0
    : n(count + 2), m(size), reader(fn ? fn : pread_VSIL),
115
0
      reader_data(user_data), last_used(nullptr)
116
0
{
117
0
}
118
119
// Returns an in-memory offset to the byte at the given address, within a sector
120
// Returns NULL if the sector can't be read
121
void *SectorCache::data(size_t address)
122
0
{
123
0
    for (size_t i = 0; i < store.size(); i++)
124
0
    {
125
0
        if (store[i].uid == address / m)
126
0
        {
127
0
            last_used = &store[i];
128
0
            return &(last_used->range[address % m]);
129
0
        }
130
0
    }
131
132
    // Not found, need a target sector to replace
133
0
    Sector *target;
134
0
    if (store.size() < m)
135
0
    {  // Create a new sector if there are slots available
136
0
        store.resize(store.size() + 1);
137
0
        target = &store.back();
138
0
    }
139
0
    else
140
0
    {  // Choose a random one to replace, but not the last used, to avoid
141
        // thrashing
142
0
        do
143
0
        {
144
0
#ifndef __COVERITY__
145
0
            target = &(store[rand() % n]);
146
#else
147
            target = &(store[0]);
148
#endif
149
0
        } while (target == last_used);
150
0
    }
151
152
0
    target->range.resize(m);
153
0
    if (reader(reader_data, &target->range[0], m,
154
0
               static_cast<off_t>((address / m) * m)))
155
0
    {  // Success
156
0
        target->uid = address / m;
157
0
        last_used = target;
158
0
        return &(last_used->range[address % m]);
159
0
    }
160
161
    // Failure
162
    // If this is the last sector, it could be a new sector with invalid data,
163
    // so we remove it Otherwise, the previous content is still good
164
0
    if (target == &store.back())
165
0
        store.pop_back();
166
    // Signal invalid request
167
0
    return nullptr;
168
0
}
169
170
// Keep in sync with the type enum
171
static const int ir_size[WMSMiniDriver_MRF::tEND] = {16, 8};
172
173
WMSMiniDriver_MRF::WMSMiniDriver_MRF()
174
0
    : m_type(tMRF), fp(nullptr), m_request(nullptr), index_cache(nullptr)
175
0
{
176
0
}
177
178
WMSMiniDriver_MRF::~WMSMiniDriver_MRF()
179
0
{
180
0
    if (index_cache)
181
0
        delete index_cache;
182
0
    if (fp)
183
0
        VSIFCloseL(fp);
184
0
    delete m_request;
185
0
}
186
187
CPLErr WMSMiniDriver_MRF::Initialize(CPLXMLNode *config,
188
                                     CPL_UNUSED char **papszOpenOptions)
189
0
{
190
    // This gets called before the rest of the WMS driver gets initialized
191
    // The MRF reader only works if all datawindow is defined within the WMS
192
    // file
193
194
0
    m_base_url = CPLGetXMLValue(config, "ServerURL", "");
195
0
    if (m_base_url.empty())
196
0
    {
197
0
        CPLError(CE_Failure, CPLE_AppDefined,
198
0
                 "GDALWMS, MRF: ServerURL missing.");
199
0
        return CE_Failure;
200
0
    }
201
202
    // Index file location, in case it is different from the normal file name
203
0
    m_idxname = CPLGetXMLValue(config, "index", "");
204
205
0
    CPLString osType(CPLGetXMLValue(config, "type", ""));
206
207
0
    if (EQUAL(osType, "bundle"))
208
0
        m_type = tBundle;
209
210
0
    if (m_type == tBundle)
211
0
    {
212
0
        m_parent_dataset->WMSSetDefaultOverviewCount(0);
213
0
        m_parent_dataset->WMSSetDefaultTileCount(128, 128);
214
0
        m_parent_dataset->WMSSetDefaultBlockSize(256, 256);
215
0
        m_parent_dataset->WMSSetDefaultTileLevel(0);
216
0
        m_parent_dataset->WMSSetNeedsDataWindow(FALSE);
217
0
        offsets.push_back(64);
218
0
    }
219
0
    else
220
0
    {  // MRF
221
0
        offsets.push_back(0);
222
0
    }
223
224
0
    return CE_None;
225
0
}
226
227
// Test for URL, things that curl can deal with while doing a range request
228
// http and https should work, not sure about ftp or file
229
int inline static is_url(const CPLString &value)
230
0
{
231
0
    return (value.ifind("http://") == 0 || value.ifind("https://") == 0 ||
232
0
            value.ifind("ftp://") == 0 || value.ifind("file://") == 0);
233
0
}
234
235
// Called after the dataset is initialized by the main WMS driver
236
CPLErr WMSMiniDriver_MRF::EndInit()
237
0
{
238
0
    int index_is_url = 1;
239
0
    if (!m_idxname.empty())
240
0
    {  // Provided, could be path or URL
241
0
        if (!is_url(m_idxname))
242
0
        {
243
0
            index_is_url = 0;
244
0
            fp = VSIFOpenL(m_idxname, "rb");
245
0
            if (fp == nullptr)
246
0
            {
247
0
                CPLError(CE_Failure, CPLE_FileIO, "Can't open index file %s",
248
0
                         m_idxname.c_str());
249
0
                return CE_Failure;
250
0
            }
251
0
            index_cache = new SectorCache(fp);
252
0
        }
253
0
    }
254
0
    else
255
0
    {  // Not provided, change extension to .idx if we can, otherwise use the
256
        // same file
257
0
        m_idxname = m_base_url;
258
0
    }
259
260
0
    if (index_is_url)
261
0
    {  // prepare a WMS request, the pread_curl will execute it repeatedly
262
0
        m_request = new WMSHTTPRequest();
263
0
        m_request->URL = m_idxname;
264
0
        m_request->options = m_parent_dataset->GetHTTPRequestOpts();
265
0
        index_cache = new SectorCache(m_request, pread_curl);
266
0
    }
267
268
    // Set the level index offsets, assume MRF order since esri bundles don't
269
    // have overviews
270
0
    ILSize size(
271
0
        m_parent_dataset->GetRasterXSize(), m_parent_dataset->GetRasterYSize(),
272
0
        1,  // Single slice for now
273
0
        1,  // Ignore the c, only single or interleved data supported by WMS
274
0
        m_parent_dataset->GetRasterBand(1)->GetOverviewCount());
275
276
0
    int psx, psy;
277
0
    m_parent_dataset->GetRasterBand(1)->GetBlockSize(&psx, &psy);
278
0
    ILSize pagesize(psx, psy, 1, 1, 1);
279
280
0
    if (m_type == tBundle)
281
0
    {  // A bundle contains 128x128 pages, regadless of the raster size
282
0
        size.x = psx * 128;
283
0
        size.y = psy * 128;
284
0
    }
285
286
0
    for (GIntBig l = size.l; l >= 0; l--)
287
0
    {
288
0
        ILSize pagecount = pcount(size, pagesize);
289
0
        pages.push_back(pagecount);
290
0
        if (l > 0)  // Only for existing levels
291
0
            offsets.push_back(offsets.back() + ir_size[m_type] * pagecount.l);
292
293
        // Sometimes this may be a 3
294
0
        size.x = pcount(size.x, 2);
295
0
        size.y = pcount(size.y, 2);
296
0
    }
297
298
0
    return CE_None;
299
0
}
300
301
// Return -1 if error occurs
302
size_t WMSMiniDriver_MRF::GetIndexAddress(
303
    const GDALWMSTiledImageRequestInfo &tiri) const
304
0
{
305
    // Bottom level is 0
306
0
    int l = -tiri.m_level;
307
0
    if (l < 0 || l >= static_cast<int>(offsets.size()))
308
0
        return ~static_cast<size_t>(0);  // Indexing error
309
0
    if (tiri.m_x >= pages[l].x || tiri.m_y >= pages[l].y)
310
0
        return ~static_cast<size_t>(0);
311
0
    return static_cast<size_t>(offsets[l] + (pages[l].x * tiri.m_y + tiri.m_x) *
312
0
                                                ir_size[m_type]);
313
0
}
314
315
// Signal errors and return error message
316
CPLErr WMSMiniDriver_MRF::TiledImageRequest(
317
    WMSHTTPRequest &request, CPL_UNUSED const GDALWMSImageRequestInfo &iri,
318
    const GDALWMSTiledImageRequestInfo &tiri)
319
0
{
320
0
    CPLString &url = request.URL;
321
0
    url = m_base_url;
322
323
0
    size_t offset = GetIndexAddress(tiri);
324
0
    if (offset == static_cast<size_t>(-1))
325
0
    {
326
0
        request.Error = "Invalid level requested";
327
0
        return CE_Failure;
328
0
    }
329
330
0
    void *raw_index = index_cache->data(offset);
331
0
    if (raw_index == nullptr)
332
0
    {
333
0
        request.Error = "Invalid indexing";
334
0
        return CE_Failure;
335
0
    };
336
337
    // Store the tile size and offset in this structure
338
0
    MRFIdx idx;
339
340
0
    if (m_type == tMRF)
341
0
    {
342
0
        memcpy(&idx, raw_index, sizeof(idx));
343
344
0
#if defined(CPL_LSB)  // raw index is MSB
345
0
        idx.offset = CPL_SWAP64(idx.offset);
346
0
        idx.size = CPL_SWAP64(idx.size);
347
0
#endif
348
0
    }
349
0
    else
350
0
    {  // Bundle
351
0
        GIntBig bidx;
352
0
        memcpy(&bidx, raw_index, sizeof(bidx));
353
354
#if defined(CPL_MSB)  // bundle index is LSB
355
        bidx = CPL_SWAP64(bidx);
356
#endif
357
358
0
        idx.offset = bidx & ((1ULL << 40) - 1);
359
0
        idx.size = bidx >> 40;
360
0
    }
361
362
    // Set the range or flag it as missing
363
0
    if (idx.size == 0)
364
0
        request.Range =
365
0
            "none";  // Signal that this block doesn't exist server-side
366
0
    else
367
0
        request.Range.Printf(CPL_FRMT_GUIB "-" CPL_FRMT_GUIB, idx.offset,
368
0
                             idx.offset + idx.size - 1);
369
370
0
    return CE_None;
371
0
}