Change as per the FlightGear Policy Document: http://www.flightgear.org/flightgear-policy-document/
478 lines
21 KiB
C++
478 lines
21 KiB
C++
// -*- coding: utf-8 -*-
|
|
//
|
|
// zlibstream.hxx --- IOStreams classes for working with RFC 1950 and RFC 1952
|
|
// compression formats (respectively known as the zlib and
|
|
// gzip formats)
|
|
//
|
|
// Copyright (C) 2017 Florent Rougon
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Library General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Library General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Library General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
// MA 02110-1301 USA.
|
|
|
|
#ifndef SG_ZLIBSTREAM_HXX
|
|
#define SG_ZLIBSTREAM_HXX
|
|
|
|
#include <ios> // std::streamsize
|
|
#include <istream>
|
|
#include <streambuf>
|
|
#include <memory> // std::unique_ptr
|
|
#include <zlib.h> // struct z_stream
|
|
|
|
#include <simgear/misc/sg_path.hxx>
|
|
|
|
// This file contains:
|
|
//
|
|
// - two stream buffer classes (ZlibCompressorIStreambuf and
|
|
// ZlibDecompressorIStreambuf), both based on the same abstract class:
|
|
// ZlibAbstractIStreambuf;
|
|
//
|
|
// - two std::istream subclasses (ZlibCompressorIStream and
|
|
// ZlibDecompressorIStream), each creating and using the corresponding
|
|
// stream buffer class from the previous item.
|
|
//
|
|
// All these allow one to work with RFC 1950 and RFC 1952 compression
|
|
// formats, respectively known as the zlib and gzip formats.
|
|
//
|
|
// These classes are *input* streaming classes, which means they can
|
|
// efficiently handle arbitrary amounts of data without using any disk
|
|
// space nor increasing amounts of memory, and allow “client code” to pull
|
|
// exactly as much data as it wants at any given time, resuming later
|
|
// when it is ready to handle the next chunk.
|
|
//
|
|
// So, for example, assuming you've created an instance of
|
|
// ZlibCompressorIStream (bound to some input stream of your choice, let's
|
|
// call it iStream), you could read 512 bytes of data from it, and you
|
|
// would get the first 512 bytes of *compressed* data corresponding to what
|
|
// iStream provided. Then you could resume at any time and ask for the next
|
|
// 512 bytes of compressed data (or any other amount), etc.
|
|
//
|
|
// Therefore, these classes are well suited, among others, to compress or
|
|
// decompress data streams while at the same time packing the result into
|
|
// discrete chunks or packets with size constraints (you can think of the
|
|
// process as making sausages :).
|
|
//
|
|
// The input being in each case an std::istream (for compressing as well as
|
|
// for decompressing), it can be tied to an arbitrary source: a file with
|
|
// sg_ifstream or std::ifstream, a memory buffer with std::istringstream or
|
|
// std::stringstream, a TCP socket with a custom std::streambuf subclass[1]
|
|
// to interface with the sockets API, etc.
|
|
//
|
|
// [1] Possibly wrapped in an std::istream.
|
|
//
|
|
// The stream buffer classes upon which ZlibCompressorIStream and
|
|
// ZlibDecompressorIStream are built have an xsgetn() implementation that
|
|
// avoids useless copies of data by asking zlib to write directly to the
|
|
// destination buffer. This xsgetn() method is used when calling read() on
|
|
// the std::istream subclasses, or sgetn() if you are using the stream
|
|
// buffer classes directly (i.e., ZlibCompressorIStreambuf and
|
|
// ZlibDecompressorIStreambuf). Other std::istream methods may instead rely
|
|
// only on the internal buffer and the underflow() method, and therefore be
|
|
// less efficient for large amounts of data. You may want to take a look at
|
|
// zlibstream_test.cxx to see various ways of using these classes.
|
|
//
|
|
// In case you use std::istream& operator>>(std::istream&, std::string&) or
|
|
// its overload friends, beware that it splits fields at spaces, and by
|
|
// default ignores spaces at the beginning of a field (cf. std::skipws,
|
|
// std::noskipws and friends). As far as I understand it, most of these
|
|
// operators are mainly intended in the IOStreams library to be used to
|
|
// read an int here, a double there, a space-delimited string afterwards,
|
|
// etc. (the exception could be the overload writing to a stream buffer,
|
|
// however it doesn't seem to be very efficient on my system with GNU
|
|
// libstdc++ [it is not using xsgetn()], so beware also of this one if you
|
|
// are handling large amounts of data). For moderately complex or large
|
|
// input handling, I'd suggest to use std::istream methods such as read(),
|
|
// gcount() and getline() (std::getline() can be useful too). Or directly
|
|
// use the stream buffer classes, in particular with sgetn().
|
|
|
|
|
|
namespace simgear
|
|
{
|
|
|
|
enum class ZLibCompressionFormat {
|
|
ZLIB = 0,
|
|
GZIP,
|
|
AUTODETECT
|
|
};
|
|
|
|
enum class ZLibMemoryStrategy {
|
|
FAVOR_MEMORY_OVER_SPEED = 0,
|
|
FAVOR_SPEED_OVER_MEMORY
|
|
};
|
|
|
|
// Abstract base class for both the compressor and decompressor stream buffers.
|
|
class ZlibAbstractIStreambuf: public std::streambuf
|
|
{
|
|
public:
|
|
/**
|
|
* @brief Constructor for ZlibAbstractIStreambuf.
|
|
* @param iStream Input stream to read from.
|
|
* @param path Optional path to the file corresponding to iStream,
|
|
* if any. Only used for error messages.
|
|
* @param inBuf Pointer to the input buffer (data read from iStream is
|
|
* written there before being compressed or decompressed).
|
|
* If nullptr, the buffer is allocated on the heap in the
|
|
* constructor and deallocated in the destructor.
|
|
* @param inBufSize Size of the input buffer, in chars.
|
|
* @param outBuf Pointer to the output buffer. Data is read by zlib
|
|
* from the input buffer, compressed or decompressed, and
|
|
* the result is directly written to the output buffer.
|
|
* If nullptr, the buffer is allocated on the heap in the
|
|
* constructor and deallocated in the destructor.
|
|
* @param outBufSize Size of the output buffer, in chars.
|
|
* @param putbackSize Size of the putback area inside the output buffer, in
|
|
* chars.
|
|
*
|
|
* It is required that putbackSize < outBufSize. It is guaranteed that,
|
|
* if at least putbackSize chars have been read without any putback (or
|
|
* unget) operation intermixed, then at least putbackSize chars can be
|
|
* put back in sequence. If you don't need this feature, use zero for the
|
|
* putbackSize value (the default) for best performance.
|
|
*/
|
|
explicit ZlibAbstractIStreambuf(std::istream& iStream,
|
|
const SGPath& path = SGPath(),
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0);
|
|
|
|
// Alternate constructor with sink semantics for the “source” std::istream.
|
|
// When used, the class takes ownership of the std::istream instance pointed
|
|
// to by the first constructor argument, and keeps it alive as long as the
|
|
// object this constructor is for is itself alive.
|
|
explicit ZlibAbstractIStreambuf(std::unique_ptr<std::istream> iStream_p,
|
|
const SGPath& path = SGPath(),
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0);
|
|
|
|
ZlibAbstractIStreambuf(const ZlibAbstractIStreambuf&) = delete;
|
|
ZlibAbstractIStreambuf& operator=(const ZlibAbstractIStreambuf&) = delete;
|
|
virtual ~ZlibAbstractIStreambuf();
|
|
|
|
protected:
|
|
enum class OperationType {
|
|
COMPRESSION = 0,
|
|
DECOMPRESSION
|
|
};
|
|
|
|
virtual OperationType operationType() const = 0;
|
|
|
|
// Either compress or decompress a chunk of data (depending on the
|
|
// particular subclass implementation). The semantics are the same as for
|
|
// zlib's inflate() and deflate() functions applied to member _zstream,
|
|
// concerning 1) the return value and 2) where, and how much to read and
|
|
// write (which thus depends on _zstream.avail_in, _zstream.next_in,
|
|
// _zstream.avail_out and _zstream.next_out).
|
|
virtual int zlibProcessData() = 0;
|
|
|
|
// The input stream, from which data is read before being processed by zlib
|
|
std::istream& _iStream;
|
|
// Pointer to the same, used when calling the constructor that takes an
|
|
// std::unique_ptr<std::istream> as its first argument; empty
|
|
// std::unique_ptr object otherwise.
|
|
std::unique_ptr<std::istream> _iStream_p;
|
|
|
|
// Corresponding path, if any (default-constructed SGPath instance otherwise)
|
|
const SGPath _path;
|
|
// Structure used to communicate with zlib
|
|
z_stream _zstream;
|
|
|
|
private:
|
|
// Callback whose role is to refill the output buffer when it's empty and
|
|
// the “client” tries to read more.
|
|
virtual int underflow() override;
|
|
// Optional override when subclassing std::streambuf. This is the most
|
|
// efficient way of reading several characters (as soon as we've emptied the
|
|
// output buffer, data is written by zlib directly to the destination
|
|
// buffer).
|
|
virtual std::streamsize xsgetn(char* dest, std::streamsize n) override;
|
|
// Utility method for xsgetn()
|
|
std::size_t xsgetn_preparePutbackArea(char* origGptr, char* dest,
|
|
char* writePtr);
|
|
// Make sure there is data to read in the input buffer, or signal EOF.
|
|
bool getInputData();
|
|
// Utility method for fillOutputBuffer()
|
|
std::size_t fOB_remainingSpace(unsigned char* nextOutPtr) const;
|
|
// Fill the output buffer (using zlib functions) as much as possible.
|
|
char* fillOutputBuffer();
|
|
// Utility method
|
|
[[ noreturn ]] void handleZ_BUF_ERROR() const;
|
|
|
|
bool _allFinished = false;
|
|
|
|
// The buffers
|
|
// ~~~~~~~~~~~
|
|
//
|
|
// The input buffer receives data obtained from _iStream, before it is
|
|
// processed by zlib. In underflow(), zlib reads from this buffer it and
|
|
// writes the resulting data(*) to the output buffer. Then we point the
|
|
// standard std::streambuf pointers (gptr() and friends) directly towards
|
|
// the data inside that output buffer. xsgetn() is even more optimized: it
|
|
// first empties the output buffer, then makes zlib write the remaining data
|
|
// directly to the destination area.
|
|
//
|
|
// (*) Compressed or decompressed, depending on the particular
|
|
// implementation of zlibProcessData() in each subclass.
|
|
char* _inBuf;
|
|
const std::size_t _inBufSize;
|
|
// _inBufEndPtr points right after the last data retrieved from _iStream and
|
|
// stored into _inBuf. When zlib has read all such data, _zstream.next_in is
|
|
// equal to _inBufEndPtr (after proper casting). Except in this particular
|
|
// situation, only _zstream.next_in <= _inBufEndPtr is guaranteed.
|
|
char* _inBufEndPtr;
|
|
// Layout of the _outBuf buffer:
|
|
//
|
|
// |_outBuf <putback area> |_outBuf + _putbackSize |_outBuf + _outBufSize
|
|
//
|
|
// The first _putbackSize chars in _outBuf are reserved for the putback area
|
|
// (right-aligned at _outBuf + _putbackSize). The actual output buffer thus
|
|
// starts at _outBuf + _putbackSize. At any given time for callers of this
|
|
// class, the number of characters that can be put back is gptr() - eback().
|
|
// It may be lower than _putbackSize if we haven't read that many characters
|
|
// yet. It may also be larger if gptr() > _outBuf + _putbackSize, i.e.,
|
|
// when the buffer for pending data is non-empty.
|
|
//
|
|
// At any given time, callers should see:
|
|
//
|
|
// _outBuf <= eback() <= _outBuf + _putbackSize <= gptr() <= egptr()
|
|
// <= _outBuf + _outBufSize
|
|
//
|
|
// (hoping this won't get out of sync with the code!)
|
|
char *_outBuf;
|
|
const std::size_t _outBufSize;
|
|
// Space reserved for characters to be put back into the stream. Must be
|
|
// strictly smaller than _outBufSize (this is checked in the constructor).
|
|
// It is guaranteed that this number of chars can be put back, except of
|
|
// course if we haven't read that many characters from the input stream yet.
|
|
// If characters are buffered in _outBuf[2], then it may be that more
|
|
// characters than _putbackSize can be put back (it is essentially a matter
|
|
// for std::streambuf of decreasing the “next pointer for the input
|
|
// sequence”, i.e., the one returned by gptr()).
|
|
//
|
|
// [2] In the [_outBuf + _putbackSize, _outBuf + _outBufSize) area.
|
|
const std::size_t _putbackSize;
|
|
|
|
// Since the constructor optionally allocates memory for the input and
|
|
// output buffers, these members allow the destructor to know which buffers
|
|
// have to be deallocated, if any.
|
|
bool _inBufMustBeFreed = false;
|
|
bool _outBufMustBeFreed = false;
|
|
};
|
|
|
|
|
|
// Stream buffer class for compressing data. Input data is obtained from an
|
|
// std::istream instance; the corresponding compressed data can be read using
|
|
// the standard std::streambuf read interface (mainly: sbumpc(), sgetc(),
|
|
// snextc(), sgetn(), sputbackc(), sungetc()). Input, uncompressed data is
|
|
// “pulled” as needed for the amount of compressed data requested by the
|
|
// “client” using the methods I just listed.
|
|
class ZlibCompressorIStreambuf: public ZlibAbstractIStreambuf
|
|
{
|
|
public:
|
|
// Same parameters as for ZlibAbstractIStreambuf, except:
|
|
//
|
|
// compressionLevel: in the [0,9] range. 0 means no compression at all.
|
|
// Levels 1 to 9 yield compressed data, with 1 giving
|
|
// the highest compression speed but worst compression
|
|
// ratio, and 9 the highest compression ratio but lowest
|
|
// compression speed.
|
|
// format either ZLibCompressionFormat::ZLIB or
|
|
// ZLibCompressionFormat::GZIP
|
|
// memStrategy either ZLibMemoryStrategy::FAVOR_MEMORY_OVER_SPEED or
|
|
// ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY
|
|
explicit ZlibCompressorIStreambuf(
|
|
std::istream& iStream,
|
|
const SGPath& path = SGPath(),
|
|
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0);
|
|
|
|
// Alternate constructor with sink semantics for the “source” std::istream.
|
|
explicit ZlibCompressorIStreambuf(
|
|
std::unique_ptr<std::istream> _iStream_p,
|
|
const SGPath& path = SGPath(),
|
|
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0);
|
|
|
|
ZlibCompressorIStreambuf(const ZlibCompressorIStreambuf&) = delete;
|
|
ZlibCompressorIStreambuf& operator=(const ZlibCompressorIStreambuf&) = delete;
|
|
virtual ~ZlibCompressorIStreambuf();
|
|
|
|
protected:
|
|
virtual OperationType operationType() const override;
|
|
// Initialize the z_stream struct used by zlib
|
|
void zStreamInit(int compressionLevel, ZLibCompressionFormat format,
|
|
ZLibMemoryStrategy memStrategy);
|
|
// Call zlib's deflate() function to compress data.
|
|
virtual int zlibProcessData() override;
|
|
};
|
|
|
|
|
|
// Stream buffer class for decompressing data. Input data is obtained from an
|
|
// std::istream instance; the corresponding decompressed data can be read
|
|
// using the standard std::streambuf read interface (mainly: sbumpc(),
|
|
// sgetc(), snextc(), sgetn(), sputbackc(), sungetc()). Input, compressed data
|
|
// is “pulled” as needed for the amount of uncompressed data requested by the
|
|
// “client” using the methods I just listed.
|
|
class ZlibDecompressorIStreambuf: public ZlibAbstractIStreambuf
|
|
{
|
|
public:
|
|
// Same parameters as for ZlibAbstractIStreambuf, except:
|
|
//
|
|
// format ZLibCompressionFormat::ZLIB,
|
|
// ZLibCompressionFormat::GZIP or
|
|
// ZLibCompressionFormat::AUTODETECT
|
|
explicit ZlibDecompressorIStreambuf(
|
|
std::istream& iStream,
|
|
const SGPath& path = SGPath(),
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
// Alternate constructor with sink semantics for the “source” std::istream.
|
|
explicit ZlibDecompressorIStreambuf(
|
|
std::unique_ptr<std::istream> _iStream_p,
|
|
const SGPath& path = SGPath(),
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
ZlibDecompressorIStreambuf(const ZlibDecompressorIStreambuf&) = delete;
|
|
ZlibDecompressorIStreambuf& operator=(const ZlibDecompressorIStreambuf&)
|
|
= delete;
|
|
virtual ~ZlibDecompressorIStreambuf();
|
|
|
|
protected:
|
|
virtual OperationType operationType() const override;
|
|
void zStreamInit(ZLibCompressionFormat format);
|
|
virtual int zlibProcessData() override;
|
|
};
|
|
|
|
// std::istream subclass for compressing data. Input data is obtained from an
|
|
// std::istream instance; the corresponding compressed data can be read using
|
|
// the standard std::istream interface (read(), readsome(), gcount(), get(),
|
|
// getline(), operator>>(), peek(), putback(), ignore(), unget()... plus
|
|
// operator overloads such as istream& operator>>(istream&, string&) as
|
|
// defined in <string>, and std::getline()). Input, uncompressed data is
|
|
// “pulled” as needed for the amount of compressed data requested by the
|
|
// “client”.
|
|
//
|
|
// To get data efficiently from an instance of this class, use its read()
|
|
// method (typically in conjunction with gcount(), inside a loop).
|
|
class ZlibCompressorIStream: public std::istream
|
|
{
|
|
public:
|
|
// Same parameters as for ZlibCompressorIStreambuf
|
|
explicit ZlibCompressorIStream(
|
|
std::istream& iStream,
|
|
const SGPath& path = SGPath(),
|
|
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
// Alternate constructor with sink semantics for the “source” std::istream.
|
|
explicit ZlibCompressorIStream(
|
|
std::unique_ptr<std::istream> _iStream_p,
|
|
const SGPath& path = SGPath(),
|
|
int compressionLevel = Z_DEFAULT_COMPRESSION,
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
ZLibMemoryStrategy memStrategy = ZLibMemoryStrategy::FAVOR_SPEED_OVER_MEMORY,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
ZlibCompressorIStream(const ZlibCompressorIStream&) = delete;
|
|
ZlibCompressorIStream& operator=(const ZlibCompressorIStream&) = delete;
|
|
virtual ~ZlibCompressorIStream();
|
|
|
|
private:
|
|
ZlibCompressorIStreambuf _streamBuf;
|
|
};
|
|
|
|
// std::istream subclass for decompressing data. Input data is obtained from
|
|
// an std::istream instance; the corresponding decompressed data can be read
|
|
// using the standard std::istream interface (read(), readsome(), gcount(),
|
|
// get(), getline(), operator>>(), peek(), putback(), ignore(), unget()...
|
|
// plus operator overloads such as istream& operator>>(istream&, string&) as
|
|
// defined in <string>, and std::getline()). Input, compressed data is
|
|
// “pulled” as needed for the amount of uncompressed data requested by the
|
|
// “client”.
|
|
//
|
|
// To get data efficiently from an instance of this class, use its read()
|
|
// method (typically in conjunction with gcount(), inside a loop).
|
|
class ZlibDecompressorIStream: public std::istream
|
|
{
|
|
public:
|
|
// Same parameters as for ZlibDecompressorIStreambuf
|
|
explicit ZlibDecompressorIStream(
|
|
std::istream& iStream,
|
|
const SGPath& path = SGPath(),
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
// Alternate constructor with sink semantics for the “source” std::istream.
|
|
explicit ZlibDecompressorIStream(
|
|
std::unique_ptr<std::istream> _iStream_p,
|
|
const SGPath& path = SGPath(),
|
|
ZLibCompressionFormat format = ZLibCompressionFormat::ZLIB,
|
|
char* inBuf = nullptr,
|
|
std::size_t inBufSize = 262144,
|
|
char* outBuf = nullptr,
|
|
std::size_t outBufSize = 262144,
|
|
std::size_t putbackSize = 0); // default optimized for speed
|
|
|
|
ZlibDecompressorIStream(const ZlibDecompressorIStream&) = delete;
|
|
ZlibDecompressorIStream& operator=(const ZlibDecompressorIStream&) = delete;
|
|
virtual ~ZlibDecompressorIStream();
|
|
|
|
private:
|
|
ZlibDecompressorIStreambuf _streamBuf;
|
|
};
|
|
|
|
} // of namespace simgear
|
|
|
|
#endif // of SG_ZLIBSTREAM_HXX
|