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