]> git.ipfire.org Git - thirdparty/zlib-ng.git/commitdiff
Make deflate output deterministic if PREFIX3(stream) is reused after deflateReset
authorVladislav Shchapov <vladislav@shchapov.ru>
Tue, 13 Jan 2026 20:02:47 +0000 (01:02 +0500)
committerHans Kristian Rosbach <hk-github@circlestorm.org>
Tue, 20 Jan 2026 11:32:09 +0000 (12:32 +0100)
Co-authored-by: Marcin Kowalczyk <QrczakMK@gmail.com>
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
match_tpl.h
test/CMakeLists.txt
test/test_deflate_deterministic.cc [new file with mode: 0644]

index 62287fb8d19308246a203aede988124025e118f1..40db4bd0a1ed782b8df914e9a94a8719bff08403 100644 (file)
@@ -158,8 +158,10 @@ Z_INTERNAL uint32_t LONGEST_MATCH(deflate_state *const s, uint32_t cur_match) {
             uint32_t match_start = cur_match - match_offset;
             s->match_start = match_start;
 
-            /* Do not look for matches beyond the end of the input. */
-            if (len > lookahead)
+            /* Do not look for better matches if the current match reaches
+             * or exceeds the end of the input.
+             */
+            if (len >= lookahead)
                 return lookahead;
             if (len >= nice_match)
                 return len;
index a71949da26041ca32f3e7be0abb868034635fedd..37c649d65d4c36f4bd9e38722034f4061cfe8766 100644 (file)
@@ -178,15 +178,16 @@ if(WITH_GTEST)
 
         if(NOT TEST_STOCK_ZLIB)
             list(APPEND TEST_SRCS
-                test_adler32.cc             # adler32_neon(), etc
-                test_adler32_copy.cc        # adler32_copy implementations
-                test_compare256.cc          # compare256_neon(), etc
-                test_compare256_rle.cc      # compare256_rle(), etc
-                test_crc32.cc               # crc32_armv8(), etc
-                test_crc32_copy.cc          # crc32_copy implementations
-                test_inflate_sync.cc        # expects a certain compressed block layout
-                test_main.cc                # cpu_check_features()
-                test_version.cc             # expects a fixed version string
+                test_adler32.cc               # adler32_neon(), etc
+                test_adler32_copy.cc          # adler32_copy implementations
+                test_compare256.cc            # compare256_neon(), etc
+                test_compare256_rle.cc        # compare256_rle(), etc
+                test_crc32.cc                 # crc32_armv8(), etc
+                test_crc32_copy.cc            # crc32_copy implementations
+                test_deflate_deterministic.cc # deterministic output after deflateReset
+                test_inflate_sync.cc          # expects a certain compressed block layout
+                test_main.cc                  # cpu_check_features()
+                test_version.cc               # expects a fixed version string
                 )
         endif()
 
diff --git a/test/test_deflate_deterministic.cc b/test/test_deflate_deterministic.cc
new file mode 100644 (file)
index 0000000..5cb5cb4
--- /dev/null
@@ -0,0 +1,71 @@
+/* test_deflate_deterministic.cc - Test deterministic output after deflateReset */
+
+#include "zbuild.h"
+#ifdef ZLIB_COMPAT
+#  include "zlib.h"
+#else
+#  include "zlib-ng.h"
+#endif
+
+#include <string>
+#include <gtest/gtest.h>
+
+#include "deflate.h"
+#include "test_shared.h"
+
+
+
+/* Issue: https://github.com/zlib-ng/zlib-ng/issues/2100 */
+
+/* len(data_b) must be greater len(data_a) */
+static const uint8_t data_a[] = {  0 , 'A', 'A', 'A', 'A',  0 , 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A' };
+static const uint8_t data_b[] = { 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B' };
+
+static std::string compress_data(const uint8_t *src, size_t len, PREFIX3(stream)* z_stream) {
+    const uint32_t buffer_size = 1024;
+    uint8_t buffer[buffer_size];
+    int err;
+
+    z_stream->next_in = (z_const uint8_t *)src;
+    z_stream->avail_in = (uint32_t)len;
+    z_stream->next_out = buffer;
+    z_stream->avail_out = buffer_size;
+    err = PREFIX(deflate)(z_stream, Z_FINISH);
+    EXPECT_EQ(err, Z_STREAM_END);
+    return std::string((const char *)buffer, (size_t)(z_stream->next_out - buffer));
+}
+
+TEST(deflate, deterministic) {
+    const int compression_level = 6;
+    const int window_bits = 15;
+    int err;
+
+    PREFIX3(stream) a_stream;
+    memset(&a_stream, 0, sizeof(a_stream));
+
+    /* Compress a with newly created z_stream. */
+    err = PREFIX(deflateInit2)(&a_stream, compression_level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
+    EXPECT_EQ(err, Z_OK);
+
+    const std::string a_compressed = compress_data(data_a, sizeof(data_a), &a_stream);
+    err = PREFIX(deflateEnd)(&a_stream);
+    EXPECT_EQ(err, Z_OK);
+
+    /* Compress b with newly created z_stream. */
+    PREFIX3(stream) b_stream;
+    memset(&b_stream, 0, sizeof(b_stream));
+
+    err = PREFIX(deflateInit2)(&b_stream, compression_level, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY);
+    EXPECT_EQ(err, Z_OK);
+    const std::string b_compressed = compress_data(data_b, sizeof(data_b), &b_stream);
+
+    /* Reset the stream. */
+    err = PREFIX(deflateReset)(&b_stream);
+    EXPECT_EQ(err, Z_OK);
+
+    const std::string a_compressed2 = compress_data(data_a, sizeof(data_a), &b_stream);
+    err = PREFIX(deflateEnd)(&b_stream);
+    EXPECT_EQ(err, Z_OK);
+
+    EXPECT_EQ(a_compressed, a_compressed2);
+}