Skip to content

Add span-based Deflate, ZLib and GZip encoder/decoder APIs#123145

Open
iremyux wants to merge 89 commits intodotnet:mainfrom
iremyux:62113-zlib-encoder-decoder
Open

Add span-based Deflate, ZLib and GZip encoder/decoder APIs#123145
iremyux wants to merge 89 commits intodotnet:mainfrom
iremyux:62113-zlib-encoder-decoder

Conversation

@iremyux
Copy link
Copy Markdown
Contributor

@iremyux iremyux commented Jan 13, 2026

This PR introduces new span-based, streamless compression and decompression APIs for Deflate, ZLib, and GZip formats, matching the existing BrotliEncoder/BrotliDecoder pattern.

New APIs

  • DeflateEncoder / DeflateDecoder
  • ZLibEncoder / ZLibDecoder
  • GZipEncoder / GZipDecoder

These classes provide:

  • Instance-based API for streaming/chunked compression with Compress(), Decompress(), and Flush()
  • Static one-shot API via TryCompress() and TryDecompress() for simple scenarios
  • GetMaxCompressedLength() to calculate buffer sizes

Closes #62113
Closes #39327
Closes #44793

@iremyux iremyux changed the title [WIP] Add span-based ZlibEncoder and ZlibDecoder APIs [WIP] Add span-based Deflate, ZLib and GZip encoder/decoder APIs Jan 19, 2026
CompressionLevel.Fastest => ZLibNative.CompressionLevel.BestSpeed,
CompressionLevel.NoCompression => ZLibNative.CompressionLevel.NoCompression,
CompressionLevel.SmallestSize => ZLibNative.CompressionLevel.BestCompression,
_ => throw new ArgumentOutOfRangeException(nameof(compressionLevel)),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would fail on valid native compression levels not covered by the CompressionLevel enum. Instead I think it should check if the value is is < -1 or > 9 to throw out of range instead.

Copy link
Copy Markdown
Member

@AraHaan AraHaan Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also to add on to the above, now those who want compression levels that just happen to == a value in the CompressionLevel enum will now not be able to use those compression levels either. Perhaps a solution to this is to expose a version of the ctor with CompressionLevel and a version with int that gets casted to ZLibNative.CompressionLevel after a range check.

Copilot AI review requested due to automatic review settings March 21, 2026 20:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 2 comments.

Copilot AI review requested due to automatic review settings March 22, 2026 18:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.

Comment on lines +179 to +187
public static long GetMaxCompressedLength(long inputLength)
{
ArgumentOutOfRangeException.ThrowIfNegative(inputLength);
ArgumentOutOfRangeException.ThrowIfGreaterThan(inputLength, uint.MaxValue);

// compressBound() returns the upper bound for zlib-wrapped deflate output.
// For raw deflate (no header/trailer) this slightly overestimates, which is safe.
return (long)Interop.ZLib.compressBound((uint)inputLength);
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetMaxCompressedLength relies on Interop.ZLib.compressBound which currently returns a 32-bit value. For sufficiently large inputLength, the true bound can exceed 32 bits, leading to truncation and an incorrect (too small) maximum length. Either tighten the allowed inputLength range to what the underlying bound can represent safely, or switch the native + interop compressBound plumbing to a wider return type and add managed overflow checks (similar to ZstandardEncoder.GetMaxCompressedLength).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird to accept long as input but then throw if it is outside of 32-bit uint range. Can we make larger numbers work?

Copilot AI review requested due to automatic review settings March 25, 2026 12:05
…ZLibCompressionOptions.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 34 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/libraries/System.IO.Compression/tests/Zstandard/ZstandardEncoderDecoderTests.cs:25

  • ZstandardUtils.WindowLog_Max is platform-dependent (31 on 64-bit, 30 on 32-bit). Hardcoding MaxWindowLog=31 and InvalidWindowLogTooHigh=32 will likely fail on 32-bit test runs. Consider using Environment.Is64BitProcess (or referencing ZstandardUtils.WindowLog_Max) to set MaxWindowLog and the corresponding invalid-too-high value.
        protected override int MinWindowLog => 10;
        protected override int MaxWindowLog => 31;
        protected override int InvalidQualityTooLow => -(1 << 17) - 1;
        protected override int InvalidQualityTooHigh => 23;
        protected override int InvalidWindowLogTooLow => 9;
        protected override int InvalidWindowLogTooHigh => 32;

Comment on lines +110 to +126
private static int ResolveWindowBits(int windowLog, CompressionFormat format)
{
// zlib-ng rejects windowBits 8 for raw deflate and gzip; classic zlib silently upgrades to 9.
if (format != CompressionFormat.ZLib)
{
windowLog = Math.Max(windowLog, 9);
}

return format switch
{
CompressionFormat.Deflate => -windowLog,
CompressionFormat.ZLib => windowLog,
CompressionFormat.GZip => windowLog + 16,
_ => throw new ArgumentOutOfRangeException(nameof(format))
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to some shared class (ZLibNative?) and reuse this in DeflateStream etc?

Comment on lines +179 to +187
public static long GetMaxCompressedLength(long inputLength)
{
ArgumentOutOfRangeException.ThrowIfNegative(inputLength);
ArgumentOutOfRangeException.ThrowIfGreaterThan(inputLength, uint.MaxValue);

// compressBound() returns the upper bound for zlib-wrapped deflate output.
// For raw deflate (no header/trailer) this slightly overestimates, which is safe.
return (long)Interop.ZLib.compressBound((uint)inputLength);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird to accept long as input but then throw if it is outside of 32-bit uint range. Can we make larger numbers work?

}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this whitespace change?

}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this whitespace change?

}
}, testScenario.ToString()).Dispose();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this whitespace change?


namespace System.IO.Compression
{
public abstract class ZLibEncoderDecoderTestBase : EncoderDecoderTestBase
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can be moved to System.IO.Compression.Tests, as it is only used by its tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add Deflate, ZLib and GZip encoder/decoder APIs Add static compression helper methods Span-based (non-stream) compression APIs

5 participants