//
-// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/beast/zlib/inflate_stream.hpp>
#include <boost/beast/core/string.hpp>
-#include <boost/beast/unit_test/suite.hpp>
+#include <boost/beast/_experimental/unit_test/suite.hpp>
#include <chrono>
#include <random>
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<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; }
+
+ 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)];
+ 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
};
void
- testInflate()
+ testInflate(IDecompressor& d)
{
{
Matrix m{*this};
m.strategy(Z_DEFAULT_STRATEGY);
m(Beast{full, once, Flush::block}, check);
}
+ {
+ // Check a known string - this provides more stable coverage,
+ // independent of the RNG-generated strings.
+ Matrix m{*this};
+ auto const check =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. "
+ "Ultricies mi eget mauris pharetra et ultrices neque ornare. Eget est "
+ "lorem ipsum dolor. Dui faucibus in ornare quam viverra orci "
+ "sagittis. Lorem mollis aliquam ut porttitor. Pretium quam vulputate "
+ "dignissim suspendisse in est ante in. Tempus egestas sed sed risus "
+ "pretium quam vulputate dignissim. Pellentesque dignissim enim sit "
+ "amet venenatis urna. Eleifend quam adipiscing vitae proin sagittis "
+ "nisl rhoncus. Aliquam etiam erat velit scelerisque in. Accumsan in "
+ "nisl nisi scelerisque eu ultrices vitae auctor eu.";
+ m.level(6);
+ m.window(9);
+ m.strategy(Z_DEFAULT_STRATEGY);
+ m(Beast{full, full}, check);
+ }
// VFALCO Fails, but I'm unsure of what the correct
// behavior of Z_TREES/Flush::trees is.
m(Beast{full, once, Flush::trees}, check);
}
#endif
+
+ 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,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, {});
+ }
+
+ std::string check(IDecompressor& d,
+ std::initializer_list<std::uint8_t> const& in,
+ error_code expected,
+ std::size_t window_size = 15,
+ std::size_t len = -1)
+ {
+ std::string out(1024, 0);
+ z_params zs;
+ inflate_stream is;
+ is.reset(static_cast<int>(window_size));
+ boost::system::error_code ec;
+
+ zs.next_in = &*in.begin();
+ zs.next_out = &out[0];
+ zs.avail_in = std::min(in.size(), len);
+ zs.avail_out = out.size();
+
+ while (zs.avail_in > 0 && !ec)
+ {
+ is.write(zs, Flush::sync, ec);
+ auto n = std::min(zs.avail_in, len);
+ zs.next_in = static_cast<char const*>(zs.next_in) + n;
+ zs.avail_in -= n;
+ }
+
+ BEAST_EXPECT(ec == expected);
+ return out;
+ }
+
+ void testInflateErrors(IDecompressor& d)
+ {
+ check(d, {0x00, 0x00, 0x00, 0x00, 0x00},
+ error::invalid_stored_length);
+ check(d, {0x03, 0x00},
+ error::end_of_stream);
+ check(d, {0x06},
+ error::invalid_block_type);
+ check(d, {0xfc, 0x00, 0x00},
+ error::too_many_symbols);
+ check(d, {0x04, 0x00, 0xfe, 0xff},
+ error::incomplete_length_set);
+ check(d, {0x04, 0x00, 0x24, 0x49, 0x00},
+ error::invalid_bit_length_repeat);
+ check(d, {0x04, 0x00, 0x24, 0xe9, 0xff, 0xff},
+ error::invalid_bit_length_repeat);
+ check(d, {0x04, 0x00, 0x24, 0xe9, 0xff, 0x6d},
+ error::missing_eob);
+ check(d, {0x04, 0x80, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24,
+ 0x71, 0xff, 0xff, 0x93, 0x11, 0x00},
+ error::over_subscribed_length);
+ check(d, {0x04, 0x80, 0x49, 0x92, 0x24, 0x0f, 0xb4, 0xff,
+ 0xff, 0xc3, 0x84},
+ error::incomplete_length_set);
+ check(d, {0x04, 0xc0, 0x81, 0x08, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x7f, 0xeb, 0x0b, 0x00, 0x00},
+ error::invalid_literal_length);
+ check(d, {0x02, 0x7e, 0xff, 0xff},
+ error::invalid_distance_code);
+ check(d, {0x0c, 0xc0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x90, 0xff, 0x6b, 0x04, 0x00},
+ error::invalid_distance);
+ 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(d, {0xed, 0xc0, 0x01, 0x01, 0x00, 0x00, 0x00, 0x40,
+ 0x20, 0xff, 0x57, 0x1b, 0x42, 0x2c, 0x4f},
+ error::end_of_stream);
+ check(d, {0x02, 0x08, 0x20, 0x80, 0x00, 0x03, 0x00},
+ error::end_of_stream);
+ check(d, {0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x78, 0x9c, 0xff},
+ error::invalid_stored_length);
+ }
+
+ void testInvalidSettings(IDecompressor& d)
+ {
+ except<std::domain_error>(
+ [&]()
+ {
+ d.init(7);
+ });
+ }
+
+ void testFixedHuffmanFlushTrees(IDecompressor& d)
+ {
+ std::string out(5, 0);
+ d.init();
+ boost::system::error_code ec;
+ std::initializer_list<std::uint8_t> in = {
+ 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00, 0x00,
+ 0x00, 0xff, 0xff};
+ 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);
+ ec = d.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ BEAST_EXPECT(d.avail_out() == 0);
+ BEAST_EXPECT(out == "Hello");
+ }
+
+ void testUncompressedFlushTrees(IDecompressor& d)
+ {
+ std::string out(5, 0);
+ d.init();
+ boost::system::error_code ec;
+ std::initializer_list<std::uint8_t> in = {
+ 0x00, 0x05, 0x00, 0xfa, 0xff, 0x48, 0x65, 0x6c,
+ 0x6c, 0x6f, 0x00, 0x00};
+ 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);
+ ec = d.write(Flush::sync);
+ BEAST_EXPECT(!ec);
+ BEAST_EXPECT(d.avail_out() == 0);
+ BEAST_EXPECT(out == "Hello");
}
void
log <<
"sizeof(inflate_stream) == " <<
sizeof(inflate_stream) << std::endl;
- testInflate();
+ 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);
}
};