Imported Upstream version 1.72.0
[platform/upstream/boost.git] / libs / beast / test / beast / zlib / deflate_stream.cpp
index 5d8e2b6..0129ce1 100644 (file)
@@ -12,7 +12,9 @@
 
 #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"
@@ -23,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<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
@@ -110,6 +244,8 @@ public:
         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();
@@ -147,8 +283,9 @@ public:
 
     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
@@ -167,29 +304,26 @@ public:
 
     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;
@@ -202,7 +336,7 @@ public:
         }
 
     fin:
-        out.resize(zs.total_out);
+        out.resize(c.total_out());
         BEAST_EXPECT(decompress(out) == check);
 
     err:
@@ -213,57 +347,52 @@ public:
 
     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:
@@ -275,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)
         {
@@ -286,20 +415,193 @@ public:
                     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
@@ -309,7 +611,20 @@ public:
             "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);
     }
 };