You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Compress(byte[]) throws for incompressible input over ~280 KB: GetMaxCompressedLength undercounts Deflate/Gzip/ZLib overhead #39
The Deflate, Gzip, ZLib and Brotli providers compute GetMaxCompressedLength as n + ((n / 65535) + 1) * 5 + 64 (for example in Essentials.CompressionProviders.Deflate/DeflateCompressionProvider.cs:23-24). The remark says this is because incompressible input goes into 64 KB stored blocks with a 5-byte header each.
zlib at the default and Optimal level doesn't do that. It closes a block every time its literal buffer fills, which is roughly every 16 KB. So the per-block overhead comes about four times as often as the formula allows.
For random data, raw deflate at level 6 (the same as .NET Optimal) gives:
input
actual output
bound
280,224 B
280,314 B
280,313 B
1 MiB
1,048,896 B
1,048,725 B
10 MiB
10,488,960 B
10,486,629 B
Gzip and ZLib add their own wrapper on top of this, so they exceed the bound too.
Why it matters
ICompressionProvider.Compress(ReadOnlySpan<byte>) (Essentials/ICompressionProvider.cs:~55) allocates its buffer from GetMaxCompressedLength through ProviderHelpers.ExecuteToExactArray. For inputs above about 280 KB, TryCompress then returns false, and the caller gets InvalidOperationException("Compression failed to produce output with the allocated buffer."). This affects any already-compressed, encrypted or random payload, such as images, archives or ciphertext.
It also breaks the contract that a destination sized from GetMaxCompressedLength guarantees TryCompress succeeds.
ProviderContractTests.Compression_Bound_Holds_For_Incompressible_Input only exercises 4096 bytes, which is why it still passes.
Suggested fix
Use zlib's deflateBound: n + (n >> 12) + (n >> 14) + (n >> 25) + 13, plus the container overhead (+6 for zlib, +18 for gzip).
For Brotli, use BrotliEncoder.GetMaxCompressedLength(n).
Compute in long, and throw or clamp near int.MaxValue so the bound can't overflow.
Extend the contract test to 1 MiB and 10 MiB of random data for every provider.
Priority: High. Compress(byte[]) throws for any incompressible payload above about 280 KB. That covers common inputs such as images, archives and ciphertext. It also breaks the GetMaxCompressedLength contract that a buffer of that size is always enough.
Duplicates / in progress: None found. No open PR references this issue.
Notes: Use zlib's deflateBound plus the container overhead, and BrotliEncoder.GetMaxCompressedLength for Brotli. Extend the contract test beyond 4 KB, or it will keep passing.
Activity
matt-edmondson commentedon Sep 26, 2026
Triage
Compress(byte[])throws for any incompressible payload above about 280 KB. That covers common inputs such as images, archives and ciphertext. It also breaks theGetMaxCompressedLengthcontract that a buffer of that size is always enough.GetMaxCompressedLengthdeflateBoundplus the container overhead, andBrotliEncoder.GetMaxCompressedLengthfor Brotli. Extend the contract test beyond 4 KB, or it will keep passing.Generated by Claude Code