1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright (c) Contributors to the OpenEXR Project.
4 //
5 
6 //-----------------------------------------------------------------------------
7 //
8 //	class TileOffsets
9 //
10 //-----------------------------------------------------------------------------
11 
12 #include <ImfTileOffsets.h>
13 #include <ImfXdr.h>
14 #include <ImfIO.h>
15 #include "Iex.h"
16 #include "ImfNamespace.h"
17 #include <algorithm>
18 
19 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
20 
21 
TileOffsets(LevelMode mode,int numXLevels,int numYLevels,const int * numXTiles,const int * numYTiles)22 TileOffsets::TileOffsets (LevelMode mode,
23 			  int numXLevels, int numYLevels,
24 			  const int *numXTiles, const int *numYTiles)
25 :
26     _mode (mode),
27     _numXLevels (numXLevels),
28     _numYLevels (numYLevels)
29 {
30     switch (_mode)
31     {
32       case ONE_LEVEL:
33       case MIPMAP_LEVELS:
34 
35         _offsets.resize (_numXLevels);
36 
37         for (unsigned int l = 0; l < _offsets.size(); ++l)
38         {
39             _offsets[l].resize (numYTiles[l]);
40 
41             for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
42 	    {
43                 _offsets[l][dy].resize (numXTiles[l]);
44             }
45         }
46         break;
47 
48       case RIPMAP_LEVELS:
49 
50         _offsets.resize (_numXLevels * _numYLevels);
51 
52         for (int ly = 0; ly < _numYLevels; ++ly)
53         {
54             for (int lx = 0; lx < _numXLevels; ++lx)
55             {
56                 int l = ly * _numXLevels + lx;
57                 _offsets[l].resize (numYTiles[ly]);
58 
59                 for (size_t dy = 0; dy < _offsets[l].size(); ++dy)
60                 {
61                     _offsets[l][dy].resize (numXTiles[lx]);
62                 }
63             }
64         }
65         break;
66 
67       case NUM_LEVELMODES :
68           throw IEX_NAMESPACE::ArgExc("Bad initialisation of TileOffsets object");
69     }
70 }
71 
72 
73 bool
anyOffsetsAreInvalid() const74 TileOffsets::anyOffsetsAreInvalid () const
75 {
76     for (unsigned int l = 0; l < _offsets.size(); ++l)
77 	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
78 	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
79 		if (_offsets[l][dy][dx] <= 0)
80 		    return true;
81 
82     return false;
83 }
84 
85 
86 void
findTiles(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool isMultiPartFile,bool isDeep,bool skipOnly)87 TileOffsets::findTiles (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, bool isMultiPartFile, bool isDeep, bool skipOnly)
88 {
89     for (unsigned int l = 0; l < _offsets.size(); ++l)
90     {
91 	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
92 	{
93 	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
94 	    {
95 		uint64_t tileOffset = is.tellg();
96 
97 		if (isMultiPartFile)
98 		{
99 		    int partNumber;
100 		    OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, partNumber);
101 		}
102 
103 		int tileX;
104 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tileX);
105 
106 		int tileY;
107 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tileY);
108 
109 		int levelX;
110 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelX);
111 
112 		int levelY;
113 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelY);
114 
115                 if(isDeep)
116                 {
117                      uint64_t packed_offset_table_size;
118                      uint64_t packed_sample_size;
119 
120                      OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset_table_size);
121                      OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample_size);
122 
123                      // check for bad values to prevent overflow
124                      if ( packed_offset_table_size < 0 ||
125                           packed_sample_size < 0 ||
126                          ( INT64_MAX -  packed_offset_table_size < packed_sample_size) ||
127                          ( INT64_MAX - (packed_offset_table_size + packed_sample_size) ) < 8 )
128                      {
129                           throw IEX_NAMESPACE::IoExc("Invalid deep tile size");
130                      }
131 
132                      // next uint64_t is unpacked sample size - skip that too
133                      Xdr::skip <StreamIO> (is, packed_offset_table_size+packed_sample_size+8);
134 
135                 }
136                 else
137                 {
138                      int dataSize;
139 		     OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, dataSize);
140 
141                      // check for bad values to prevent overflow
142                      if ( dataSize < 0 )
143                      {
144                           throw IEX_NAMESPACE::IoExc("Invalid tile size");
145                      }
146 
147 		     Xdr::skip <StreamIO> (is, dataSize);
148                 }
149 		if (skipOnly) continue;
150 
151 		if (!isValidTile(tileX, tileY, levelX, levelY))
152 		    return;
153 
154 		operator () (tileX, tileY, levelX, levelY) = tileOffset;
155 	    }
156 	}
157     }
158 }
159 
160 
161 void
reconstructFromFile(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool isMultiPart,bool isDeep)162 TileOffsets::reconstructFromFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is,bool isMultiPart,bool isDeep)
163 {
164     //
165     // Try to reconstruct a missing tile offset table by sequentially
166     // scanning through the file, and recording the offsets in the file
167     // of the tiles we find.
168     //
169 
170     uint64_t position = is.tellg();
171 
172     try
173     {
174 	findTiles (is,isMultiPart,isDeep,false);
175     }
176     catch (...) //NOSONAR - suppress vulnerability reports from SonarCloud.
177     {
178         //
179         // Suppress all exceptions.  This function is called only to
180 	// reconstruct the tile offset table for incomplete files,
181 	// and exceptions are likely.
182         //
183     }
184 
185     is.clear();
186     is.seekg (position);
187 }
188 
189 
190 void
readFrom(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool & complete,bool isMultiPartFile,bool isDeep)191 TileOffsets::readFrom (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, bool &complete,bool isMultiPartFile, bool isDeep)
192 {
193     //
194     // Read in the tile offsets from the file's tile offset table
195     //
196 
197     for (unsigned int l = 0; l < _offsets.size(); ++l)
198 	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
199 	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
200 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, _offsets[l][dy][dx]);
201 
202     //
203     // Check if any tile offsets are invalid.
204     //
205     // Invalid offsets mean that the file is probably incomplete
206     // (the offset table is the last thing written to the file).
207     // Either some process is still busy writing the file, or
208     // writing the file was aborted.
209     //
210     // We should still be able to read the existing parts of the
211     // file.  In order to do this, we have to make a sequential
212     // scan over the scan tile to reconstruct the tile offset
213     // table.
214     //
215 
216     if (anyOffsetsAreInvalid())
217     {
218 	complete = false;
219 	reconstructFromFile (is,isMultiPartFile,isDeep);
220     }
221     else
222     {
223 	complete = true;
224     }
225 
226 }
227 
228 
229 void
readFrom(std::vector<uint64_t> chunkOffsets,bool & complete)230 TileOffsets::readFrom (std::vector<uint64_t> chunkOffsets,bool &complete)
231 {
232     size_t totalSize = 0;
233 
234     for (unsigned int l = 0; l < _offsets.size(); ++l)
235         for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
236             totalSize += _offsets[l][dy].size();
237 
238     if (chunkOffsets.size() != totalSize)
239         throw IEX_NAMESPACE::ArgExc ("Wrong offset count, not able to read from this array");
240 
241 
242 
243     int pos = 0;
244     for (size_t l = 0; l < _offsets.size(); ++l)
245         for (size_t dy = 0; dy < _offsets[l].size(); ++dy)
246             for (size_t dx = 0; dx < _offsets[l][dy].size(); ++dx)
247             {
248                 _offsets[l][dy][dx] = chunkOffsets[pos];
249                 pos++;
250             }
251 
252     complete = !anyOffsetsAreInvalid();
253 
254 }
255 
256 
257 uint64_t
writeTo(OPENEXR_IMF_INTERNAL_NAMESPACE::OStream & os) const258 TileOffsets::writeTo (OPENEXR_IMF_INTERNAL_NAMESPACE::OStream &os) const
259 {
260     //
261     // Write the tile offset table to the file, and
262     // return the position of the start of the table
263     // in the file.
264     //
265 
266     uint64_t pos = os.tellp();
267 
268     if (pos == static_cast<uint64_t>(-1))
269 	IEX_NAMESPACE::throwErrnoExc ("Cannot determine current file position (%T).");
270 
271     for (unsigned int l = 0; l < _offsets.size(); ++l)
272 	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
273 	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
274 		OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (os, _offsets[l][dy][dx]);
275 
276     return pos;
277 }
278 
279 namespace {
280 struct tilepos{
281     uint64_t filePos;
282     int dx;
283     int dy;
284     int l;
operator <__anonf84dcb2d0111::tilepos285     bool operator <(const tilepos & other) const
286     {
287         return filePos < other.filePos;
288     }
289 };
290 }
291 //-------------------------------------
292 // fill array with tile coordinates in the order they appear in the file
293 //
294 // each input array must be of size (totalTiles)
295 //
296 //
297 // if the tile order is not RANDOM_Y, it is more efficient to compute the
298 // tile ordering rather than using this function
299 //
300 //-------------------------------------
getTileOrder(int dx_table[],int dy_table[],int lx_table[],int ly_table[]) const301 void TileOffsets::getTileOrder(int dx_table[],int dy_table[],int lx_table[],int ly_table[]) const
302 {
303     //
304     // helper class
305     //
306 
307     // how many entries?
308     size_t entries=0;
309     for (unsigned int l = 0; l < _offsets.size(); ++l)
310         for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
311            entries+=_offsets[l][dy].size();
312 
313     std::vector<struct tilepos> table(entries);
314 
315     size_t i = 0;
316     for (unsigned int l = 0; l < _offsets.size(); ++l)
317         for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
318             for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
319             {
320                 table[i].filePos = _offsets[l][dy][dx];
321                 table[i].dx = dx;
322                 table[i].dy = dy;
323                 table[i].l = l;
324 
325                 ++i;
326 
327             }
328 
329     std::sort(table.begin(),table.end());
330 
331     //
332     // write out the values
333     //
334 
335     // pass 1: write out dx and dy, since these are independent of level mode
336 
337     for(size_t i=0;i<entries;i++)
338     {
339         dx_table[i] = table[i].dx;
340         dy_table[i] = table[i].dy;
341     }
342 
343     // now write out the levels, which depend on the level mode
344 
345     switch (_mode)
346     {
347         case ONE_LEVEL:
348         {
349             for(size_t i=0;i<entries;i++)
350             {
351                 lx_table[i] = 0;
352                 ly_table[i] = 0;
353             }
354             break;
355         }
356         case MIPMAP_LEVELS:
357         {
358             for(size_t i=0;i<entries;i++)
359             {
360                 lx_table[i]= table[i].l;
361                 ly_table[i] =table[i].l;
362 
363             }
364             break;
365         }
366 
367         case RIPMAP_LEVELS:
368         {
369             for(size_t i=0;i<entries;i++)
370             {
371                 lx_table[i]= table[i].l % _numXLevels;
372                 ly_table[i] = table[i].l / _numXLevels;
373 
374             }
375             break;
376         }
377         case NUM_LEVELMODES :
378             throw IEX_NAMESPACE::LogicExc("Bad level mode getting tile order");
379     }
380 
381 
382 
383 }
384 
385 
386 bool
isEmpty() const387 TileOffsets::isEmpty () const
388 {
389     for (unsigned int l = 0; l < _offsets.size(); ++l)
390 	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
391 	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
392 		if (_offsets[l][dy][dx] != 0)
393 		    return false;
394     return true;
395 }
396 
397 
398 bool
isValidTile(int dx,int dy,int lx,int ly) const399 TileOffsets::isValidTile (int dx, int dy, int lx, int ly) const
400 {
401     if(lx<0 || ly < 0 || dx<0 || dy < 0) return false;
402     switch (_mode)
403     {
404       case ONE_LEVEL:
405 
406         if (lx == 0 &&
407 	    ly == 0 &&
408 	    _offsets.size() > 0 &&
409             int(_offsets[0].size()) > dy &&
410             int(_offsets[0][dy].size()) > dx)
411 	{
412             return true;
413 	}
414 
415         break;
416 
417       case MIPMAP_LEVELS:
418 
419         if (lx < _numXLevels &&
420 	    ly < _numYLevels &&
421             int(_offsets.size()) > lx &&
422             int(_offsets[lx].size()) > dy &&
423             int(_offsets[lx][dy].size()) > dx)
424 	{
425             return true;
426 	}
427 
428         break;
429 
430       case RIPMAP_LEVELS:
431 
432         if (lx < _numXLevels &&
433 	    ly < _numYLevels &&
434 	    (_offsets.size() > (size_t) lx+  ly *  (size_t) _numXLevels) &&
435             int(_offsets[lx + ly * _numXLevels].size()) > dy &&
436             int(_offsets[lx + ly * _numXLevels][dy].size()) > dx)
437 	{
438             return true;
439 	}
440 
441         break;
442 
443       default:
444 
445         return false;
446     }
447 
448     return false;
449 }
450 
451 
452 uint64_t &
operator ()(int dx,int dy,int lx,int ly)453 TileOffsets::operator () (int dx, int dy, int lx, int ly)
454 {
455     //
456     // Looks up the value of the tile with tile coordinate (dx, dy)
457     // and level number (lx, ly) in the _offsets array, and returns
458     // the cooresponding offset.
459     //
460 
461     switch (_mode)
462     {
463       case ONE_LEVEL:
464 
465         return _offsets[0][dy][dx];
466 
467       case MIPMAP_LEVELS:
468 
469         return _offsets[lx][dy][dx];
470 
471       case RIPMAP_LEVELS:
472 
473         return _offsets[lx + ly * _numXLevels][dy][dx];
474 
475       default:
476 
477         throw IEX_NAMESPACE::ArgExc ("Unknown LevelMode format.");
478     }
479 }
480 
481 
482 uint64_t &
operator ()(int dx,int dy,int l)483 TileOffsets::operator () (int dx, int dy, int l)
484 {
485     return operator () (dx, dy, l, l);
486 }
487 
488 
489 const uint64_t &
operator ()(int dx,int dy,int lx,int ly) const490 TileOffsets::operator () (int dx, int dy, int lx, int ly) const
491 {
492     //
493     // Looks up the value of the tile with tile coordinate (dx, dy)
494     // and level number (lx, ly) in the _offsets array, and returns
495     // the cooresponding offset.
496     //
497 
498     switch (_mode)
499     {
500       case ONE_LEVEL:
501 
502         return _offsets[0][dy][dx];
503 
504       case MIPMAP_LEVELS:
505 
506         return _offsets[lx][dy][dx];
507 
508       case RIPMAP_LEVELS:
509 
510         return _offsets[lx + ly * _numXLevels][dy][dx];
511 
512       default:
513 
514         throw IEX_NAMESPACE::ArgExc ("Unknown LevelMode format.");
515     }
516 }
517 
518 
519 const uint64_t &
operator ()(int dx,int dy,int l) const520 TileOffsets::operator () (int dx, int dy, int l) const
521 {
522     return operator () (dx, dy, l, l);
523 }
524 
525 const std::vector<std::vector<std::vector <uint64_t> > >&
getOffsets() const526 TileOffsets::getOffsets() const
527 {
528     return _offsets;
529 }
530 
531 
532 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
533