Skip to content

[API Proposal]: Add Deflate, ZLib and GZip encoder/decoder APIs #62113

@AraHaan

Description

@AraHaan

Background and motivation

Currently there is no non-stream based apis to zlib compression. As such I feel like an encoder / decoder implementation is needed similar to the Brotli implementation.

The brotli implementation also uses the encoder / decoder internally in the streams, which can help make the implementations of the zlib based streams (GZipStream, DeflateStream, and ZLibStream) better.

A single ZlibEncoder and ZlibDecoder that takes a class of values (ZlibOptions), where ZlibOptions also has subclasses named DeflateOptions, and GZipOptions where only the window bits are different.

This issue partially addresses:

Implementing this issue should also resolve this one as well:

I currently have a baseline implementation locally of this (except for the stream changes that would need to be done), and it should be ready by the time .NET 7 goes into an api freeze (until .NET 8's development starts).

API Proposal ( updated by @iremyux )

namespace System.IO.Compression
{
   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 { } }
   }
}
namespace System.IO.Compression
{  
  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 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 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; }
  }
}

API Usage ( updated by @iremyux )

// Simple one-shot compression (Deflate)
byte[] compressed = new byte[DeflateEncoder.GetMaxCompressedLength(data.Length)];
bool success = DeflateEncoder.TryCompress(data, compressed, out int bytesWritten);

// With compression level (ZLib format)
ZLibEncoder.TryCompress(data, compressed, out bytesWritten, CompressionLevel.SmallestSize);

// GZip format
GZipEncoder.TryCompress(data, compressed, out bytesWritten, CompressionLevel.Fastest);

// Stateful for streaming/chunked data
using var encoder = new DeflateEncoder(CompressionLevel.Optimal);
encoder.Compress(chunk1, dest, out int consumed, out int written, isFinalBlock: false);
encoder.Compress(chunk2, dest, out consumed, out written, isFinalBlock: true);

// With strategy
using var encoder2 = new ZLibEncoder(CompressionLevel.Fastest, ZLibCompressionStrategy.HuffmanOnly);

// With fine-grained options (compression level 0-9)
var options = new ZLibCompressionOptions { CompressionLevel = 9, CompressionStrategy = ZLibCompressionStrategy.Filtered };
using var encoder3 = new GZipEncoder(options);

// Decompression
using var decoder = new GZipDecoder();
decoder.Decompress(compressed, decompressed, out consumed, out written);

// One-shot decompression
ZLibDecoder.TryDecompress(compressed, decompressed, out bytesWritten);

Alternative Designs ( updated by @iremyux )

  1. We considered having a single ZlibEncoder class with a ZlibCompressionFormat enum parameter (Deflate, ZLib, GZip) to select the output format. However, this would be confusing for users: creating a GZip formatted output from something called "ZlibEncoder" is unintuitive. It also doesn't match the existing pattern where DeflateStream, GZipStream, and ZLibStream are separate classes.

Metadata

Metadata

Assignees

Labels

api-ready-for-reviewAPI is ready for review, it is NOT ready for implementationarea-System.IO.Compressionin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions