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