Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e402958
Add Zlib Encoder and Decoder classes
iremyux Jan 12, 2026
95ac930
Add tests
iremyux Jan 12, 2026
88acc57
Merge branch 'dotnet:main' into 62113-zlib-encoder-decoder
iremyux Jan 13, 2026
6f1ab80
Use CompressionLevel instead of int
iremyux Jan 15, 2026
8926759
Use CompressionLevel instead of int
iremyux Jan 15, 2026
bb2d89c
Introduce seperate encoders and decoders
iremyux Jan 19, 2026
a21a800
Update the tests accordingly
iremyux Jan 19, 2026
4b87dcd
Remove ZlibEncoder and decoder with lower L
iremyux Jan 19, 2026
aabc3a6
Add ZLibEncoder and Decoder with capital L
iremyux Jan 19, 2026
cfb3d7e
Change GetMaxCompressedLengt's inputLength to long
iremyux Jan 19, 2026
f031eed
"File is required to end with a single newline character"
iremyux Jan 19, 2026
1d18eec
Add Interop layer for zlib's compressBound and use it inside GetMaxCo…
iremyux Jan 20, 2026
0cf5b04
Merge branch 'main' into 62113-zlib-encoder-decoder
iremyux Jan 23, 2026
01fba5a
Fix gzip's header+trailer size
iremyux Jan 23, 2026
03fbcbc
Add CompressionNative_CompressBound to pal_zlib.h
iremyux Jan 23, 2026
665ae10
Remove empty line
iremyux Jan 23, 2026
ee0a437
Add CompressionNative_CompressBound to entrypoints.c
iremyux Jan 26, 2026
238aa58
Use EncoderDecoderTestBase for tests
iremyux Feb 4, 2026
aeebd78
Expose quality and windowLog for encoders & remove compressionLevel enum
iremyux Feb 9, 2026
ab9ee4a
Add WindowLog to ZLibCompressionOptions
iremyux Feb 9, 2026
cdb1cb4
Change the parameter name from inputSize to inputLength
iremyux Feb 12, 2026
46c5a8f
Add WindowLog to ZLibCompressionOptions
iremyux Feb 12, 2026
e35f082
VAlidate inputLength
iremyux Feb 13, 2026
fb4b1c8
Remove EnsureInitialized
iremyux Feb 13, 2026
f82ddc4
Fix tests
iremyux Feb 13, 2026
89ba215
Fix tests
iremyux Feb 13, 2026
6f05f53
Change the parameter name from inputSize to inputLength
iremyux Feb 13, 2026
c6384e4
Remove unnecessary parameters
iremyux Feb 16, 2026
f3dd1c4
Move test to TestBase
iremyux Feb 16, 2026
91b2890
Remove unused library
iremyux Feb 16, 2026
e52c80e
Remove function body
iremyux Feb 16, 2026
802d228
Add EnsureNotDisposed method to Gzip & Zlib too
iremyux Feb 16, 2026
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 @@ -37,5 +37,8 @@ internal static unsafe partial ZLibNative.ErrorCode DeflateInit2_(

[LibraryImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_Crc32")]
internal static unsafe partial uint crc32(uint crc, byte* buffer, int len);

[LibraryImport(Libraries.CompressionNative, EntryPoint = "CompressionNative_CompressBound")]
internal static partial uint compressBound(uint sourceLen);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,25 @@ public void RoundTrip_Chunks()
}
}

[Fact]
public void RoundTrip_AllCompressionLevels()
{
byte[] input = CreateTestData();

for (int quality = 0; quality <= 9; quality++)
{
byte[] compressed = new byte[GetMaxCompressedLength(input.Length)];
using var encoder = CreateEncoder(quality, ValidWindowLog);
encoder.Compress(input, compressed, out _, out int compressedSize, isFinalBlock: true);

byte[] decompressed = new byte[input.Length];
using var decoder = CreateDecoder();
decoder.Decompress(compressed.AsSpan(0, compressedSize), decompressed, out _, out _);

Assert.Equal(input, decompressed);
}
}

