From d895906bcc7daf1678e2cc98dd9fe03efccf7b62 Mon Sep 17 00:00:00 2001 From: AeroStun <24841307+AeroStun@users.noreply.github.com> Date: Wed, 23 Oct 2019 13:09:52 +0200 Subject: [PATCH] Refactor zlib tests and fix typo: close #1744 * Fixed typo in enum name * Added missing zlib error enumerator * Marked caveat in zlib impl to avoid future confusion * Created Compressor/Decompressor abstractions in Beast.zlib tests * Applied abstractions on Beast.zlib tests * Workaround Travis-CI' 10min silence timeout * Add test for Beast.zlib's need_dict error * Avoid breaking user-code dependant on deprecated enumerator interface Signed-off-by: AeroStun <24841307+AeroStun@users.noreply.github.com> --- CHANGELOG.md | 1 + .../beast/zlib/detail/deflate_stream.ipp | 2 +- include/boost/beast/zlib/error.hpp | 16 +- include/boost/beast/zlib/impl/error.ipp | 3 +- test/beast/zlib/deflate_stream.cpp | 384 +++++++++++------- test/beast/zlib/error.cpp | 3 +- test/beast/zlib/inflate_stream.cpp | 228 ++++++++--- 7 files changed, 443 insertions(+), 194 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f5b34cd..d2f1a44e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Version 275: * Support Concepts for completion token params * https_get example sends the Host header * Fix async_close error code when async_read times out +* Refactor zlib tests and fix enum typo -------------------------------------------------------------------------------- diff --git a/include/boost/beast/zlib/detail/deflate_stream.ipp b/include/boost/beast/zlib/detail/deflate_stream.ipp index 77b8895f..e33814fa 100644 --- a/include/boost/beast/zlib/detail/deflate_stream.ipp +++ b/include/boost/beast/zlib/detail/deflate_stream.ipp @@ -399,7 +399,7 @@ doWrite(z_params& zs, boost::optional flush, error_code& ec) } } else if(zs.avail_in == 0 && ( - old_flush && flush <= *old_flush + old_flush && flush <= *old_flush // Caution: depends on enum order ) && flush != Flush::finish) { /* Make sure there is something to do and avoid duplicate consecutive diff --git a/include/boost/beast/zlib/error.hpp b/include/boost/beast/zlib/error.hpp index 2c7d3915..940ed466 100644 --- a/include/boost/beast/zlib/error.hpp +++ b/include/boost/beast/zlib/error.hpp @@ -66,6 +66,17 @@ enum class error */ end_of_stream, + /** Preset dictionary required + + This error indicates that a preset dictionary was not provided and is now + needed at this point. + + This does not always indicate a failure condition. + + @note This is the same as `Z_NEED_DICT` returned by ZLib. + */ + need_dict, + /** Invalid stream or parameters. This error is returned when invalid parameters are passed, @@ -95,7 +106,10 @@ enum class error too_many_symbols, /// Invalid code lengths - invalid_code_lenths, + invalid_code_lengths, +#ifndef BOOST_BEAST_NO_DEPRECATED + invalid_code_lenths = invalid_code_lengths, +#endif /// Invalid bit length repeat invalid_bit_length_repeat, diff --git a/include/boost/beast/zlib/impl/error.ipp b/include/boost/beast/zlib/impl/error.ipp index 75310bfc..f6b02316 100644 --- a/include/boost/beast/zlib/impl/error.ipp +++ b/include/boost/beast/zlib/impl/error.ipp @@ -62,12 +62,13 @@ public: { case error::need_buffers: return "need buffers"; case error::end_of_stream: return "unexpected end of deflate stream"; + case error::need_dict: return "need dict"; case error::stream_error: return "stream error"; case error::invalid_block_type: return "invalid block type"; case error::invalid_stored_length: return "invalid stored block length"; case error::too_many_symbols: return "too many symbols"; - case error::invalid_code_lenths: return "invalid code lengths"; + case error::invalid_code_lengths: return "invalid code lengths"; case error::invalid_bit_length_repeat: return "invalid bit length repeat"; case error::missing_eob: return "missing end of block code"; case error::invalid_literal_length: return "invalid literal/length code"; diff --git a/test/beast/zlib/deflate_stream.cpp b/test/beast/zlib/deflate_stream.cpp index ce738d03..0129ce11 100644 --- a/test/beast/zlib/deflate_stream.cpp +++ b/test/beast/zlib/deflate_stream.cpp @@ -25,6 +25,138 @@ namespace zlib { 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(static_cast(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(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(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 @@ -151,6 +283,7 @@ public: using self = deflate_stream_test; typedef void(self::*pmf_t)( + ICompressor& c, int level, int windowBits, int memLevel, int strategy, std::string const&); @@ -171,29 +304,26 @@ public: void doDeflate1_beast( + 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, memLevel, - toStrategy(strategy)); - out.resize(ds.upper_bound( - static_cast(check.size()))); - zs.next_in = (Bytef*)check.data(); - zs.avail_in = static_cast(check.size()); - zs.next_out = (Bytef*)out.data(); - zs.avail_out = static_cast(out.size()); + 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; @@ -206,7 +336,7 @@ public: } fin: - out.resize(zs.total_out); + out.resize(c.total_out()); BEAST_EXPECT(decompress(out) == check); err: @@ -217,6 +347,7 @@ public: void doDeflate2_beast( + ICompressor& c, int level, int windowBits, int memLevel, int strategy, std::string const& check) { @@ -224,50 +355,44 @@ public: { for(std::size_t j = 1;; ++j) { - z_params zs; - deflate_stream ds; - ds.reset( + c.init( level, windowBits, memLevel, - toStrategy(strategy)); + strategy); std::string out; - out.resize(ds.upper_bound( - static_cast(check.size()))); + out.resize(c.bound(check.size())); if(j >= out.size()) break; - zs.next_in = (Bytef*)check.data(); - zs.avail_in = static_cast(i); - zs.next_out = (Bytef*)out.data(); - zs.avail_out = static_cast(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(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(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: @@ -279,7 +404,7 @@ public: //-------------------------------------------------------------------------- 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) { @@ -293,127 +418,115 @@ public: for (int memLevel = 8; memLevel <= 9; ++memLevel) { (this->*pmf)( - level, windowBits, memLevel, strategy, check); + c, level, windowBits, memLevel, strategy, check); } } } } // Check default settings - (this->*pmf)(compression::default_size, 15, 8, 0, check); + (this->*pmf)(c, compression::default_size, 15, 8, 0, check); } void - testDeflate() + testDeflate(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); + 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() + void testInvalidSettings(ICompressor& c) { except( - []() + [&]() { - deflate_stream ds; - ds.reset(-42, 15, 8, Strategy::normal); + c.init(-42, 15, 8, static_cast(Strategy::normal)); }); except( - []() + [&]() { - deflate_stream ds; - ds.reset(compression::default_size, -1, 8, Strategy::normal); + c.init(compression::default_size, -1, 8, static_cast(Strategy::normal)); }); except( - []() + [&]() { - deflate_stream ds; - ds.reset(compression::default_size, 15, -1, Strategy::normal); + c.init(compression::default_size, 15, -1, static_cast(Strategy::normal)); }); except( - []() + [&]() { - deflate_stream ds; - ds.reset(); - z_params zp{}; - zp.avail_in = 1; - zp.next_in = nullptr; - error_code ec; - ds.write(zp, Flush::full, ec); + c.init(); + c.avail_in(1); + c.next_in(nullptr); + c.write(Flush::full); }); } void - testWriteAfterFinish() + testWriteAfterFinish(ICompressor& c) { - z_params zp; - deflate_stream ds; - ds.reset(); + c.init(); std::string out; out.resize(1024); string_view s = "Hello"; - zp.next_in = s.data(); - zp.avail_in = s.size(); - zp.next_out = &out.front(); - zp.avail_out = out.size(); - error_code ec; - ds.write(zp, Flush::sync, ec); + 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); - zp.next_in = nullptr; - zp.avail_in = 0; - ds.write(zp, Flush::finish, ec); + c.next_in(nullptr); + c.avail_in(0); + ec = c.write(Flush::finish); BEAST_EXPECT(ec == error::end_of_stream); - zp.next_in = s.data(); - zp.avail_in = s.size(); - zp.next_out = &out.front(); - zp.avail_out = out.size(); - ds.write(zp, Flush::sync, ec); + 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); - ds.write(zp, Flush::finish, ec); + ec = c.write(Flush::finish); BEAST_EXPECT(ec == error::need_buffers); } void - testFlushPartial() + testFlushPartial(ICompressor& c) { - z_params zp; - deflate_stream ds; - ds.reset(); + c.init(); std::string out; out.resize(1024); string_view s = "Hello"; - zp.next_in = s.data(); - zp.avail_in = s.size(); - zp.next_out = &out.front(); - zp.avail_out = out.size(); + c.next_in(s.data()); + c.avail_in(s.size()); + c.next_out(&out.front()); + c.avail_out(out.size()); error_code ec; - ds.write(zp, Flush::none, ec); + ec = c.write(Flush::none); BEAST_EXPECT(!ec); - ds.write(zp, Flush::partial, ec); + ec = c.write(Flush::partial); BEAST_EXPECT(!ec); } void - testFlushAtLiteralBufferFull() + testFlushAtLiteralBufferFull(ICompressor& c) { struct fixture { - explicit fixture(std::size_t n, Strategy s) + ICompressor& c; + explicit fixture(ICompressor&c, std::size_t n, Strategy s) : c(c) { - ds.reset(8, 15, 1, s); - std::iota(in.begin(), in.end(), - static_cast(0)); + c.init(8, 15, 1, static_cast(s)); + std::iota(in.begin(), in.end(), std::uint8_t{0}); out.resize(n); - zp.next_in = in.data(); - zp.avail_in = in.size(); - zp.next_out = &out.front(); - zp.avail_out = out.size(); + c.next_in(in.data()); + c.avail_in(in.size()); + c.next_out(&out.front()); + c.avail_out(out.size()); } - z_params zp; - deflate_stream ds; std::array in; std::string out; }; @@ -421,25 +534,22 @@ public: for (auto s : {Strategy::huffman, Strategy::rle, Strategy::normal}) { { - fixture f{264, s}; - error_code ec; - f.ds.write(f.zp, Flush::finish, ec); + fixture f{c, 264, s}; + error_code ec = c.write(Flush::finish); BEAST_EXPECT(ec == error::end_of_stream); - BEAST_EXPECT(f.zp.avail_out == 1); + BEAST_EXPECT(c.avail_out() == 1); } { - fixture f{263, s}; - error_code ec; - f.ds.write(f.zp, Flush::finish, ec); + fixture f{c,263, s}; + error_code ec = c.write(Flush::finish); BEAST_EXPECT(!ec); - BEAST_EXPECT(f.zp.avail_out == 0); + BEAST_EXPECT(c.avail_out() == 0); } { - fixture f{20, s}; - error_code ec; - f.ds.write(f.zp, Flush::sync, ec); + fixture f{c, 20, s}; + error_code ec = c.write(Flush::sync); BEAST_EXPECT(!ec); } @@ -447,35 +557,30 @@ public: } void - testRLEMatchLengthExceedLookahead() + testRLEMatchLengthExceedLookahead(ICompressor& c) { - z_params zp; - deflate_stream ds; std::vector in; in.resize(300); - - ds.reset(8, 15, 1, Strategy::rle); + c.init(8, 15, 1, static_cast(Strategy::rle)); std::fill_n(in.begin(), 4, 'a'); std::string out; out.resize(in.size() * 2); - zp.next_in = in.data(); - zp.avail_in = in.size(); - zp.next_out = &out.front(); - zp.avail_out = 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; - ds.write(zp, Flush::sync, ec); + ec = c.write(Flush::sync); BEAST_EXPECT(!ec); } void - testFlushAfterDistMatch() + testFlushAfterDistMatch(ICompressor& c) { for (auto out_size : {144, 129}) { - z_params zp; - deflate_stream ds; std::array in{}; // 125 will mostly fill the lit buffer, so emitting a distance code // results in a flush. @@ -485,16 +590,16 @@ public: std::iota(in.begin() + n, in.end(), static_cast(0)); - ds.reset(8, 15, 1, Strategy::normal); + c.init(8, 15, 1, static_cast(Strategy::normal)); std::string out; out.resize(out_size); - zp.next_in = in.data(); - zp.avail_in = in.size(); - zp.next_out = &out.front(); - zp.avail_out = 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; - ds.write(zp, Flush::sync, ec); + ec = c.write(Flush::sync); BEAST_EXPECT(!ec); } } @@ -506,13 +611,20 @@ public: "sizeof(deflate_stream) == " << sizeof(deflate_stream) << std::endl; - testDeflate(); - testInvalidSettings(); - testWriteAfterFinish(); - testFlushPartial(); - testFlushAtLiteralBufferFull(); - testRLEMatchLengthExceedLookahead(); - testFlushAfterDistMatch(); + 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); } }; diff --git a/test/beast/zlib/error.cpp b/test/beast/zlib/error.cpp index 8d290551..f9096e6e 100644 --- a/test/beast/zlib/error.cpp +++ b/test/beast/zlib/error.cpp @@ -41,12 +41,13 @@ public: { check("boost.beast.zlib", error::need_buffers); check("boost.beast.zlib", error::end_of_stream); + check("boost.beast.zlib", error::need_dict); check("boost.beast.zlib", error::stream_error); check("boost.beast.zlib", error::invalid_block_type); check("boost.beast.zlib", error::invalid_stored_length); check("boost.beast.zlib", error::too_many_symbols); - check("boost.beast.zlib", error::invalid_code_lenths); + check("boost.beast.zlib", error::invalid_code_lengths); check("boost.beast.zlib", error::invalid_bit_length_repeat); check("boost.beast.zlib", error::missing_eob); check("boost.beast.zlib", error::invalid_literal_length); diff --git a/test/beast/zlib/inflate_stream.cpp b/test/beast/zlib/inflate_stream.cpp index 5152440e..66f53c92 100644 --- a/test/beast/zlib/inflate_stream.cpp +++ b/test/beast/zlib/inflate_stream.cpp @@ -23,6 +23,127 @@ namespace zlib { class inflate_stream_test : public beast::unit_test::suite { + struct IDecompressor { + virtual void init() = 0; + virtual void init(int windowBits) = 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 error_code write(Flush) = 0; + virtual ~IDecompressor() = default; + }; + class ZlibDecompressor : public IDecompressor { + z_stream zs; + + public: + ZlibDecompressor() = default; + void init(int windowBits) override + { + inflateEnd(&zs); + zs = {}; + const auto res = inflateInit2(&zs, windowBits); + switch(res){ + case Z_OK: + break; + case Z_MEM_ERROR: + BOOST_THROW_EXCEPTION(std::runtime_error{"zlib decompressor: no memory"}); + case Z_STREAM_ERROR: + BOOST_THROW_EXCEPTION(std::domain_error{"zlib decompressor: bad arg"}); + } + } + void init() override { + inflateEnd(&zs); + zs = {}; + const auto res = inflateInit2(&zs, -15); + switch(res){ + case Z_OK: + break; + case Z_MEM_ERROR: + BOOST_THROW_EXCEPTION(std::runtime_error{"zlib decompressor: no memory"}); + case Z_STREAM_ERROR: + BOOST_THROW_EXCEPTION(std::domain_error{"zlib decompressor: 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(static_cast(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; } + + 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(flush)]; + const auto res = inflate(&zs, zlib_flush); + switch(res){ + case Z_OK: + return {}; + case Z_STREAM_END: + return error::end_of_stream; + case Z_NEED_DICT: + return error::need_dict; + case Z_DATA_ERROR: + case Z_STREAM_ERROR: + return error::stream_error; + case Z_MEM_ERROR: + BOOST_THROW_EXCEPTION(std::bad_alloc{}); + case Z_BUF_ERROR: + return error::need_buffers; + default: + BOOST_THROW_EXCEPTION(std::runtime_error{"zlib decompressor: impossible value"}); + } + } + + ~ZlibDecompressor() override { + inflateEnd(&zs); + } + } zlib_decompressor{}; + class BeastCompressor : public IDecompressor { + z_params zp; + inflate_stream is; + + public: + BeastCompressor() = default; + + void init(int windowBits) override + { + zp = {}; + is.clear(); + is.reset(windowBits); + } + void init() override { + zp = {}; + is.clear(); + is.reset(); + } + + 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; } + + error_code write(Flush flush) override { + error_code ec{}; + is.write(zp, flush, ec); + return ec; + } + + ~BeastCompressor() override = default; + } beast_decompressor{}; public: // Lots of repeats, limited char range static @@ -283,7 +404,7 @@ public: }; void - testInflate() + testInflate(IDecompressor& d) { { Matrix m{*this}; @@ -381,8 +502,8 @@ public: } #endif - check({0x63, 0x18, 0x05, 0x40, 0x0c, 0x00}, {}, 8, 3); - check({0xed, 0xc0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, + check(d, {0x63, 0x18, 0x05, 0x40, 0x0c, 0x00}, {}, 8, 3); + check(d, {0xed, 0xc0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0xa0, 0xfd, 0xa9, 0x17, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -390,7 +511,7 @@ public: 0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, {}); } - std::string check( + std::string check(IDecompressor& d, std::initializer_list const& in, error_code expected, std::size_t window_size = 15, @@ -419,103 +540,97 @@ public: return out; } - void testInflateErrors() + void testInflateErrors(IDecompressor& d) { - check({0x00, 0x00, 0x00, 0x00, 0x00}, + check(d, {0x00, 0x00, 0x00, 0x00, 0x00}, error::invalid_stored_length); - check({0x03, 0x00}, + check(d, {0x03, 0x00}, error::end_of_stream); - check({0x06}, + check(d, {0x06}, error::invalid_block_type); - check({0xfc, 0x00, 0x00}, + check(d, {0xfc, 0x00, 0x00}, error::too_many_symbols); - check({0x04, 0x00, 0xfe, 0xff}, + check(d, {0x04, 0x00, 0xfe, 0xff}, error::incomplete_length_set); - check({0x04, 0x00, 0x24, 0x49, 0x00}, + check(d, {0x04, 0x00, 0x24, 0x49, 0x00}, error::invalid_bit_length_repeat); - check({0x04, 0x00, 0x24, 0xe9, 0xff, 0xff}, + check(d, {0x04, 0x00, 0x24, 0xe9, 0xff, 0xff}, error::invalid_bit_length_repeat); - check({0x04, 0x00, 0x24, 0xe9, 0xff, 0x6d}, + check(d, {0x04, 0x00, 0x24, 0xe9, 0xff, 0x6d}, error::missing_eob); - check({0x04, 0x80, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, + check(d, {0x04, 0x80, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0x71, 0xff, 0xff, 0x93, 0x11, 0x00}, error::over_subscribed_length); - check({0x04, 0x80, 0x49, 0x92, 0x24, 0x0f, 0xb4, 0xff, + check(d, {0x04, 0x80, 0x49, 0x92, 0x24, 0x0f, 0xb4, 0xff, 0xff, 0xc3, 0x84}, error::incomplete_length_set); - check({0x04, 0xc0, 0x81, 0x08, 0x00, 0x00, 0x00, 0x00, + check(d, {0x04, 0xc0, 0x81, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x7f, 0xeb, 0x0b, 0x00, 0x00}, error::invalid_literal_length); - check({0x02, 0x7e, 0xff, 0xff}, + check(d, {0x02, 0x7e, 0xff, 0xff}, error::invalid_distance_code); - check({0x0c, 0xc0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, + check(d, {0x0c, 0xc0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xff, 0x6b, 0x04, 0x00}, error::invalid_distance); - check({0x05,0xe0, 0x81, 0x91, 0x24, 0xcb, 0xb2, 0x2c, + check(d, {0x05,0xe0, 0x81, 0x91, 0x24, 0xcb, 0xb2, 0x2c, 0x49, 0xe2, 0x0f, 0x2e, 0x8b, 0x9a, 0x47, 0x56, 0x9f, 0xfb, 0xfe, 0xec, 0xd2, 0xff, 0x1f}, error::end_of_stream); - check({0xed, 0xc0, 0x01, 0x01, 0x00, 0x00, 0x00, 0x40, + check(d, {0xed, 0xc0, 0x01, 0x01, 0x00, 0x00, 0x00, 0x40, 0x20, 0xff, 0x57, 0x1b, 0x42, 0x2c, 0x4f}, error::end_of_stream); - check({0x02, 0x08, 0x20, 0x80, 0x00, 0x03, 0x00}, + check(d, {0x02, 0x08, 0x20, 0x80, 0x00, 0x03, 0x00}, error::end_of_stream); - // TODO: Excess data (from golang test inflate suite), should this be an error? - check({0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, 0x9c, 0xff}, + check(d, {0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, 0x9c, 0xff}, error::invalid_stored_length); } - void testInvalidSettings() + void testInvalidSettings(IDecompressor& d) { except( - []() + [&]() { - inflate_stream is; - is.reset(7); + d.init(7); }); } - void testFixedHuffmanFlushTrees() + void testFixedHuffmanFlushTrees(IDecompressor& d) { std::string out(5, 0); - z_params zs; - inflate_stream is; - is.reset(); + d.init(); boost::system::error_code ec; std::initializer_list in = { 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x00, 0x00, 0xff, 0xff}; - zs.next_in = &*in.begin(); - zs.next_out = &out[0]; - zs.avail_in = in.size(); - zs.avail_out = out.size(); - is.write(zs, Flush::trees, ec); + d.next_in(&*in.begin()); + d.next_out(&out[0]); + d.avail_in(in.size()); + d.avail_out(out.size()); + ec = d.write(Flush::trees); BEAST_EXPECT(!ec); - is.write(zs, Flush::sync, ec); + ec = d.write(Flush::sync); BEAST_EXPECT(!ec); - BEAST_EXPECT(zs.avail_out == 0); + BEAST_EXPECT(d.avail_out() == 0); BEAST_EXPECT(out == "Hello"); } - void testUncompressedFlushTrees() + void testUncompressedFlushTrees(IDecompressor& d) { std::string out(5, 0); - z_params zs; - inflate_stream is; - is.reset(); + d.init(); boost::system::error_code ec; std::initializer_list in = { 0x00, 0x05, 0x00, 0xfa, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00}; - zs.next_in = &*in.begin(); - zs.next_out = &out[0]; - zs.avail_in = in.size(); - zs.avail_out = out.size(); - is.write(zs, Flush::trees, ec); + d.next_in(&*in.begin()); + d.next_out(&out[0]); + d.avail_in(in.size()); + d.avail_out(out.size()); + ec = d.write(Flush::trees); BEAST_EXPECT(!ec); - is.write(zs, Flush::sync, ec); + ec = d.write(Flush::sync); BEAST_EXPECT(!ec); - BEAST_EXPECT(zs.avail_out == 0); + BEAST_EXPECT(d.avail_out() == 0); BEAST_EXPECT(out == "Hello"); } @@ -525,11 +640,16 @@ public: log << "sizeof(inflate_stream) == " << sizeof(inflate_stream) << std::endl; - testInflate(); - testInflateErrors(); - testInvalidSettings(); - testFixedHuffmanFlushTrees(); - testUncompressedFlushTrees(); + testInflate(zlib_decompressor); + testInflate(beast_decompressor); + testInflateErrors(zlib_decompressor); + testInflateErrors(beast_decompressor); + testInvalidSettings(zlib_decompressor); + testInvalidSettings(beast_decompressor); + testFixedHuffmanFlushTrees(zlib_decompressor); + testFixedHuffmanFlushTrees(beast_decompressor); + testUncompressedFlushTrees(zlib_decompressor); + testUncompressedFlushTrees(beast_decompressor); } };