From c5ec042e214e1eb59fc37b619386c8aa66e7ad2d Mon Sep 17 00:00:00 2001 From: iremyux Date: Wed, 1 Jul 2026 10:49:55 +0200 Subject: [PATCH 1/5] Add public Reset() and isFinished bool to deflate decoders --- .../ref/System.IO.Compression.cs | 6 ++++ .../System/IO/Compression/DeflateDecoder.cs | 28 +++++++++++++++++++ .../src/System/IO/Compression/GZipDecoder.cs | 22 +++++++++++++++ .../src/System/IO/Compression/ZLibDecoder.cs | 23 +++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index f9ce507af62804..20fa4174cca8e3 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -21,8 +21,10 @@ public enum CompressionMode public sealed partial class DeflateDecoder : System.IDisposable { public DeflateDecoder() { } + public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } + public void Reset() { } public static bool TryDecompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } } public sealed partial class DeflateEncoder : System.IDisposable @@ -78,8 +80,10 @@ public override void WriteByte(byte value) { } public sealed partial class GZipDecoder : System.IDisposable { public GZipDecoder() { } + public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } + public void Reset() { } public static bool TryDecompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } } public sealed partial class GZipEncoder : System.IDisposable @@ -207,8 +211,10 @@ public enum ZLibCompressionStrategy public sealed partial class ZLibDecoder : System.IDisposable { public ZLibDecoder() { } + public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } + public void Reset() { } public static bool TryDecompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } } public sealed partial class ZLibEncoder : System.IDisposable diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs index 66ce9a7d3ef80f..8341dfc83b0cbf 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs @@ -13,9 +13,19 @@ namespace System.IO.Compression public sealed class DeflateDecoder : IDisposable { private ZLibNative.ZLibStreamHandle? _state; + private readonly int _windowBits; private bool _disposed; private bool _finished; + /// + /// Gets a value indicating whether the decompression operation has completed. + /// + /// + /// Once this property returns , subsequent calls to return without consuming input or producing output. + /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. + /// + public bool IsFinished => _finished; + /// /// Initializes a new instance of the class. /// @@ -27,6 +37,7 @@ public DeflateDecoder() internal DeflateDecoder(int windowBits) { + _windowBits = windowBits; _state = ZLibNative.ZLibStreamHandle.CreateForInflate(windowBits); } @@ -53,6 +64,7 @@ private void EnsureNotDisposed() /// When this method returns, the total number of bytes that were read from . /// When this method returns, the total number of bytes that were written to . /// One of the enumeration values that describes the status with which the span-based operation finished. + /// The decoder has been disposed. public OperationStatus Decompress(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) { EnsureNotDisposed(); @@ -106,6 +118,22 @@ public OperationStatus Decompress(ReadOnlySpan source, Span destinat } } + /// + /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. + /// + /// + /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// + /// The decoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + Debug.Assert(_state is not null); + + _state.InflateReset2_(_windowBits); + _finished = false; + } + /// /// Tries to decompress a source byte span into a destination span. /// diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs index 6202c7cc1c8dc8..0759fec1778c83 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs @@ -13,6 +13,15 @@ public sealed class GZipDecoder : IDisposable private readonly DeflateDecoder _deflateDecoder; private bool _disposed; + /// + /// Gets a value indicating whether the decompression operation has completed. + /// + /// + /// Once this property returns , subsequent calls to return without consuming input or producing output. + /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. + /// + public bool IsFinished => _deflateDecoder.IsFinished; + /// /// Initializes a new instance of the class. /// @@ -50,6 +59,19 @@ public OperationStatus Decompress(ReadOnlySpan source, Span destinat return _deflateDecoder.Decompress(source, destination, out bytesConsumed, out bytesWritten); } + /// + /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. + /// + /// + /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// + /// The decoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + _deflateDecoder.Reset(); + } + /// /// Tries to decompress a source byte span into a destination span. /// diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs index 7eaf6d4140c6fb..014d926e424c39 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs @@ -13,6 +13,15 @@ public sealed class ZLibDecoder : IDisposable private readonly DeflateDecoder _deflateDecoder; private bool _disposed; + /// + /// Gets a value indicating whether the decompression operation has completed. + /// + /// + /// Once this property returns , subsequent calls to return without consuming input or producing output. + /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. + /// + public bool IsFinished => _deflateDecoder.IsFinished; + /// /// Initializes a new instance of the class. /// @@ -44,12 +53,26 @@ private void EnsureNotDisposed() /// When this method returns, the total number of bytes that were read from . /// When this method returns, the total number of bytes that were written to . /// One of the enumeration values that describes the status with which the span-based operation finished. + /// The decoder has been disposed. public OperationStatus Decompress(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) { EnsureNotDisposed(); return _deflateDecoder.Decompress(source, destination, out bytesConsumed, out bytesWritten); } + /// + /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. + /// + /// + /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// + /// The decoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + _deflateDecoder.Reset(); + } + /// /// Tries to decompress a source byte span into a destination span. /// From 3468e301c6c8c630257a95dca8cf67b39d825a8e Mon Sep 17 00:00:00 2001 From: iremyux Date: Tue, 14 Jul 2026 14:16:49 +0200 Subject: [PATCH 2/5] Remove isFinished and add reset to encoders --- .../Common/src/Interop/Interop.zlib.cs | 3 +++ .../src/System/IO/Compression/ZLibNative.cs | 22 +++++++++++++++++++ .../ref/System.IO.Compression.cs | 6 ++--- .../System/IO/Compression/DeflateDecoder.cs | 11 +--------- .../System/IO/Compression/DeflateEncoder.cs | 16 ++++++++++++++ .../src/System/IO/Compression/GZipDecoder.cs | 11 +--------- .../src/System/IO/Compression/GZipEncoder.cs | 13 +++++++++++ .../src/System/IO/Compression/ZLibDecoder.cs | 11 +--------- .../src/System/IO/Compression/ZLibEncoder.cs | 13 +++++++++++ .../System.IO.Compression.Native.def | 1 + ...stem.IO.Compression.Native_unixexports.src | 1 + .../entrypoints.c | 1 + .../System.IO.Compression.Native/pal_zlib.c | 11 ++++++++++ .../System.IO.Compression.Native/pal_zlib.h | 9 ++++++++ 14 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/libraries/Common/src/Interop/Interop.zlib.cs b/src/libraries/Common/src/Interop/Interop.zlib.cs index 0ca40a2c712211..97c4c46af42023 100644 --- a/src/libraries/Common/src/Interop/Interop.zlib.cs +++ b/src/libraries/Common/src/Interop/Interop.zlib.cs @@ -23,6 +23,9 @@ internal static unsafe partial ZLibNative.ErrorCode DeflateInit2_( [LibraryImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_DeflateEnd")] internal static unsafe partial ZLibNative.ErrorCode DeflateEnd(ZLibNative.ZStream* stream); + [LibraryImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_DeflateReset")] + internal static unsafe partial ZLibNative.ErrorCode DeflateReset(ZLibNative.ZStream* stream); + [LibraryImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_InflateInit2_")] internal static unsafe partial ZLibNative.ErrorCode InflateInit2_(ZLibNative.ZStream* stream, int windowBits); diff --git a/src/libraries/Common/src/System/IO/Compression/ZLibNative.cs b/src/libraries/Common/src/System/IO/Compression/ZLibNative.cs index 195910dc00e756..e2ef5b388eb702 100644 --- a/src/libraries/Common/src/System/IO/Compression/ZLibNative.cs +++ b/src/libraries/Common/src/System/IO/Compression/ZLibNative.cs @@ -333,6 +333,28 @@ public unsafe ErrorCode Deflate(FlushCode flush) } } + public unsafe ErrorCode DeflateReset() + { + bool refAdded = false; + try + { + DangerousAddRef(ref refAdded); + EnsureState(State.InitializedForDeflate); + + fixed (ZStream* stream = &_zStream) + { + return Interop.ZLib.DeflateReset(stream); + } + } + finally + { + if (refAdded) + { + DangerousRelease(); + } + } + } + private unsafe ErrorCode DeflateEnd() { EnsureState(State.InitializedForDeflate); diff --git a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs index 20fa4174cca8e3..89e032ab80ec02 100644 --- a/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs +++ b/src/libraries/System.IO.Compression/ref/System.IO.Compression.cs @@ -21,7 +21,6 @@ public enum CompressionMode public sealed partial class DeflateDecoder : System.IDisposable { public DeflateDecoder() { } - public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } public void Reset() { } @@ -37,6 +36,7 @@ public DeflateEncoder(System.IO.Compression.ZLibCompressionOptions options) { } public void Dispose() { } public System.Buffers.OperationStatus Flush(System.Span destination, out int bytesWritten) { throw null; } public static long GetMaxCompressedLength(long inputLength) { throw null; } + public void Reset() { } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality, int windowLog) { throw null; } @@ -80,7 +80,6 @@ public override void WriteByte(byte value) { } public sealed partial class GZipDecoder : System.IDisposable { public GZipDecoder() { } - public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } public void Reset() { } @@ -96,6 +95,7 @@ public GZipEncoder(System.IO.Compression.ZLibCompressionOptions options) { } public void Dispose() { } public System.Buffers.OperationStatus Flush(System.Span destination, out int bytesWritten) { throw null; } public static long GetMaxCompressedLength(long inputLength) { throw null; } + public void Reset() { } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality, int windowLog) { throw null; } @@ -211,7 +211,6 @@ public enum ZLibCompressionStrategy public sealed partial class ZLibDecoder : System.IDisposable { public ZLibDecoder() { } - public bool IsFinished { get { throw null; } } public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan source, System.Span destination, out int bytesConsumed, out int bytesWritten) { throw null; } public void Dispose() { } public void Reset() { } @@ -227,6 +226,7 @@ public ZLibEncoder(System.IO.Compression.ZLibCompressionOptions options) { } public void Dispose() { } public System.Buffers.OperationStatus Flush(System.Span destination, out int bytesWritten) { throw null; } public static long GetMaxCompressedLength(long inputLength) { throw null; } + public void Reset() { } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality) { throw null; } public static bool TryCompress(System.ReadOnlySpan source, System.Span destination, out int bytesWritten, int quality, int windowLog) { throw null; } diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs index 8341dfc83b0cbf..0af345fe6326e6 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateDecoder.cs @@ -17,15 +17,6 @@ public sealed class DeflateDecoder : IDisposable private bool _disposed; private bool _finished; - /// - /// Gets a value indicating whether the decompression operation has completed. - /// - /// - /// Once this property returns , subsequent calls to return without consuming input or producing output. - /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. - /// - public bool IsFinished => _finished; - /// /// Initializes a new instance of the class. /// @@ -122,7 +113,7 @@ public OperationStatus Decompress(ReadOnlySpan source, Span destinat /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. /// /// - /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// After this method returns, any sliding-window history from a previous decompression is discarded. /// /// The decoder has been disposed. public void Reset() diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs index 0b70ce1f51da35..d9f9aff3d6eadb 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs @@ -296,6 +296,22 @@ public OperationStatus Flush(Span destination, out int bytesWritten) } } + /// + /// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation. + /// + /// + /// The encoder keeps the compression quality and window size it was created with. Any pending output or unflushed input from a previous, unfinished compression is discarded. + /// + /// The encoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + Debug.Assert(_state is not null); + + _state.DeflateReset(); + _finished = false; + } + /// /// Tries to compress a source byte span into a destination span using the default quality. /// diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs index 0759fec1778c83..8cebd403089f64 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipDecoder.cs @@ -13,15 +13,6 @@ public sealed class GZipDecoder : IDisposable private readonly DeflateDecoder _deflateDecoder; private bool _disposed; - /// - /// Gets a value indicating whether the decompression operation has completed. - /// - /// - /// Once this property returns , subsequent calls to return without consuming input or producing output. - /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. - /// - public bool IsFinished => _deflateDecoder.IsFinished; - /// /// Initializes a new instance of the class. /// @@ -63,7 +54,7 @@ public OperationStatus Decompress(ReadOnlySpan source, Span destinat /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. /// /// - /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// After this method returns, any sliding-window history from a previous decompression is discarded. /// /// The decoder has been disposed. public void Reset() diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs index 513474672594ac..4a2c6fa3852e0d 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs @@ -119,6 +119,19 @@ public OperationStatus Flush(Span destination, out int bytesWritten) return _deflateEncoder.Flush(destination, out bytesWritten); } + /// + /// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation. + /// + /// + /// The encoder keeps the compression quality and window size it was created with. Any pending output or unflushed input from a previous, unfinished compression is discarded. + /// + /// The encoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + _deflateEncoder.Reset(); + } + /// /// Tries to compress a source byte span into a destination span using the default quality. /// diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs index 014d926e424c39..d22933d3db8431 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibDecoder.cs @@ -13,15 +13,6 @@ public sealed class ZLibDecoder : IDisposable private readonly DeflateDecoder _deflateDecoder; private bool _disposed; - /// - /// Gets a value indicating whether the decompression operation has completed. - /// - /// - /// Once this property returns , subsequent calls to return without consuming input or producing output. - /// Call to reuse the same instance for a new, independent decompression operation, or create a new instance. - /// - public bool IsFinished => _deflateDecoder.IsFinished; - /// /// Initializes a new instance of the class. /// @@ -64,7 +55,7 @@ public OperationStatus Decompress(ReadOnlySpan source, Span destinat /// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation. /// /// - /// After this method returns, is and any sliding-window history from a previous decompression is discarded. + /// After this method returns, any sliding-window history from a previous decompression is discarded. /// /// The decoder has been disposed. public void Reset() diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs index c6bc045f8e4b31..28eb4f4009d8a4 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs @@ -111,6 +111,19 @@ public OperationStatus Flush(Span destination, out int bytesWritten) return _deflateEncoder.Flush(destination, out bytesWritten); } + /// + /// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation. + /// + /// + /// The encoder keeps the compression quality and window size it was created with. Any pending output or unflushed input from a previous, unfinished compression is discarded. + /// + /// The encoder has been disposed. + public void Reset() + { + EnsureNotDisposed(); + _deflateEncoder.Reset(); + } + /// /// Tries to compress a source byte span into a destination span using the default quality. /// diff --git a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def index 736cbbcab8aebd..34496cc73b6bad 100644 --- a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def +++ b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def @@ -17,6 +17,7 @@ EXPORTS CompressionNative_Deflate CompressionNative_DeflateEnd CompressionNative_DeflateInit2_ + CompressionNative_DeflateReset CompressionNative_Inflate CompressionNative_InflateEnd CompressionNative_InflateInit2_ diff --git a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src index 4b6725b1c0820d..fbb9536b99cc82 100644 --- a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src +++ b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src @@ -17,6 +17,7 @@ CompressionNative_Crc32 CompressionNative_Deflate CompressionNative_DeflateEnd CompressionNative_DeflateInit2_ +CompressionNative_DeflateReset CompressionNative_Inflate CompressionNative_InflateEnd CompressionNative_InflateInit2_ diff --git a/src/native/libs/System.IO.Compression.Native/entrypoints.c b/src/native/libs/System.IO.Compression.Native/entrypoints.c index 84767e534df162..4b72f5d3c0fb35 100644 --- a/src/native/libs/System.IO.Compression.Native/entrypoints.c +++ b/src/native/libs/System.IO.Compression.Native/entrypoints.c @@ -37,6 +37,7 @@ static const Entry s_compressionNative[] = DllImportEntry(CompressionNative_Deflate) DllImportEntry(CompressionNative_DeflateEnd) DllImportEntry(CompressionNative_DeflateInit2_) + DllImportEntry(CompressionNative_DeflateReset) DllImportEntry(CompressionNative_Inflate) DllImportEntry(CompressionNative_InflateEnd) DllImportEntry(CompressionNative_InflateInit2_) diff --git a/src/native/libs/System.IO.Compression.Native/pal_zlib.c b/src/native/libs/System.IO.Compression.Native/pal_zlib.c index da8f6ee83e7b0c..8ea72ec351ea34 100644 --- a/src/native/libs/System.IO.Compression.Native/pal_zlib.c +++ b/src/native/libs/System.IO.Compression.Native/pal_zlib.c @@ -152,6 +152,17 @@ int32_t CompressionNative_DeflateEnd(PAL_ZStream* stream) return result; } +int32_t CompressionNative_DeflateReset(PAL_ZStream* stream) +{ + assert(stream != NULL); + + z_stream* zStream = GetCurrentZStream(stream); + int32_t result = deflateReset(zStream); + TransferStateToPalZStream(zStream, stream); + + return result; +} + int32_t CompressionNative_InflateInit2_(PAL_ZStream* stream, int32_t windowBits) { assert(stream != NULL); diff --git a/src/native/libs/System.IO.Compression.Native/pal_zlib.h b/src/native/libs/System.IO.Compression.Native/pal_zlib.h index 56dfdaba17de45..2ea1bff7880712 100644 --- a/src/native/libs/System.IO.Compression.Native/pal_zlib.h +++ b/src/native/libs/System.IO.Compression.Native/pal_zlib.h @@ -102,6 +102,15 @@ Returns a PAL_ErrorCode indicating success or an error number on failure. */ FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENTION CompressionNative_DeflateEnd(PAL_ZStream* stream); +/* +This function is equivalent to DeflateEnd followed by DeflateInit, but does not free and reallocate +the internal compression state. The stream keeps the compression level and any other attributes set +when the stream was initialized. + +Returns a PAL_ErrorCode indicating success or an error number on failure. +*/ +FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENTION CompressionNative_DeflateReset(PAL_ZStream* stream); + /* Initializes the PAL_ZStream so the Inflate function can be invoked on it. From dd6ecb5a4724d71c7131cb74b6724da2895d6581 Mon Sep 17 00:00:00 2001 From: iremyux Date: Wed, 15 Jul 2026 17:49:34 +0200 Subject: [PATCH 3/5] Add tests --- .../IO/Compression/EncoderDecoderTestBase.cs | 88 +++++++++++++++++++ .../tests/DeflateEncoderDecoderTests.cs | 4 +- .../tests/GZipEncoderDecoderTests.cs | 4 +- .../tests/ZLibEncoderDecoderTestBase.cs | 2 +- .../tests/ZLibEncoderDecoderTests.cs | 4 +- 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs b/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs index b27712a3dd9f4d..f36df9843f98fb 100644 --- a/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs +++ b/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs @@ -557,6 +557,94 @@ public void Reset_AllowsReuseForMultipleDecompressions(bool useDictionary) } } + [Fact] + public void Reset_OnFreshInstance_IsNoOp() + { + if (!SupportsReset) + return; + + byte[] input = CreateTestData(); + + using EncoderAdapter encoder = CreateEncoder(ValidQuality, ValidWindowLog); + encoder.Reset(); + encoder.Reset(); + + byte[] compressed = new byte[GetMaxCompressedLength(input.Length)]; + OperationStatus compressStatus = encoder.Compress(input, compressed, out int consumed, out int compressedLength, isFinalBlock: true); + Assert.Equal(OperationStatus.Done, compressStatus); + Assert.Equal(input.Length, consumed); + + using DecoderAdapter decoder = CreateDecoder(); + decoder.Reset(); + decoder.Reset(); + + byte[] output = new byte[input.Length]; + OperationStatus decompressStatus = decoder.Decompress(compressed.AsSpan(0, compressedLength), output, out _, out int written); + Assert.Equal(OperationStatus.Done, decompressStatus); + Assert.Equal(input.Length, written); + Assert.Equal(input, output); + } + + [Fact] + public void Reset_AfterUnfinishedCompression_ProducesIndependentStream() + { + if (!SupportsReset) + return; + + byte[] input = CreateTestData(); + + using EncoderAdapter encoder = CreateEncoder(ValidQuality, ValidWindowLog); + + // Start compressing without finalizing, leaving the encoder mid-operation. + byte[] scratch = new byte[GetMaxCompressedLength(input.Length)]; + encoder.Compress(input, scratch, out _, out _, isFinalBlock: false); + + // Reset must discard the in-progress state so the next operation is independent. + encoder.Reset(); + + byte[] compressed = new byte[GetMaxCompressedLength(input.Length)]; + OperationStatus status = encoder.Compress(input, compressed, out int consumed, out int compressedLength, isFinalBlock: true); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(input.Length, consumed); + + // The produced stream must be a valid, complete stream that round-trips. + byte[] output = new byte[input.Length]; + using DecoderAdapter decoder = CreateDecoder(); + OperationStatus decompressStatus = decoder.Decompress(compressed.AsSpan(0, compressedLength), output, out _, out int written); + Assert.Equal(OperationStatus.Done, decompressStatus); + Assert.Equal(input.Length, written); + Assert.Equal(input, output); + } + + [Fact] + public void Reset_AfterPartialDecompression_ProducesIndependentResult() + { + if (!SupportsReset) + return; + + byte[] input = CreateTestData(); + byte[] compressed = new byte[GetMaxCompressedLength(input.Length)]; + Assert.True(TryCompress(input, compressed, out int compressedLength)); + compressed = compressed.AsSpan(0, compressedLength).ToArray(); + + using DecoderAdapter decoder = CreateDecoder(); + + // Decompress into a buffer too small to hold the full result, leaving the decoder mid-stream. + byte[] tooSmall = new byte[input.Length / 2]; + OperationStatus partialStatus = decoder.Decompress(compressed, tooSmall, out _, out _); + Assert.Equal(OperationStatus.DestinationTooSmall, partialStatus); + + // Reset must discard the partial state so the same stream can be decoded from scratch. + decoder.Reset(); + + byte[] output = new byte[input.Length]; + OperationStatus status = decoder.Decompress(compressed, output, out int consumed, out int written); + Assert.Equal(OperationStatus.Done, status); + Assert.Equal(compressed.Length, consumed); + Assert.Equal(input.Length, written); + Assert.Equal(input, output); + } + [Theory] [MemberData(nameof(GetRoundTripTestData))] public void RoundTrip_SuccessfullyCompressesAndDecompresses(int quality, bool useDictionary, bool staticEncode, bool staticDecode) diff --git a/src/libraries/System.IO.Compression/tests/DeflateEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/DeflateEncoderDecoderTests.cs index 246fa6cc7f55cb..52fbad18252159 100644 --- a/src/libraries/System.IO.Compression/tests/DeflateEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/DeflateEncoderDecoderTests.cs @@ -24,7 +24,7 @@ public override OperationStatus Flush(Span destination, out int bytesWritt _encoder.Flush(destination, out bytesWritten); public override void Dispose() => _encoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _encoder.Reset(); } public class DeflateDecoderAdapter : DecoderAdapter @@ -40,7 +40,7 @@ public override OperationStatus Decompress(ReadOnlySpan source, Span _decoder.Decompress(source, destination, out bytesConsumed, out bytesWritten); public override void Dispose() => _decoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _decoder.Reset(); } protected override EncoderAdapter CreateEncoder() => diff --git a/src/libraries/System.IO.Compression/tests/GZipEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/GZipEncoderDecoderTests.cs index 9f0e6340a3aab5..ca6ed4ddccc291 100644 --- a/src/libraries/System.IO.Compression/tests/GZipEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/GZipEncoderDecoderTests.cs @@ -24,7 +24,7 @@ public override OperationStatus Flush(Span destination, out int bytesWritt _encoder.Flush(destination, out bytesWritten); public override void Dispose() => _encoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _encoder.Reset(); } public class GZipDecoderAdapter : DecoderAdapter @@ -40,7 +40,7 @@ public override OperationStatus Decompress(ReadOnlySpan source, Span _decoder.Decompress(source, destination, out bytesConsumed, out bytesWritten); public override void Dispose() => _decoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _decoder.Reset(); } protected override EncoderAdapter CreateEncoder() => diff --git a/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTestBase.cs b/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTestBase.cs index 703133663df626..d676cc01109681 100644 --- a/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTestBase.cs +++ b/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTestBase.cs @@ -9,7 +9,7 @@ namespace System.IO.Compression public abstract class ZLibEncoderDecoderTestBase : EncoderDecoderTestBase { protected override bool SupportsDictionaries => false; - protected override bool SupportsReset => false; + protected override bool SupportsReset => true; protected override string WindowLogParamName => "windowLog"; protected override string InputLengthParamName => "inputLength"; diff --git a/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTests.cs b/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTests.cs index e4e09f0ebf60fb..c6d649086a2df3 100644 --- a/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZLibEncoderDecoderTests.cs @@ -24,7 +24,7 @@ public override OperationStatus Flush(Span destination, out int bytesWritt _encoder.Flush(destination, out bytesWritten); public override void Dispose() => _encoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _encoder.Reset(); } public class ZLibDecoderAdapter : DecoderAdapter @@ -40,7 +40,7 @@ public override OperationStatus Decompress(ReadOnlySpan source, Span _decoder.Decompress(source, destination, out bytesConsumed, out bytesWritten); public override void Dispose() => _decoder.Dispose(); - public override void Reset() => throw new NotSupportedException(); + public override void Reset() => _decoder.Reset(); } protected override EncoderAdapter CreateEncoder() => From cc90ddcbe01046134d4dc2de15d039c27f8f20b9 Mon Sep 17 00:00:00 2001 From: iremyux Date: Fri, 24 Jul 2026 11:35:38 +0200 Subject: [PATCH 4/5] Update comment --- src/native/libs/System.IO.Compression.Native/pal_zlib.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/native/libs/System.IO.Compression.Native/pal_zlib.h b/src/native/libs/System.IO.Compression.Native/pal_zlib.h index 2ea1bff7880712..9e67bbc8da1c99 100644 --- a/src/native/libs/System.IO.Compression.Native/pal_zlib.h +++ b/src/native/libs/System.IO.Compression.Native/pal_zlib.h @@ -103,9 +103,10 @@ Returns a PAL_ErrorCode indicating success or an error number on failure. FUNCTIONEXPORT int32_t FUNCTIONCALLINGCONVENTION CompressionNative_DeflateEnd(PAL_ZStream* stream); /* -This function is equivalent to DeflateEnd followed by DeflateInit, but does not free and reallocate -the internal compression state. The stream keeps the compression level and any other attributes set -when the stream was initialized. +This function is equivalent to CompressionNative_DeflateEnd followed by CompressionNative_DeflateInit2_, +but does not free and reallocate the internal compression state. The stream preserves the original +initialization settings (compression level, window bits, memory level, and strategy) that were supplied +to CompressionNative_DeflateInit2_. Returns a PAL_ErrorCode indicating success or an error number on failure. */ From 7aa6a8639173c0b45ae8a50cd3f69735b96768c7 Mon Sep 17 00:00:00 2001 From: iremyux Date: Wed, 29 Jul 2026 13:33:27 +0200 Subject: [PATCH 5/5] Add CompressionNative_DeflateReset to callhelpers-pinvoke.cpp --- src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp b/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp index f18883c38a6431..9dbd7f579c68e6 100644 --- a/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp +++ b/src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp @@ -16,6 +16,7 @@ extern "C" { int32_t CompressionNative_Deflate (void *, int32_t); int32_t CompressionNative_DeflateEnd (void *); int32_t CompressionNative_DeflateInit2_ (void *, int32_t, int32_t, int32_t, int32_t, int32_t); + int32_t CompressionNative_DeflateReset (void *); int32_t CompressionNative_Inflate (void *, int32_t); int32_t CompressionNative_InflateEnd (void *); int32_t CompressionNative_InflateInit2_ (void *, int32_t); @@ -200,6 +201,7 @@ static const Entry s_libSystem_IO_Compression_Native [] = { DllImportEntry(CompressionNative_Deflate) // System.IO.Compression, System.Net.WebSockets DllImportEntry(CompressionNative_DeflateEnd) // System.IO.Compression, System.Net.WebSockets DllImportEntry(CompressionNative_DeflateInit2_) // System.IO.Compression, System.Net.WebSockets + DllImportEntry(CompressionNative_DeflateReset) // System.IO.Compression DllImportEntry(CompressionNative_Inflate) // System.IO.Compression, System.Net.WebSockets DllImportEntry(CompressionNative_InflateEnd) // System.IO.Compression, System.Net.WebSockets DllImportEntry(CompressionNative_InflateInit2_) // System.IO.Compression, System.Net.WebSockets @@ -324,7 +326,7 @@ typedef struct PInvokeTable { static PInvokeTable s_PInvokeTables[] = { {"libSystem.Globalization.Native", s_libSystem_Globalization_Native, 34}, - {"libSystem.IO.Compression.Native", s_libSystem_IO_Compression_Native, 9}, + {"libSystem.IO.Compression.Native", s_libSystem_IO_Compression_Native, 10}, {"libSystem.Native", s_libSystem_Native, 94}, {"libSystem.Native.Browser", s_libSystem_Native_Browser, 1}, {"libSystem.Runtime.InteropServices.JavaScript.Native", s_libSystem_Runtime_InteropServices_JavaScript_Native, 6}