Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/libraries/Common/src/Interop/Interop.zlib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
22 changes: 22 additions & 0 deletions src/libraries/Common/src/System/IO/Compression/ZLibNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public sealed partial class DeflateDecoder : System.IDisposable
public DeflateDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public void Reset() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
Comment thread
iremyux marked this conversation as resolved.
}
public sealed partial class DeflateEncoder : System.IDisposable
Expand All @@ -35,6 +36,7 @@ public DeflateEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public void Reset() { }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog2) { throw null; }
Expand Down Expand Up @@ -80,6 +82,7 @@ public sealed partial class GZipDecoder : System.IDisposable
public GZipDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public void Reset() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class GZipEncoder : System.IDisposable
Expand All @@ -92,6 +95,7 @@ public GZipEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public void Reset() { }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog2) { throw null; }
Expand Down Expand Up @@ -227,6 +231,7 @@ public sealed partial class ZLibDecoder : System.IDisposable
public ZLibDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public void Reset() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class ZLibEncoder : System.IDisposable
Expand All @@ -239,6 +244,7 @@ public ZLibEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public void Reset() { }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog2) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace System.IO.Compression
public sealed class DeflateDecoder : IDisposable
{
private ZLibNative.ZLibStreamHandle? _state;
private readonly int _windowBits;
private bool _disposed;
private bool _finished;

Expand All @@ -27,6 +28,7 @@ public DeflateDecoder()

internal DeflateDecoder(int windowBits)
{
_windowBits = windowBits;
_state = ZLibNative.ZLibStreamHandle.CreateForInflate(windowBits);
}

Expand All @@ -53,6 +55,7 @@ private void EnsureNotDisposed()
/// <param name="bytesConsumed">When this method returns, the total number of bytes that were read from <paramref name="source"/>.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
/// <returns>One of the enumeration values that describes the status with which the span-based operation finished.</returns>
/// <exception cref="ObjectDisposedException">The decoder has been disposed.</exception>
public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten)
{
EnsureNotDisposed();
Expand Down Expand Up @@ -106,6 +109,22 @@ public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destinat
}
}

/// <summary>
/// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation.
/// </summary>
/// <remarks>
/// After this method returns, any sliding-window history from a previous decompression is discarded.
/// </remarks>
/// <exception cref="ObjectDisposedException">The decoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
Debug.Assert(_state is not null);

_state.InflateReset2_(_windowBits);
_finished = false;
}

/// <summary>
/// Tries to decompress a source byte span into a destination span.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,22 @@ public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
}
}

/// <summary>
/// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
Debug.Assert(_state is not null);

_state.DeflateReset();
_finished = false;
}

/// <summary>
/// Tries to compress a source byte span into a destination span using the default quality.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destinat
return _deflateDecoder.Decompress(source, destination, out bytesConsumed, out bytesWritten);
}

/// <summary>
/// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation.
/// </summary>
/// <remarks>
/// After this method returns, any sliding-window history from a previous decompression is discarded.
/// </remarks>
/// <exception cref="ObjectDisposedException">The decoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
_deflateDecoder.Reset();
}

/// <summary>
/// Tries to decompress a source byte span into a destination span.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
return _deflateEncoder.Flush(destination, out bytesWritten);
}

/// <summary>
/// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
_deflateEncoder.Reset();
}

/// <summary>
/// Tries to compress a source byte span into a destination span using the default quality.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,26 @@ private void EnsureNotDisposed()
/// <param name="bytesConsumed">When this method returns, the total number of bytes that were read from <paramref name="source"/>.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
/// <returns>One of the enumeration values that describes the status with which the span-based operation finished.</returns>
/// <exception cref="ObjectDisposedException">The decoder has been disposed.</exception>
public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten)
{
EnsureNotDisposed();
return _deflateDecoder.Decompress(source, destination, out bytesConsumed, out bytesWritten);
}

/// <summary>
/// Resets the decoder to its initial state so the same instance can be reused for a new, independent decompression operation.
/// </summary>
/// <remarks>
/// After this method returns, any sliding-window history from a previous decompression is discarded.
/// </remarks>
/// <exception cref="ObjectDisposedException">The decoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
_deflateDecoder.Reset();
}

/// <summary>
/// Tries to decompress a source byte span into a destination span.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ public OperationStatus Flush(Span<byte> destination, out int bytesWritten)
return _deflateEncoder.Flush(destination, out bytesWritten);
}

/// <summary>
/// Resets the encoder to its initial state so the same instance can be reused for a new, independent compression operation.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <exception cref="ObjectDisposedException">The encoder has been disposed.</exception>
public void Reset()
{
EnsureNotDisposed();
_deflateEncoder.Reset();
}

/// <summary>
/// Tries to compress a source byte span into a destination span using the default quality.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public override OperationStatus Flush(Span<byte> 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
Expand All @@ -40,7 +40,7 @@ public override OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte>
_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() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public override OperationStatus Flush(Span<byte> 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
Expand All @@ -40,7 +40,7 @@ public override OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte>
_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() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 => "windowLog2";
protected override string InputLengthParamName => "inputLength";
Expand Down
Loading
Loading