#include <boost/beast/core/string.hpp>
#include <boost/beast/_experimental/unit_test/suite.hpp>
+#include <array>
#include <cstdint>
+#include <numeric>
#include <random>
#include "zlib-1.2.11/zlib.h"
class deflate_stream_test : public beast::unit_test::suite
{
+ struct ICompressor {
+ virtual void init() = 0;
+ virtual void init(
+ int level,
+ int windowBits,
+ int memLevel,
+ int strategy) = 0;
+
+ virtual std::size_t avail_in() const noexcept = 0;
+ virtual void avail_in(std::size_t) noexcept = 0;
+ virtual void const* next_in() const noexcept = 0;
+ virtual void next_in(const void*) noexcept = 0;
+ virtual std::size_t avail_out() const noexcept = 0;
+ virtual void avail_out(std::size_t) noexcept = 0;
+ virtual void* next_out() const noexcept = 0;
+ virtual void next_out(void*) noexcept = 0;
+ virtual std::size_t total_out() const noexcept = 0;
+
+ virtual std::size_t bound(std::size_t) = 0;
+ virtual error_code write(Flush) = 0;
+ virtual ~ICompressor() = default;
+ };
+ class ZlibCompressor : public ICompressor {
+ z_stream zs{};
+
+ public:
+ ZlibCompressor() = default;
+ void init() override {
+ deflateEnd(&zs);
+ zs = {};
+ const auto res = deflateInit2(&zs, -1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ if(res != Z_OK)
+ throw std::invalid_argument{"zlib compressor: failure"};
+ }
+ void init(
+ int level,
+ int windowBits,
+ int memLevel,
+ int strategy) override
+ {
+ deflateEnd(&zs);
+ zs = {};
+ const auto res = deflateInit2(&zs, level, Z_DEFLATED, -windowBits, memLevel, strategy);
+ if(res != Z_OK)
+ BOOST_THROW_EXCEPTION(std::invalid_argument{"zlib compressor: bad arg"});
+ }
+
+ virtual std::size_t avail_in() const noexcept override { return zs.avail_in; }
+ virtual void avail_in(std::size_t n) noexcept override { zs.avail_in = n; }
+ virtual void const* next_in() const noexcept override { return zs.next_in; }
+ virtual void next_in(const void* ptr) noexcept override { zs.next_in = const_cast<Bytef*>(static_cast<const Bytef*>(ptr)); }
+ virtual std::size_t avail_out() const noexcept override { return zs.avail_out; }
+ virtual void avail_out(std::size_t n_out) noexcept override { zs.avail_out = n_out; }
+ virtual void* next_out() const noexcept override { return zs.next_out; }
+ virtual void next_out(void* ptr) noexcept override { zs.next_out = (Bytef*)ptr; }
+ virtual std::size_t total_out() const noexcept override { return zs.total_out; }
+
+ std::size_t bound(std::size_t src_size) override {
+ return deflateBound(&zs, static_cast<uLong>(src_size));
+ }
+ error_code write(Flush flush) override {
+ constexpr static int zlib_flushes[] = {0, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH, Z_TREES};
+ const auto zlib_flush = zlib_flushes[static_cast<int>(flush)];
+ if(zs.next_in == nullptr && zs.avail_in != 0)
+ BOOST_THROW_EXCEPTION(std::invalid_argument{"zlib compressor: invalid input"});
+ const auto res = deflate(&zs, zlib_flush);
+ switch(res){
+ case Z_OK:
+ return {};
+ case Z_STREAM_END:
+ return error::end_of_stream;
+ case Z_STREAM_ERROR:
+ return error::stream_error;
+ case Z_BUF_ERROR:
+ return error::need_buffers;
+ default:
+ throw;
+ }
+ }
+
+ ~ZlibCompressor() override {
+ deflateEnd(&zs);
+ }
+ } zlib_compressor;
+ class BeastCompressor : public ICompressor {
+ z_params zp;
+ deflate_stream ds;
+
+ public:
+ BeastCompressor() = default;
+ void init() override {
+ zp = {};
+ ds.clear();
+ ds.reset();
+ }
+ void init(
+ int level,
+ int windowBits,
+ int memLevel,
+ int strategy) override
+ {
+ zp = {};
+ ds.clear();
+ ds.reset(
+ level,
+ windowBits,
+ memLevel,
+ toStrategy(strategy));
+ }
+
+ virtual std::size_t avail_in() const noexcept override { return zp.avail_in; }
+ virtual void avail_in(std::size_t n) noexcept override { zp.avail_in = n; }
+ virtual void const* next_in() const noexcept override { return zp.next_in; }
+ virtual void next_in(const void* ptr) noexcept override { zp.next_in = ptr; }
+ virtual std::size_t avail_out() const noexcept override { return zp.avail_out; }
+ virtual void avail_out(std::size_t n_out) noexcept override { zp.avail_out = n_out; }
+ virtual void* next_out() const noexcept override { return zp.next_out; }
+ virtual void next_out(void* ptr) noexcept override { zp.next_out = (Bytef*)ptr; }
+ virtual std::size_t total_out() const noexcept override { return zp.total_out; }
+
+ std::size_t bound(std::size_t src_size) override {
+ return ds.upper_bound(src_size);
+ }
+ error_code write(Flush flush) override {
+ error_code ec{};
+ ds.write(zp, flush, ec);
+ return ec;
+ }
+
+ ~BeastCompressor() override = default;
+ } beast_compressor;
+
public:
// Lots of repeats, limited char range
static
z_stream zs;
memset(&zs, 0, sizeof(zs));
result = inflateInit2(&zs, -15);
+ if(result != Z_OK)
+ throw std::logic_error{"inflateInit2 failed"};
try
{
zs.next_in = (Bytef*)in.data();
using self = deflate_stream_test;
typedef void(self::*pmf_t)(
- int level, int windowBits, int strategy,
- std::string const&);
+ ICompressor& c,
+ int level, int windowBits, int memLevel,
+ int strategy, std::string const&);
static
Strategy
void
doDeflate1_beast(
- int level, int windowBits, int strategy,
- std::string const& check)
+ ICompressor& c,
+ int level, int windowBits, int memLevel,
+ int strategy, std::string const& check)
{
std::string out;
- z_params zs;
- deflate_stream ds;
- ds.reset(
+ c.init(
level,
windowBits,
- 8,
- toStrategy(strategy));
- out.resize(ds.upper_bound(
- static_cast<uLong>(check.size())));
- zs.next_in = (Bytef*)check.data();
- zs.avail_in = static_cast<uInt>(check.size());
- zs.next_out = (Bytef*)out.data();
- zs.avail_out = static_cast<uInt>(out.size());
+ memLevel,
+ strategy);
+ out.resize(c.bound(check.size()));
+ c.next_in(check.data());
+ c.avail_in(check.size());
+ c.next_out((void*)out.data());
+ c.avail_out(out.size());
{
bool progress = true;
for(;;)
{
- error_code ec;
- ds.write(zs, Flush::full, ec);
+ error_code ec = c.write(Flush::full);
if( ec == error::need_buffers ||
ec == error::end_of_stream) // per zlib FAQ
goto fin;
}
fin:
- out.resize(zs.total_out);
+ out.resize(c.total_out());
BEAST_EXPECT(decompress(out) == check);
err:
void
doDeflate2_beast(
- int level, int windowBits, int strategy,
- std::string const& check)
+ ICompressor& c,
+ int level, int windowBits, int memLevel,
+ int strategy, std::string const& check)
{
for(std::size_t i = 1; i < check.size(); ++i)
{
for(std::size_t j = 1;; ++j)
{
- z_params zs;
- deflate_stream ds;
- ds.reset(
+ c.init(
level,
windowBits,
- 8,
- toStrategy(strategy));
+ memLevel,
+ strategy);
std::string out;
- out.resize(ds.upper_bound(
- static_cast<uLong>(check.size())));
+ out.resize(c.bound(check.size()));
if(j >= out.size())
break;
- zs.next_in = (Bytef*)check.data();
- zs.avail_in = static_cast<uInt>(i);
- zs.next_out = (Bytef*)out.data();
- zs.avail_out = static_cast<uInt>(j);
+ c.next_in((void*)check.data());
+ c.avail_in(i);
+ c.next_out((void*)out.data());
+ c.avail_out(j);
bool bi = false;
bool bo = false;
for(;;)
{
- error_code ec;
- ds.write(zs,
- bi ? Flush::full : Flush::none, ec);
+ error_code ec = c.write(
+ bi ? Flush::full : Flush::none);
if( ec == error::need_buffers ||
ec == error::end_of_stream) // per zlib FAQ
goto fin;
if(! BEAST_EXPECTS(! ec, ec.message()))
goto err;
- if(zs.avail_in == 0 && ! bi)
+ if(c.avail_in() == 0 && ! bi)
{
bi = true;
- zs.avail_in =
- static_cast<uInt>(check.size() - i);
+ c.avail_in(check.size() - i);
}
- if(zs.avail_out == 0 && ! bo)
+ if(c.avail_out() == 0 && ! bo)
{
bo = true;
- zs.avail_out =
- static_cast<uInt>(out.size() - j);
+ c.avail_out(out.size() - j);
}
}
fin:
- out.resize(zs.total_out);
+ out.resize(c.total_out());
BEAST_EXPECT(decompress(out) == check);
err:
//--------------------------------------------------------------------------
void
- doMatrix(std::string const& check, pmf_t pmf)
+ doMatrix(ICompressor& c, std::string const& check, pmf_t pmf)
{
for(int level = 0; level <= 9; ++level)
{
continue;
for(int strategy = 0; strategy <= 4; ++strategy)
{
- (this->*pmf)(
- level, windowBits, strategy, check);
+ for (int memLevel = 8; memLevel <= 9; ++memLevel)
+ {
+ (this->*pmf)(
+ c, level, windowBits, memLevel, strategy, check);
+ }
}
}
}
+
+ // Check default settings
+ (this->*pmf)(c, compression::default_size, 15, 8, 0, check);
+ }
+
+ void
+ testDeflate(ICompressor& c)
+ {
+ doMatrix(c, "Hello, world!", &self::doDeflate1_beast);
+ doMatrix(c, "Hello, world!", &self::doDeflate2_beast);
+ log << "no-silence keepalive" << std::endl;
+ doMatrix(c, corpus1(56), &self::doDeflate2_beast);
+ doMatrix(c, corpus1(1024), &self::doDeflate1_beast);
+ }
+
+ void testInvalidSettings(ICompressor& c)
+ {
+ except<std::invalid_argument>(
+ [&]()
+ {
+ c.init(-42, 15, 8, static_cast<int>(Strategy::normal));
+ });
+ except<std::invalid_argument>(
+ [&]()
+ {
+ c.init(compression::default_size, -1, 8, static_cast<int>(Strategy::normal));
+ });
+ except<std::invalid_argument>(
+ [&]()
+ {
+ c.init(compression::default_size, 15, -1, static_cast<int>(Strategy::normal));
+ });
+ except<std::invalid_argument>(
+ [&]()
+ {
+ c.init();
+ c.avail_in(1);
+ c.next_in(nullptr);
+ c.write(Flush::full);
+ });
+ }
+
+ void
+ testWriteAfterFinish(ICompressor& c)
+ {
+ c.init();
+ std::string out;
+ out.resize(1024);
+ string_view s = "Hello";
+ c.next_in(s.data());
+ c.avail_in(s.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+ error_code ec = c.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ c.next_in(nullptr);
+ c.avail_in(0);
+ ec = c.write(Flush::finish);
+ BEAST_EXPECT(ec == error::end_of_stream);
+ c.next_in(s.data());
+ c.avail_in(s.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+ ec = c.write(Flush::sync);
+ BEAST_EXPECT(ec == error::stream_error);
+ ec = c.write(Flush::finish);
+ BEAST_EXPECT(ec == error::need_buffers);
+ }
+
+ void
+ testFlushPartial(ICompressor& c)
+ {
+ c.init();
+ std::string out;
+ out.resize(1024);
+ string_view s = "Hello";
+ c.next_in(s.data());
+ c.avail_in(s.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+ error_code ec;
+ ec = c.write(Flush::none);
+ BEAST_EXPECT(!ec);
+ ec = c.write(Flush::partial);
+ BEAST_EXPECT(!ec);
+ }
+
+ void
+ testFlushAtLiteralBufferFull(ICompressor& c)
+ {
+ struct fixture
+ {
+ ICompressor& c;
+ explicit fixture(ICompressor&c, std::size_t n, Strategy s) : c(c)
+ {
+ c.init(8, 15, 1, static_cast<int>(s));
+ std::iota(in.begin(), in.end(), std::uint8_t{0});
+ out.resize(n);
+ c.next_in(in.data());
+ c.avail_in(in.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+ }
+
+ std::array<std::uint8_t, 255> in;
+ std::string out;
+ };
+
+ for (auto s : {Strategy::huffman, Strategy::rle, Strategy::normal})
+ {
+ {
+ fixture f{c, 264, s};
+ error_code ec = c.write(Flush::finish);
+ BEAST_EXPECT(ec == error::end_of_stream);
+ BEAST_EXPECT(c.avail_out() == 1);
+ }
+
+ {
+ fixture f{c,263, s};
+ error_code ec = c.write(Flush::finish);
+ BEAST_EXPECT(!ec);
+ BEAST_EXPECT(c.avail_out() == 0);
+ }
+
+ {
+ fixture f{c, 20, s};
+ error_code ec = c.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ }
+
+ }
}
void
- testDeflate()
+ testRLEMatchLengthExceedLookahead(ICompressor& c)
{
- doMatrix("Hello, world!", &self::doDeflate1_beast);
- doMatrix("Hello, world!", &self::doDeflate2_beast);
- doMatrix(corpus1(56), &self::doDeflate2_beast);
- doMatrix(corpus1(1024), &self::doDeflate1_beast);
+ std::vector<std::uint8_t> in;
+ in.resize(300);
+
+ c.init(8, 15, 1, static_cast<int>(Strategy::rle));
+ std::fill_n(in.begin(), 4, 'a');
+ std::string out;
+ out.resize(in.size() * 2);
+ c.next_in(in.data());
+ c.avail_in(in.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+
+ error_code ec;
+ ec = c.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ }
+
+ void
+ testFlushAfterDistMatch(ICompressor& c)
+ {
+ for (auto out_size : {144, 129})
+ {
+ std::array<std::uint8_t, 256> in{};
+ // 125 will mostly fill the lit buffer, so emitting a distance code
+ // results in a flush.
+ auto constexpr n = 125;
+ std::iota(in.begin(), in.begin() + n,
+ static_cast<std::uint8_t>(0));
+ std::iota(in.begin() + n, in.end(),
+ static_cast<std::uint8_t>(0));
+
+ c.init(8, 15, 1, static_cast<int>(Strategy::normal));
+ std::string out;
+ out.resize(out_size);
+ c.next_in(in.data());
+ c.avail_in(in.size());
+ c.next_out(&out.front());
+ c.avail_out(out.size());
+
+ error_code ec;
+ ec = c.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ }
}
void
"sizeof(deflate_stream) == " <<
sizeof(deflate_stream) << std::endl;
- testDeflate();
+ testDeflate(zlib_compressor);
+ testDeflate(beast_compressor);
+ testInvalidSettings(zlib_compressor);
+ testInvalidSettings(beast_compressor);
+ testWriteAfterFinish(zlib_compressor);
+ testWriteAfterFinish(beast_compressor);
+ testFlushPartial(zlib_compressor);
+ testFlushPartial(beast_compressor);
+ testFlushAtLiteralBufferFull(zlib_compressor);
+ testFlushAtLiteralBufferFull(beast_compressor);
+ testRLEMatchLengthExceedLookahead(zlib_compressor);
+ testRLEMatchLengthExceedLookahead(beast_compressor);
+ testFlushAfterDistMatch(zlib_compressor);
+ testFlushAfterDistMatch(beast_compressor);
}
};