]> git.ipfire.org Git - thirdparty/zlib-ng.git/commitdiff
Add inflateBack test for safe mode bailout MATCH state handler
authorNathan Moinvaziri <nathan@nathanm.com>
Tue, 10 Mar 2026 21:28:50 +0000 (14:28 -0700)
committerHans Kristian Rosbach <hk-github@circlestorm.org>
Fri, 1 May 2026 12:33:40 +0000 (14:33 +0200)
test/CMakeLists.txt
test/test_inflate_back.cc [new file with mode: 0644]

index 6550a6c2c1cf12e8df3ea2f347469d7ddb48447a..6509cd59be75c4ba7c916a41c58428dc92dabdf9 100644 (file)
@@ -167,6 +167,7 @@ if(WITH_GTEST)
             test_deflate_tune.cc
             test_dict.cc
             test_inflate_adler32.cc
+            test_inflate_back.cc
             test_inflate_copy.cc
             test_raw.cc
             test_small_window.cc
diff --git a/test/test_inflate_back.cc b/test/test_inflate_back.cc
new file mode 100644 (file)
index 0000000..14169b8
--- /dev/null
@@ -0,0 +1,129 @@
+/* test_inflate_back.cc - Test inflateBack() with safe mode match bailout
+ * Verifies that inflateBack() correctly handles the MATCH state when
+ * inflate_fast() bails out due to insufficient output space.
+ */
+
+#include "zbuild.h"
+#ifdef ZLIB_COMPAT
+#  include "zlib.h"
+#else
+#  include "zlib-ng.h"
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+/* Callback to provide all input at once */
+static z_uint32_t pull_all(void *desc, z_const unsigned char **buf) {
+    PREFIX3(stream) *strm = (PREFIX3(stream) *)desc;
+    if (strm->avail_in == 0)
+        return 0;
+    *buf = strm->next_in;
+    unsigned avail = strm->avail_in;
+    strm->avail_in = 0;
+    return avail;
+}
+
+struct output_state {
+    uint8_t *buf;
+    size_t size;
+    size_t pos;
+};
+
+/* Callback to collect all output */
+static z_int32_t push_all(void *desc, unsigned char *buf, z_uint32_t len) {
+    struct output_state *out = (struct output_state *)desc;
+    if (out->pos + len > out->size)
+        return 1;  /* overflow */
+    memcpy(out->buf + out->pos, buf, len);
+    out->pos += len;
+    return 0;
+}
+
+class inflate_back : public ::testing::Test {
+protected:
+    static const int wbits = 9;
+    static const uint32_t wsize = 1U << wbits;
+    static const uint32_t data_size = wsize * 64;
+
+    uint8_t *window = nullptr;
+    uint8_t *decompressed = nullptr;
+    uint8_t *original = nullptr;
+    uint8_t *compressed = nullptr;
+
+    void SetUp() override {
+        window = (uint8_t *)malloc(wsize);
+        decompressed = (uint8_t *)malloc(data_size);
+        original = (uint8_t *)malloc(data_size);
+        ASSERT_NE(window, nullptr);
+        ASSERT_NE(decompressed, nullptr);
+        ASSERT_NE(original, nullptr);
+
+        /* Generate data that triggers MATCH bailout */
+        /* 5-byte repeating pattern -> len=258 dist=5 tokens */
+        for (uint32_t i = 0; i < data_size; i++)
+            original[i] = (uint8_t)(i % 5);
+    }
+
+    void TearDown() override {
+        free(compressed);
+        free(original);
+        free(decompressed);
+        free(window);
+    }
+};
+
+/* Test that inflateBack() handles the MATCH state when inflate_fast() bails
+ * out due to insufficient output space. */
+TEST_F(inflate_back, match_state) {
+    int err;
+
+    /* Compress with raw deflate (no zlib/gzip header) */
+    PREFIX3(stream) c_strm;
+    memset(&c_strm, 0, sizeof(c_strm));
+    err = PREFIX(deflateInit2)(&c_strm, Z_BEST_COMPRESSION, Z_DEFLATED,
+                               -wbits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+    ASSERT_EQ(err, Z_OK);
+
+    z_uintmax_t comp_bound = PREFIX(deflateBound)(&c_strm, data_size);
+    compressed = (uint8_t *)malloc(comp_bound);
+    ASSERT_NE(compressed, nullptr);
+
+    c_strm.next_in = original;
+    c_strm.avail_in = data_size;
+    c_strm.next_out = compressed;
+    c_strm.avail_out = (uint32_t)comp_bound;
+
+    err = PREFIX(deflate)(&c_strm, Z_FINISH);
+    EXPECT_EQ(err, Z_STREAM_END);
+    uint32_t compressed_size = (uint32_t)c_strm.total_out;
+    PREFIX(deflateEnd)(&c_strm);
+
+    /* Decompress with inflateBack() */
+    PREFIX3(stream) d_strm;
+    memset(&d_strm, 0, sizeof(d_strm));
+
+    err = PREFIX(inflateBackInit)(&d_strm, wbits, window);
+    ASSERT_EQ(err, Z_OK);
+
+    d_strm.next_in = compressed;
+    d_strm.avail_in = compressed_size;
+
+    struct output_state out_state;
+    out_state.buf = decompressed;
+    out_state.size = data_size;
+    out_state.pos = 0;
+
+    err = PREFIX(inflateBack)(&d_strm, pull_all, &d_strm, push_all, &out_state);
+    EXPECT_EQ(err, Z_STREAM_END);
+
+    PREFIX(inflateBackEnd)(&d_strm);
+
+    /* Verify decompressed output matches original */
+    EXPECT_EQ(out_state.pos, (size_t)data_size);
+    EXPECT_EQ(memcmp(decompressed, original, data_size), 0);
+}