1 // -*- coding: utf-8 -*-
2 //
3 // zlibstream.cxx --- IOStreams classes for working with RFC 1950 and RFC 1952
4 //                    compression formats (respectively known as the zlib and
5 //                    gzip formats)
6 //
7 // Copyright (C) 2017  Florent Rougon
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Library General Public
11 // License as published by the Free Software Foundation; either
12 // version 2 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // Library General Public License for more details.
18 //
19 // You should have received a copy of the GNU Library General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 // MA  02110-1301  USA.
23 
24 #include <simgear_config.h>
25 
26 #include <string>
27 #include <ios>                  // std::streamsize
28 #include <istream>
29 #include <memory>               // std::unique_ptr
30 #include <utility>              // std::move()
31 #include <algorithm>
32 #include <stdexcept>
33 #include <unordered_map>
34 #include <limits>               // std::numeric_limits
35 #include <type_traits>          // std::make_unsigned(), std::underlying_type
36 #include <cstddef>              // std::size_t, std::ptrdiff_t
37 #include <cassert>
38 
39 #include <zlib.h>
40 
41 #include <simgear/debug/logstream.hxx>
42 #include <simgear/io/iostreams/zlibstream.hxx>
43 #include <simgear/misc/strutils.hxx>
44 #include <simgear/misc/sg_path.hxx>
45 #include <simgear/sg_inlines.h>
46 #include <simgear/structure/exception.hxx>
47 
48 using std::string;
49 using simgear::enumValue;
50 
51 using traits = std::char_traits<char>;
52 
53 // Private utility function
zlibErrorMessage(const z_stream & zstream,int errorCode)54 static string zlibErrorMessage(const z_stream& zstream, int errorCode)
55 {
56   string res;
57   std::unordered_map<int, string> errorCodeToMessageMap = {
58     {Z_OK,            "zlib: no error (code Z_OK)"},
59     {Z_STREAM_END,    "zlib stream end"},
60     {Z_NEED_DICT,     "zlib: Z_NEED_DICT"},
61     {Z_STREAM_ERROR,  "zlib stream error"},
62     {Z_DATA_ERROR,    "zlib data error"},
63     {Z_MEM_ERROR,     "zlib memory error"},
64     {Z_BUF_ERROR,     "zlib buffer error"},
65     {Z_VERSION_ERROR, "zlib version error"}
66   };
67 
68   if (errorCode == Z_ERRNO) {
69     res = simgear::strutils::error_string(errno);
70   } else if (zstream.msg != nullptr) {
71     // Caution: this only works if the zstream structure hasn't been
72     // deallocated!
73     res = "zlib: " + string(zstream.msg);
74   } else {
75     try {
76       res = errorCodeToMessageMap.at(errorCode);
77     } catch (const std::out_of_range&) {
78       res = string("unknown zlib error (code " + std::to_string(errorCode) +
79                    ")");
80     }
81   }
82 
83   return res;
84 }
85 
86 // Requirement: 'val' must be non-negative.
87 //
88 // Return:
89 //   - 'val' cast as BoundType if it's lower than the largest value BoundType
90 //     can represent;
91 //   - this largest value otherwise.
92 template<class BoundType, class T>
clipCast(T val)93 static BoundType clipCast(T val)
94 {
95   typedef typename std::make_unsigned<T>::type uT;
96   typedef typename std::make_unsigned<BoundType>::type uBoundType;
97   assert(val >= 0); // otherwise, the comparison and cast to uT would be unsafe
98 
99   // Casts to avoid the signed-compare warning; they don't affect the values,
100   // since both are non-negative.
101   if (static_cast<uT>(val) <
102       static_cast<uBoundType>( std::numeric_limits<BoundType>::max() )) {
103     return static_cast<BoundType>(val);
104   } else {
105     return std::numeric_limits<BoundType>::max();
106   }
107 }
108 
109 // Requirement: 'size' must be non-negative.
110 //
111 // Return:
112 //   - 'size' if it is lower than or equal to std::numeric_limits<uInt>::max();
113 //   - std::numeric_limits<uInt>::max() cast as a T otherwise (this is always
114 //     possible in a lossless way, since in this case, one has
115 //     0 <= std::numeric_limits<uInt>::max() < size, and 'size' is of type T).
116 //
117 // Note: uInt is the type of z_stream.avail_in and z_stream.avail_out, hence
118 //       the function name.
119 template<class T>
zlibChunk(T size)120 static T zlibChunk(T size)
121 {
122   typedef typename std::make_unsigned<T>::type uT;
123   assert(size >= 0); // otherwise, the comparison and cast to uT would be unsafe
124 
125   if (static_cast<uT>(size) <= std::numeric_limits<uInt>::max()) {
126     return size;
127   } else {
128     // In this case, we are sure that T can represent
129     // std::numeric_limits<uInt>::max(), thus the cast is safe.
130     return static_cast<T>(std::numeric_limits<uInt>::max());
131   }
132 }
133 
134 namespace simgear
135 {
136 
137 // ***************************************************************************
138 // *                      ZlibAbstractIStreambuf class                       *
139 // ***************************************************************************
140 
141 // Common initialization. Subclasses must complete the z_stream struct
142 // initialization with a call to deflateInit2() or inflateInit2(), typically.
ZlibAbstractIStreambuf(std::istream & iStream,const SGPath & path,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)143 ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(std::istream& iStream,
144                                                const SGPath& path,
145                                                char* inBuf,
146                                                std::size_t inBufSize,
147                                                char *outBuf,
148                                                std::size_t outBufSize,
149                                                std::size_t putbackSize)
150   : _iStream(iStream),
151     _iStream_p(nullptr),
152     _path(path),
153     _inBuf(inBuf),
154     _inBufSize(inBufSize),
155     _outBuf(outBuf),
156     _outBufSize(outBufSize),
157     _putbackSize(putbackSize)
158 {
159   assert(_inBufSize > 0);
160   assert(_putbackSize >= 0);    // guaranteed unless the type is changed...
161   assert(_putbackSize < _outBufSize);
162 
163   if (_inBuf == nullptr) {
164     _inBuf = new char[_inBufSize];
165     _inBufMustBeFreed = true;
166   }
167 
168   if (_outBuf == nullptr) {
169     _outBuf = new char[_outBufSize];
170     _outBufMustBeFreed = true;
171   }
172 
173   _inBufEndPtr = _inBuf;
174   // The input buffer is empty.
175   _zstream.next_in = reinterpret_cast<unsigned char *>(_inBuf);
176   _zstream.avail_in = 0;
177   // ZLib's documentation says its init functions such as inflateInit2() might
178   // consume stream input, therefore let's fill the input buffer now. This
179   // way, constructors of derived classes just have to call the appropriate
180   // ZLib init function: the data will already be in place.
181   getInputData();
182 
183   // Force underflow() of the stream buffer on the first read. We could use
184   // some other value, but I avoid nullptr in order to be sure we can always
185   // reliably compare the three pointers with < and >, as well as compute the
186   // difference between any two of them.
187   setg(_outBuf, _outBuf, _outBuf);
188 }
189 
ZlibAbstractIStreambuf(std::unique_ptr<std::istream> iStream_p,const SGPath & path,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)190 ZlibAbstractIStreambuf::ZlibAbstractIStreambuf(
191   std::unique_ptr<std::istream> iStream_p,
192   const SGPath& path,
193   char* inBuf,
194   std::size_t inBufSize,
195   char *outBuf,
196   std::size_t outBufSize,
197   std::size_t putbackSize)
198   : ZlibAbstractIStreambuf(*iStream_p, path, inBuf, inBufSize,
199                            outBuf, outBufSize, putbackSize)
200 {
201   // Take ownership of the object. This is a way to ensure that the _iStream
202   // reference stays valid as long as our instance is alive, and that the
203   // corresponding std::istream object is automatically destroyed as soon as
204   // our instance is itself destroyed.
205   _iStream_p = std::move(iStream_p);
206 }
207 
~ZlibAbstractIStreambuf()208 ZlibAbstractIStreambuf::~ZlibAbstractIStreambuf()
209 {
210   if (_inBufMustBeFreed) {
211     delete[] _inBuf;
212   }
213 
214   if (_outBufMustBeFreed) {
215     delete[] _outBuf;
216   }
217 }
218 
219 // Fill or refill the output buffer, and update the three pointers
220 // corresponding to eback(), gptr() and egptr().
underflow()221 int ZlibAbstractIStreambuf::underflow()
222 {
223   if (_allFinished) {
224     return traits::eof();
225   }
226 
227   // According to the C++11 standard: “The public members of basic_streambuf
228   // call this virtual function only if gptr() is null or gptr() >= egptr()”.
229   // Still, it seems some people do the following or similar (maybe in case
230   // underflow() is called “incorrectly”?). See for instance N. Josuttis, The
231   // C++ Standard Library (1st edition), p. 584. One sure thing is that it
232   // can't hurt (except performance, marginally), so let's do it.
233   if (gptr() < egptr()) {
234     return traits::to_int_type(*gptr());
235   }
236 
237   assert(gptr() == egptr());
238   assert(egptr() - eback() >= 0);
239   std::size_t nbPutbackChars = std::min(
240     static_cast<std::size_t>(egptr() - eback()), // OK because egptr() >= eback()
241     _putbackSize);
242   std::copy(egptr() - nbPutbackChars, egptr(),
243             _outBuf + _putbackSize - nbPutbackChars);
244 
245   setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
246        _outBuf + _putbackSize,                  // start of obtained data
247        fillOutputBuffer());                     // one-past-end of obtained data
248 
249   return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr());
250 }
251 
252 // Simple utility method for fillOutputBuffer(), used to improve readability.
253 // Return the remaining space available in the output buffer, where zlib can
254 // write.
255 std::size_t
fOB_remainingSpace(unsigned char * nextOutPtr) const256 ZlibAbstractIStreambuf::fOB_remainingSpace(unsigned char* nextOutPtr) const
257 {
258   std::ptrdiff_t remainingSpaceInOutBuf = (
259     _outBuf + _outBufSize - reinterpret_cast<char*>(nextOutPtr));
260   assert(remainingSpaceInOutBuf >= 0);
261 
262   return static_cast<std::size_t>(remainingSpaceInOutBuf);
263 }
264 
265 // Simple method for dealing with the Z_BUF_ERROR code that may be returned
266 // by zlib's deflate() and inflate() methods.
handleZ_BUF_ERROR() const267 [[ noreturn ]] void ZlibAbstractIStreambuf::handleZ_BUF_ERROR() const
268 {
269   switch (operationType()) {
270   case OperationType::DECOMPRESSION:
271   {
272     string message = (_path.isNull()) ?
273       "Got Z_BUF_ERROR from zlib while decompressing a stream. The stream "
274       "was probably incomplete."
275       :
276       "Got Z_BUF_ERROR from zlib during decompression. The compressed stream "
277       "was probably incomplete.";
278     // When _path.isNull(), sg_location(_path) is equivalent to sg_location()
279     throw sg_io_exception(message, sg_location(_path));
280   }
281   case OperationType::COMPRESSION:
282     throw std::logic_error(
283       "Called ZlibAbstractIStreambuf::handleZ_BUF_ERROR() with "
284       "operationType() == ZlibAbstractIStreambuf::OperationType::COMPRESSION");
285   default:
286     throw std::logic_error(
287       "Unexpected operationType() in "
288       "ZlibAbstractIStreambuf::handleZ_BUF_ERROR(): " +
289       std::to_string(enumValue(operationType())));
290   }
291 }
292 
293 // Fill or refill the output buffer. Return a pointer to the first unused char
294 // in _outBuf (i.e., right after the data written by this method, if any;
295 // otherwise: _outBuf + _putbackSize).
fillOutputBuffer()296 char* ZlibAbstractIStreambuf::fillOutputBuffer()
297 {
298   bool allInputRead = false;
299   std::size_t remainingSpaceInOutBuf;
300   int retCode;
301 
302   // We have to do these unpleasant casts, because zlib uses pointers to
303   // unsigned char for its input and output buffers, while the IOStreams
304   // library in the C++ standard uses plain char pointers...
305   _zstream.next_out = reinterpret_cast<unsigned char*>(_outBuf + _putbackSize);
306   remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
307 
308   while (remainingSpaceInOutBuf > 0) {
309     _zstream.avail_out = clipCast<uInt>(remainingSpaceInOutBuf);
310 
311     if (_zstream.avail_in == 0 && !allInputRead) {
312       // Get data from _iStream, store it in _inBuf
313       allInputRead = getInputData();
314     }
315 
316     // Make zlib process some data (compress or decompress it). This updates
317     // _zstream.{avail,next}_{in,out} (4 fields of the z_stream struct).
318     retCode = zlibProcessData();
319 
320     if (retCode == Z_BUF_ERROR) {
321       handleZ_BUF_ERROR();            // doesn't return
322     } else if (retCode == Z_STREAM_END) {
323       assert(_zstream.avail_in == 0); // all of _inBuf must have been used
324       _allFinished = true;
325       break;
326     } else if (retCode < 0) {         // negative codes are errors
327       throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
328                             sg_location(_path));
329     }
330 
331     remainingSpaceInOutBuf = fOB_remainingSpace(_zstream.next_out);
332   }
333 
334   return reinterpret_cast<char*>(_zstream.next_out);
335 }
336 
337 // This method provides input data to zlib:
338 //   - if data is already available in _inBuf, it updates _zstream.avail_in
339 //     accordingly (using the largest possible amount given _zstream.avail_in's
340 //     type, i.e., uInt);
341 //   - otherwise, it reads() as much data as possible into _inBuf from
342 //     _iStream and updates _zstream.avail_in to tell zlib about this new
343 //     available data (again: largest possible amount given the uInt type).
344 //
345 // This method must be called only when _zstream.avail_in == 0, which means
346 // zlib has read everything we fed it (and does *not* mean, by the way, that
347 // the input buffer starting at _inBuf is empty; this is because the buffer
348 // size might exceed what can be represented by an uInt).
349 //
350 // On return:
351 //   - either EOF has been reached for _iStream, and the return value is true;
352 //   - or _zstream.avail_in > 0, and the return value is false.
353 //
354 // Note: don't read more in the previous paragraph concerning the state on
355 //       return. In the first case, there is *no guarantee* about
356 //       _zstream.avail_in's value; in the second case, there is *no guarantee*
357 //       about whether EOF has been reached for _iStream.
getInputData()358 bool ZlibAbstractIStreambuf::getInputData()
359 {
360   bool allInputRead = false;
361 
362   assert(_zstream.avail_in == 0);
363   std::ptrdiff_t alreadyAvailable =
364     _inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
365   assert(alreadyAvailable >= 0);
366 
367   // Data already available?
368   if (alreadyAvailable > 0) {
369     _zstream.avail_in = clipCast<uInt>(alreadyAvailable);
370     return allInputRead;
371   }
372 
373   if (_inBufEndPtr == _inBuf + _inBufSize) { // buffer full, rewind
374     _inBufEndPtr = _inBuf;
375     _zstream.next_in = reinterpret_cast<unsigned char*>(_inBuf);
376   }
377 
378   // Fill the input buffer (as much as possible)
379   while (_inBufEndPtr < _inBuf + _inBufSize && !_iStream.eof()) {
380     std::streamsize nbCharsToRead = clipCast<std::streamsize>(
381       _inBuf + _inBufSize - _inBufEndPtr);
382     _iStream.read(_inBufEndPtr, nbCharsToRead);
383 
384     if (_iStream.bad()) {
385       string errMsg = simgear::strutils::error_string(errno);
386       string msgStart = (_path.isNull()) ?
387         "Error while reading from a stream" : "Read error";
388       throw sg_io_exception(msgStart + ": " + errMsg, sg_location(_path));
389     }
390 
391     // Could be zero if at EOF
392     std::streamsize nbCharsRead = _iStream.gcount();
393     // std::streamsize is a signed integral type!
394     assert(0 <= nbCharsRead);
395     _inBufEndPtr += nbCharsRead;
396   }
397 
398   std::ptrdiff_t availableChars =
399     _inBufEndPtr - reinterpret_cast<char*>(_zstream.next_in);
400   // assert(availableChars >= 0);    <-- already done in clipCast<uInt>()
401   _zstream.avail_in = clipCast<uInt>(availableChars);
402 
403   if (_iStream.eof()) {
404     allInputRead = true;
405   } else {
406     // Trying to rewind a fully read std::istringstream with seekg() can lead
407     // to a weird state, where the stream doesn't return any character but
408     // doesn't report EOF either. Make sure we are not in this situation.
409     assert(_zstream.avail_in > 0);
410   }
411 
412   return allInputRead;
413 }
414 
415 // Implementing this method is optional, but should provide better
416 // performance. It makes zlib write the data directly in the buffer starting
417 // at 'dest'. Without it, the data would go through an intermediate buffer
418 // (_outBuf) before being copied to its (hopefully) final destination.
xsgetn(char * dest,std::streamsize n)419 std::streamsize ZlibAbstractIStreambuf::xsgetn(char* dest, std::streamsize n)
420 {
421   // Despite the somewhat misleading footnote 296 of §27.5.3 of the C++11
422   // standard, one can't assume std::size_t to be at least as large as
423   // std::streamsize (64 bits std::streamsize in a 32 bits Windows program).
424   std::streamsize remaining = n;
425   char* origGptr = gptr();
426   char* writePtr = dest;        // we'll need dest later -> work with a copy
427 
428   // First, let's take data present in our internal buffer (_outBuf)
429   while (remaining > 0) {
430     // Number of available chars in _outBuf
431     std::ptrdiff_t avail = egptr() - gptr();
432     if (avail == 0) {           // our internal buffer is empty
433       break;
434     }
435 
436     // We need an int for gbump(), at least in C++11.
437     int chunkSize_i = clipCast<int>(avail);
438     if (chunkSize_i > remaining) {
439       chunkSize_i = static_cast<int>(remaining);
440     }
441     assert(chunkSize_i >= 0);
442 
443     std::copy(gptr(), gptr() + chunkSize_i, writePtr);
444     gbump(chunkSize_i);
445     writePtr += chunkSize_i;
446     // This cast is okay because 0 <= chunkSize_i <= remaining, which is an
447     // std::streamsize
448     remaining -= static_cast<std::streamsize>(chunkSize_i);
449   }
450 
451   if (remaining == 0) {
452     // Everything we needed was already in _outBuf. The putback area is set up
453     // as it should between eback() and the current gptr(), so we are fine to
454     // return.
455     return n;
456   }
457 
458   // Now, let's make it so that the remaining data we need is directly written
459   // to the destination area, without going through _outBuf.
460   _zstream.next_out = reinterpret_cast<unsigned char*>(writePtr);
461   bool allInputRead = false;
462   int retCode;
463 
464   while (remaining > 0) {
465     std::streamsize chunkSize_s = zlibChunk(remaining);
466     // chunkSize_s > 0 and does fit in a zlib uInt: that's the whole point of
467     // zlibChunk.
468     _zstream.avail_out = static_cast<uInt>(chunkSize_s);
469 
470     if (_zstream.avail_in == 0 && !allInputRead) {
471       allInputRead = getInputData();
472     }
473 
474     // Make zlib process some data (compress or decompress). This updates
475     // _zstream.{avail,next}_{in,out} (4 fields of the z_stream struct).
476     retCode = zlibProcessData();
477     // chunkSize_s - _zstream.avail_out is the number of chars written by zlib.
478     // 0 <= _zstream.avail_out <= chunkSize_s, which is an std::streamsize.
479     remaining -= chunkSize_s - static_cast<std::streamsize>(_zstream.avail_out);
480 
481     if (retCode == Z_BUF_ERROR) {
482       handleZ_BUF_ERROR();            // doesn't return
483     } else if (retCode == Z_STREAM_END) {
484       assert(_zstream.avail_in == 0); // all of _inBuf must have been used
485       _allFinished = true;
486       break;
487     } else if (retCode < 0) {   // negative codes are errors
488       throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
489                             sg_location(_path));
490     }
491   }
492 
493   // Finally, copy chars to the putback area.
494   std::size_t nbPutbackChars = xsgetn_preparePutbackArea(
495     origGptr, dest, reinterpret_cast<char*>(_zstream.next_out));
496   setg(_outBuf + _putbackSize - nbPutbackChars, // start of putback area
497        _outBuf + _putbackSize,                  // the buffer for pending,
498        _outBuf + _putbackSize);                 // available data is empty
499 
500   assert(remaining >= 0);
501   assert(n - remaining >= 0);
502   // Total number of chars copied.
503   return n - remaining;
504 }
505 
506 // Utility method for xsgetn(): copy some chars to the putback area
xsgetn_preparePutbackArea(char * origGptr,char * dest,char * writePtr)507 std::size_t ZlibAbstractIStreambuf::xsgetn_preparePutbackArea(
508   char* origGptr, char* dest, char* writePtr)
509 {
510   // There are two buffers containing characters we potentially have to copy
511   // to the putback area: the one starting at _outBuf and the one starting at
512   // dest. In the following diagram, ***** represents those from _outBuf,
513   // which are right-aligned at origGptr[1], and the series of # represents
514   // those in the [dest, writePtr) address range:
515   //
516   // |_outBuf  |eback() *****|origGptr      |_outBuf + _outBufSize
517   //
518   // |dest #################|writePtr
519   //
520   // Together, these two memory blocks logically form a contiguous stream of
521   // chars we gave to the “client”: *****#################. All we have to do
522   // now is copy the appropriate amount of chars from this logical stream to
523   // the putback area, according to _putbackSize. These chars are the last
524   // ones in said stream (i.e., right portion of *****#################), but
525   // we have to copy them in order of increasing address to avoid possible
526   // overlapping problems in _outBuf. This is because some of the chars to
527   // copy may be located before _outBuf + _putbackSize (i.e., already be in
528   // the putback area).
529   //
530   //   [1] This means that the last char represented by a star is at address
531   //       origGptr-1.
532 
533   // It seems std::ptrdiff_t is the signed counterpart of std::size_t,
534   // therefore this should always hold (even with equality).
535   static_assert(sizeof(std::size_t) >= sizeof(std::ptrdiff_t),
536                 "Unexpected: sizeof(std::size_t) < sizeof(std::ptrdiff_t)");
537   assert(writePtr - dest >= 0);
538   std::size_t inDestBuffer = static_cast<std::size_t>(writePtr - dest);
539 
540   assert(origGptr - eback() >= 0);
541   std::size_t nbPutbackChars = std::min(
542     static_cast<std::size_t>(origGptr - eback()) + inDestBuffer,
543     _putbackSize);
544   std::size_t nbPutbackCharsToGo = nbPutbackChars;
545 
546   // Are there chars in _outBuf that need to be copied to the putback area?
547   if (nbPutbackChars > inDestBuffer) {
548     std::size_t chunkSize = nbPutbackChars - inDestBuffer; // yes, this number
549     std::copy(origGptr - chunkSize, origGptr,
550               _outBuf + _putbackSize - nbPutbackChars);
551     nbPutbackCharsToGo -= chunkSize;
552   }
553 
554   // Finally, copy those that are not in _outBuf
555   std::copy(writePtr - nbPutbackCharsToGo, writePtr,
556             _outBuf + _putbackSize - nbPutbackCharsToGo);
557 
558   return nbPutbackChars;
559 }
560 
561 // ***************************************************************************
562 // *                     ZlibCompressorIStreambuf class                      *
563 // ***************************************************************************
564 
ZlibCompressorIStreambuf(std::istream & iStream,const SGPath & path,int compressionLevel,ZLibCompressionFormat format,ZLibMemoryStrategy memStrategy,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)565 ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
566   std::istream& iStream,
567   const SGPath& path,
568   int compressionLevel,
569   ZLibCompressionFormat format,
570   ZLibMemoryStrategy memStrategy,
571   char* inBuf,
572   std::size_t inBufSize,
573   char *outBuf,
574   std::size_t outBufSize,
575   std::size_t putbackSize)
576   : ZlibAbstractIStreambuf(iStream, path, inBuf, inBufSize, outBuf, outBufSize,
577                            putbackSize)
578 {
579   zStreamInit(compressionLevel, format, memStrategy);
580 }
581 
ZlibCompressorIStreambuf(std::unique_ptr<std::istream> iStream_p,const SGPath & path,int compressionLevel,ZLibCompressionFormat format,ZLibMemoryStrategy memStrategy,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)582 ZlibCompressorIStreambuf::ZlibCompressorIStreambuf(
583   std::unique_ptr<std::istream> iStream_p,
584   const SGPath& path,
585   int compressionLevel,
586   ZLibCompressionFormat format,
587   ZLibMemoryStrategy memStrategy,
588   char* inBuf,
589   std::size_t inBufSize,
590   char *outBuf,
591   std::size_t outBufSize,
592   std::size_t putbackSize)
593   : ZlibCompressorIStreambuf(*iStream_p, path, compressionLevel, format,
594                              memStrategy, inBuf, inBufSize, outBuf, outBufSize,
595                              putbackSize)
596 {
597   _iStream_p = std::move(iStream_p); // take ownership of the object
598 }
599 
~ZlibCompressorIStreambuf()600 ZlibCompressorIStreambuf::~ZlibCompressorIStreambuf()
601 {
602   int retCode = deflateEnd(&_zstream); // deallocate the z_stream struct
603   if (retCode != Z_OK) {
604     // In C++11, we can't throw exceptions from a destructor.
605     SG_LOG(SG_IO, SG_ALERT, "ZlibCompressorIStreambuf: " <<
606            ::zlibErrorMessage(_zstream, retCode));
607   }
608 }
609 
610 ZlibAbstractIStreambuf::OperationType
operationType() const611 ZlibCompressorIStreambuf::operationType() const
612 {
613   return OperationType::COMPRESSION;
614 }
615 
zStreamInit(int compressionLevel,ZLibCompressionFormat format,ZLibMemoryStrategy memStrategy)616 void ZlibCompressorIStreambuf::zStreamInit(int compressionLevel,
617                                           ZLibCompressionFormat format,
618                                           ZLibMemoryStrategy memStrategy)
619 {
620   int windowBits, memLevel;
621 
622   // Intentionally not listing ZLibCompressionFormat::AUTODETECT here (it is
623   // only for decompression!)
624   switch (format) {
625   case ZLibCompressionFormat::ZLIB:
626     windowBits = 15;
627     break;
628   case ZLibCompressionFormat::GZIP:
629     windowBits = 31;
630     break;
631   default:
632     throw std::logic_error("Unexpected compression format: " +
633                            std::to_string(enumValue(format)));
634   }
635 
636   switch (memStrategy) {
637   case ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED:
638     memLevel = 8;
639     break;
640   case ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY:
641     memLevel = 9;
642     break;
643   default:
644     throw std::logic_error("Unexpected memory strategy: " +
645                            std::to_string(enumValue(memStrategy)));
646   }
647 
648   _zstream.zalloc = Z_NULL;     // No custom memory allocation routines
649   _zstream.zfree = Z_NULL;      // Ditto. Therefore, the 'opaque' field won't
650   _zstream.opaque = Z_NULL;     // be used, actually.
651 
652   int retCode = deflateInit2(&_zstream, compressionLevel, Z_DEFLATED,
653                              windowBits, memLevel, Z_DEFAULT_STRATEGY);
654   if (retCode != Z_OK) {
655     throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
656                           sg_location(_path));
657   }
658 }
659 
zlibProcessData()660 int ZlibCompressorIStreambuf::zlibProcessData()
661 {
662   // Compress as much data as possible given _zstream.avail_in and
663   // _zstream.avail_out. The input data starts at _zstream.next_in, the output
664   // at _zstream.next_out, and these four fields are all updated by this call
665   // to reflect exactly the amount of data zlib has consumed and produced.
666   return deflate(&_zstream, _zstream.avail_in ? Z_NO_FLUSH : Z_FINISH);
667 }
668 
669 // ***************************************************************************
670 // *                    ZlibDecompressorIStreambuf class                     *
671 // ***************************************************************************
672 
ZlibDecompressorIStreambuf(std::istream & iStream,const SGPath & path,ZLibCompressionFormat format,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)673 ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
674   std::istream& iStream,
675   const SGPath& path,
676   ZLibCompressionFormat format,
677   char* inBuf,
678   std::size_t inBufSize,
679   char *outBuf,
680   std::size_t outBufSize,
681   std::size_t putbackSize)
682   : ZlibAbstractIStreambuf(iStream, path, inBuf, inBufSize, outBuf, outBufSize,
683                            putbackSize)
684 {
685   zStreamInit(format);
686 }
687 
ZlibDecompressorIStreambuf(std::unique_ptr<std::istream> iStream_p,const SGPath & path,ZLibCompressionFormat format,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)688 ZlibDecompressorIStreambuf::ZlibDecompressorIStreambuf(
689   std::unique_ptr<std::istream> iStream_p,
690   const SGPath& path,
691   ZLibCompressionFormat format,
692   char* inBuf,
693   std::size_t inBufSize,
694   char *outBuf,
695   std::size_t outBufSize,
696   std::size_t putbackSize)
697   : ZlibDecompressorIStreambuf(*iStream_p, path, format, inBuf, inBufSize,
698                                outBuf, outBufSize, putbackSize)
699 {
700   _iStream_p = std::move(iStream_p); // take ownership of the object
701 }
702 
~ZlibDecompressorIStreambuf()703 ZlibDecompressorIStreambuf::~ZlibDecompressorIStreambuf()
704 {
705   int retCode = inflateEnd(&_zstream); // deallocate the z_stream struct
706   if (retCode != Z_OK) {
707     // In C++11, we can't throw exceptions from a destructor.
708     SG_LOG(SG_IO, SG_ALERT, "ZlibDecompressorIStreambuf: " <<
709            ::zlibErrorMessage(_zstream, retCode));
710   }
711 }
712 
713 ZlibAbstractIStreambuf::OperationType
operationType() const714 ZlibDecompressorIStreambuf::operationType() const
715 {
716   return OperationType::DECOMPRESSION;
717 }
718 
zStreamInit(ZLibCompressionFormat format)719 void ZlibDecompressorIStreambuf::zStreamInit(ZLibCompressionFormat format)
720 {
721   int windowBits;
722 
723   switch (format) {
724   case ZLibCompressionFormat::ZLIB:
725     windowBits = 15;
726     break;
727   case ZLibCompressionFormat::GZIP:
728     windowBits = 31;
729     break;
730   case ZLibCompressionFormat::AUTODETECT:
731     windowBits = 47;            // 47 = 32 + 15
732     break;
733   default:
734     throw std::logic_error("Unexpected compression format: " +
735                            std::to_string(enumValue(format)));
736   }
737 
738   _zstream.zalloc = Z_NULL;     // No custom memory allocation routines
739   _zstream.zfree = Z_NULL;      // Ditto. Therefore, the 'opaque' field won't
740   _zstream.opaque = Z_NULL;     // be used, actually.
741 
742   int retCode = inflateInit2(&_zstream, windowBits);
743   if (retCode != Z_OK) {
744     throw sg_io_exception(::zlibErrorMessage(_zstream, retCode),
745                           sg_location(_path));
746   }
747 }
748 
zlibProcessData()749 int ZlibDecompressorIStreambuf::zlibProcessData()
750 {
751   // Decompress as much data as possible given _zstream.avail_in and
752   // _zstream.avail_out. The input data starts at _zstream.next_in, the output
753   // at _zstream.next_out, and these four fields are all updated by this call
754   // to reflect exactly the amount of data zlib has consumed and produced.
755   return inflate(&_zstream, _zstream.avail_in ? Z_NO_FLUSH : Z_FINISH);
756 }
757 
758 // ***************************************************************************
759 // *                       ZlibCompressorIStream class                       *
760 // ***************************************************************************
761 
ZlibCompressorIStream(std::istream & iStream,const SGPath & path,int compressionLevel,ZLibCompressionFormat format,ZLibMemoryStrategy memStrategy,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)762 ZlibCompressorIStream::ZlibCompressorIStream(std::istream& iStream,
763                                              const SGPath& path,
764                                              int compressionLevel,
765                                              ZLibCompressionFormat format,
766                                              ZLibMemoryStrategy memStrategy,
767                                              char* inBuf,
768                                              std::size_t inBufSize,
769                                              char *outBuf,
770                                              std::size_t outBufSize,
771                                              std::size_t putbackSize)
772   : std::istream(nullptr),
773     _streamBuf(iStream, path, compressionLevel, format, memStrategy, inBuf,
774                inBufSize, outBuf, outBufSize, putbackSize)
775 {
776   // Associate _streamBuf to 'this' and clear the error state flags
777   rdbuf(&_streamBuf);
778 }
779 
ZlibCompressorIStream(std::unique_ptr<std::istream> iStream_p,const SGPath & path,int compressionLevel,ZLibCompressionFormat format,ZLibMemoryStrategy memStrategy,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)780 ZlibCompressorIStream::ZlibCompressorIStream(
781   std::unique_ptr<std::istream> iStream_p,
782   const SGPath& path,
783   int compressionLevel,
784   ZLibCompressionFormat format,
785   ZLibMemoryStrategy memStrategy,
786   char* inBuf,
787   std::size_t inBufSize,
788   char *outBuf,
789   std::size_t outBufSize,
790   std::size_t putbackSize)
791   : std::istream(nullptr),
792     _streamBuf(std::move(iStream_p), path, compressionLevel, format,
793                memStrategy, inBuf, inBufSize, outBuf, outBufSize, putbackSize)
794 {
795   // Associate _streamBuf to 'this' and clear the error state flags
796   rdbuf(&_streamBuf);
797 }
798 
~ZlibCompressorIStream()799 ZlibCompressorIStream::~ZlibCompressorIStream()
800 { }
801 
802 // ***************************************************************************
803 // *                      ZlibDecompressorIStream class                      *
804 // ***************************************************************************
805 
ZlibDecompressorIStream(std::istream & iStream,const SGPath & path,ZLibCompressionFormat format,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)806 ZlibDecompressorIStream::ZlibDecompressorIStream(std::istream& iStream,
807                                                  const SGPath& path,
808                                                  ZLibCompressionFormat format,
809                                                  char* inBuf,
810                                                  std::size_t inBufSize,
811                                                  char *outBuf,
812                                                  std::size_t outBufSize,
813                                                  std::size_t putbackSize)
814   : std::istream(nullptr),
815     _streamBuf(iStream, path, format, inBuf, inBufSize, outBuf, outBufSize,
816                putbackSize)
817 {
818   // Associate _streamBuf to 'this' and clear the error state flags
819   rdbuf(&_streamBuf);
820 }
821 
ZlibDecompressorIStream(std::unique_ptr<std::istream> iStream_p,const SGPath & path,ZLibCompressionFormat format,char * inBuf,std::size_t inBufSize,char * outBuf,std::size_t outBufSize,std::size_t putbackSize)822 ZlibDecompressorIStream::ZlibDecompressorIStream(
823   std::unique_ptr<std::istream> iStream_p,
824   const SGPath& path,
825   ZLibCompressionFormat format,
826   char* inBuf,
827   std::size_t inBufSize,
828   char *outBuf,
829   std::size_t outBufSize,
830   std::size_t putbackSize)
831   : std::istream(nullptr),
832     _streamBuf(std::move(iStream_p), path, format, inBuf, inBufSize,
833                outBuf, outBufSize, putbackSize)
834 {
835   // Associate _streamBuf to 'this' and clear the error state flags
836   rdbuf(&_streamBuf);
837 }
838 
~ZlibDecompressorIStream()839 ZlibDecompressorIStream::~ZlibDecompressorIStream()
840 { }
841 
842 } // of namespace simgear
843