Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
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; }
}
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
Loading
Loading