public static byte[] CreateTestData(int size = 1000)
{
// Create test data of specified size
Expand Down
64 changes: 64 additions & 0 deletions src/libraries/System.IO.Compression/ref/System.IO.Compression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ public enum CompressionMode
Decompress = 0,
Compress = 1,
}
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 static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class DeflateEncoder : System.IDisposable
{
public DeflateEncoder() { }
public DeflateEncoder(int quality) { }
public DeflateEncoder(int quality, int windowLog) { }
public DeflateEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
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 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 windowLog) { throw null; }
}
public partial class DeflateStream : System.IO.Stream
{
public DeflateStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel) { }
Expand Down Expand Up @@ -54,6 +75,27 @@ public override void Write(System.ReadOnlySpan<byte> buffer) { }
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override void WriteByte(byte value) { }
}
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 static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class GZipEncoder : System.IDisposable
{
public GZipEncoder() { }
public GZipEncoder(int quality) { }
public GZipEncoder(int quality, int windowLog) { }
public GZipEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
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 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 windowLog) { throw null; }
}
public partial class GZipStream : System.IO.Stream
{
public GZipStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel) { }
Expand Down Expand Up @@ -149,6 +191,7 @@ public sealed partial class ZLibCompressionOptions
public ZLibCompressionOptions() { }
public int CompressionLevel { get { throw null; } set { } }
public System.IO.Compression.ZLibCompressionStrategy CompressionStrategy { get { throw null; } set { } }
public int WindowLog { get { throw null; } set { } }
}
public enum ZLibCompressionStrategy
{
Expand All @@ -158,6 +201,27 @@ public enum ZLibCompressionStrategy
RunLengthEncoding = 3,
Fixed = 4,
}
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 static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class ZLibEncoder : System.IDisposable
{
public ZLibEncoder() { }
public ZLibEncoder(int quality) { }
public ZLibEncoder(int quality, int windowLog) { }
public ZLibEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
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 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 windowLog) { throw null; }
}
public sealed partial class ZLibStream : System.IO.Stream
{
public ZLibStream(System.IO.Stream stream, System.IO.Compression.CompressionLevel compressionLevel) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
<data name="ZLibErrorUnexpected" xml:space="preserve">
<value>The underlying compression routine returned an unexpected error code: '{0}'.</value>
</data>

<data name="CDCorrupt" xml:space="preserve">
<value>Central Directory corrupt.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@
<Compile Include="System\IO\Compression\Crc32Helper.ZLib.cs" />
<Compile Include="System\IO\Compression\GZipStream.cs" />
<Compile Include="System\IO\Compression\PositionPreservingWriteOnlyStreamWrapper.cs" />
<Compile Include="System\IO\Compression\DeflateDecoder.cs" />
<Compile Include="System\IO\Compression\DeflateEncoder.cs" />
<Compile Include="System\IO\Compression\GZipDecoder.cs" />
<Compile Include="System\IO\Compression\GZipEncoder.cs" />
<Compile Include="System\IO\Compression\ZLibCompressionOptions.cs" />
<Compile Include="System\IO\Compression\ZLibDecoder.cs" />
<Compile Include="System\IO\Compression\ZLibEncoder.cs" />
<Compile Include="System\IO\Compression\ZLibStream.cs" />
<Compile Include="System\IO\Compression\ZipCompressionMethod.cs" />
<Compile Include="$(CommonPath)System\Obsoletions.cs"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.IO.Compression
{
/// <summary>
/// Provides methods and static methods to decode data compressed in the Deflate data format in a streamless, non-allocating, and performant manner.
/// </summary>
public sealed class DeflateDecoder : IDisposable
{
private ZLibNative.ZLibStreamHandle? _state;
private bool _disposed;
private bool _finished;

/// <summary>
/// Initializes a new instance of the <see cref="DeflateDecoder"/> class.
/// </summary>
/// <exception cref="IOException">Failed to create the <see cref="DeflateDecoder"/> instance.</exception>
public DeflateDecoder()
: this(ZLibNative.Deflate_DefaultWindowBits)
{
}

internal DeflateDecoder(int windowBits)
{
_disposed = false;
_finished = false;
_state = ZLibNative.ZLibStreamHandle.CreateForInflate(windowBits);
}

/// <summary>
/// Frees and disposes unmanaged resources.
/// </summary>
public void Dispose()
{
_disposed = true;
_state?.Dispose();
_state = null;
}

private void EnsureNotDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}

/// <summary>
/// Decompresses a read-only byte span into a destination span.
/// </summary>
/// <param name="source">A read-only span of bytes containing the compressed source data.</param>
/// <param name="destination">When this method returns, a byte span where the decompressed data is stored.</param>
/// <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>
public OperationStatus Decompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten)
{
EnsureNotDisposed();
Debug.Assert(_state is not null);

bytesConsumed = 0;
bytesWritten = 0;

if (_finished)
{
return OperationStatus.Done;
}

if (destination.IsEmpty && source.Length > 0)
{
return OperationStatus.DestinationTooSmall;
}

unsafe
{
fixed (byte* inputPtr = &MemoryMarshal.GetReference(source))
fixed (byte* outputPtr = &MemoryMarshal.GetReference(destination))
{
_state.NextIn = (IntPtr)inputPtr;
_state.AvailIn = (uint)source.Length;
_state.NextOut = (IntPtr)outputPtr;
_state.AvailOut = (uint)destination.Length;

ZLibNative.ErrorCode errorCode = _state.Inflate(ZLibNative.FlushCode.NoFlush);

bytesConsumed = source.Length - (int)_state.AvailIn;
bytesWritten = destination.Length - (int)_state.AvailOut;

OperationStatus status = errorCode switch
{
ZLibNative.ErrorCode.Ok => _state.AvailIn == 0 && _state.AvailOut > 0
? OperationStatus.NeedMoreData
: _state.AvailOut == 0
? OperationStatus.DestinationTooSmall
: OperationStatus.NeedMoreData,
ZLibNative.ErrorCode.StreamEnd => OperationStatus.Done,
ZLibNative.ErrorCode.BufError => _state.AvailOut == 0
? OperationStatus.DestinationTooSmall
: OperationStatus.NeedMoreData,
ZLibNative.ErrorCode.DataError => OperationStatus.InvalidData,
_ => OperationStatus.InvalidData
};

// Track if decompression is finished
if (errorCode == ZLibNative.ErrorCode.StreamEnd)
{
_finished = true;
}

return status;
}
}
}

/// <summary>
/// Tries to decompress a source byte span into a destination span.
/// </summary>
/// <param name="source">A read-only span of bytes containing the compressed source data.</param>
/// <param name="destination">When this method returns, a span of bytes where the decompressed data is stored.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes that were written to <paramref name="destination"/>.</param>
/// <returns><see langword="true"/> if the decompression operation was successful; <see langword="false"/> otherwise.</returns>
public static bool TryDecompress(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
{
using var decoder = new DeflateDecoder();
OperationStatus status = decoder.Decompress(source, destination, out int consumed, out bytesWritten);

return status == OperationStatus.Done && consumed == source.Length;
}
}
}
Loading
Loading