diff --git a/src/libraries/Common/src/Interop/Interop.Brotli.cs b/src/libraries/Common/src/Interop/Interop.Brotli.cs index cc49e4b323a53..31d6a94d6fdde 100644 --- a/src/libraries/Common/src/Interop/Interop.Brotli.cs +++ b/src/libraries/Common/src/Interop/Interop.Brotli.cs @@ -41,6 +41,9 @@ internal static unsafe partial BOOL BrotliEncoderCompressStream( [LibraryImport(Libraries.CompressionNative)] internal static partial BOOL BrotliEncoderHasMoreOutput(SafeBrotliEncoderHandle state); + [LibraryImport(Libraries.CompressionNative)] + internal static partial nuint BrotliEncoderMaxCompressedSize(nuint inputSize); + [LibraryImport(Libraries.CompressionNative)] internal static partial void BrotliEncoderDestroyInstance(IntPtr state); diff --git a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliUtils.cs b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliUtils.cs index 2e8e55a570ae3..3ddf4d2a28826 100644 --- a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliUtils.cs +++ b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/BrotliUtils.cs @@ -11,7 +11,7 @@ internal static partial class BrotliUtils public const int Quality_Min = 0; public const int Quality_Default = 4; public const int Quality_Max = 11; - public const int MaxInputSize = int.MaxValue - 515; // 515 is the max compressed extra bytes + public const int MaxInputSize = int.MaxValue - 524_166; // 524_166 is the max compressed extra bytes internal static int GetQualityFromCompressionLevel(CompressionLevel compressionLevel) => compressionLevel switch diff --git a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliEncoder.cs b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliEncoder.cs index eb25871c69e04..3ad7309aac7ea 100644 --- a/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliEncoder.cs +++ b/src/libraries/System.IO.Compression.Brotli/src/System/IO/Compression/enc/BrotliEncoder.cs @@ -103,26 +103,16 @@ internal void SetWindow(int window) } /// Gets the maximum expected compressed length for the provided input size. - /// The input size to get the maximum expected compressed length from. Must be greater or equal than 0 and less or equal than - 515. + /// The input size to get the maximum expected compressed length from. Must be greater or equal than 0 and less or equal than - 524166. /// A number representing the maximum compressed length for the provided input size. - /// Returns 1 if is 0. - /// is less than 0, the minimum allowed input size, or greater than - 515, the maximum allowed input size. + /// Returns 2 if is 0. + /// is less than 0, the minimum allowed input size, or greater than - 524166, the maximum allowed input size. public static int GetMaxCompressedLength(int inputSize) { ArgumentOutOfRangeException.ThrowIfNegative(inputSize); ArgumentOutOfRangeException.ThrowIfGreaterThan(inputSize, BrotliUtils.MaxInputSize); - if (inputSize == 0) - { - return 1; - } - - int numLargeBlocks = inputSize >> 24; - int tail = inputSize & 0xFFFFFF; - int tailOverhead = (tail > (1 << 20)) ? 4 : 3; - int overhead = 2 + (4 * numLargeBlocks) + tailOverhead + 1; - int result = inputSize + overhead; - return result; + return (int)Interop.Brotli.BrotliEncoderMaxCompressedSize((nuint)inputSize); } internal OperationStatus Flush(Memory destination, out int bytesWritten) => Flush(destination.Span, out bytesWritten); diff --git a/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs b/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs index e3a7f2b830644..5b992cfcb246a 100644 --- a/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs +++ b/src/libraries/System.IO.Compression.Brotli/tests/CompressionStreamUnitTests.Brotli.cs @@ -78,9 +78,23 @@ public void InvalidWindow() public void GetMaxCompressedSize_Basic() { Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(-1)); - Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(2147483133)); - Assert.InRange(BrotliEncoder.GetMaxCompressedLength(2147483132), 0, int.MaxValue); - Assert.Equal(1, BrotliEncoder.GetMaxCompressedLength(0)); + Assert.Throws("inputSize", () => BrotliEncoder.GetMaxCompressedLength(2_146_959_482)); + Assert.InRange(BrotliEncoder.GetMaxCompressedLength(2_146_959_481), 0, int.MaxValue); // 2_146_959_481 produces int.MaxValue + Assert.Equal(2, BrotliEncoder.GetMaxCompressedLength(0)); + } + + [Fact] + public void DestinationBufferWithSizeEqualToMaxCompressedLength_ShouldAlwaysSucceed() + { + byte[] source = new byte[256000]; + var rng = new Random(1234); + rng.NextBytes(source); + + int maxLength = BrotliEncoder.GetMaxCompressedLength(source.Length); + var resultBuffer = new byte[maxLength]; + + Assert.True(BrotliEncoder.TryCompress(source, resultBuffer, out int bytesWritten, quality: 5, window: 10)); + Assert.True(maxLength >= bytesWritten); } [Fact] diff --git a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def index 6821d0e538f51..05da5501d8582 100644 --- a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def +++ b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native.def @@ -11,6 +11,7 @@ EXPORTS BrotliEncoderCreateInstance BrotliEncoderDestroyInstance BrotliEncoderHasMoreOutput + BrotliEncoderMaxCompressedSize BrotliEncoderSetParameter CompressionNative_Crc32 CompressionNative_Deflate diff --git a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src index 08dd1700a52f2..0205afdf9a95d 100644 --- a/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src +++ b/src/native/libs/System.IO.Compression.Native/System.IO.Compression.Native_unixexports.src @@ -11,6 +11,7 @@ BrotliEncoderCompressStream BrotliEncoderCreateInstance BrotliEncoderDestroyInstance BrotliEncoderHasMoreOutput +BrotliEncoderMaxCompressedSize BrotliEncoderSetParameter CompressionNative_Crc32 CompressionNative_Deflate diff --git a/src/native/libs/System.IO.Compression.Native/entrypoints.c b/src/native/libs/System.IO.Compression.Native/entrypoints.c index 05a8c228eb785..2f33eb6fb228b 100644 --- a/src/native/libs/System.IO.Compression.Native/entrypoints.c +++ b/src/native/libs/System.IO.Compression.Native/entrypoints.c @@ -22,6 +22,7 @@ static const Entry s_compressionNative[] = DllImportEntry(BrotliEncoderCreateInstance) DllImportEntry(BrotliEncoderDestroyInstance) DllImportEntry(BrotliEncoderHasMoreOutput) + DllImportEntry(BrotliEncoderMaxCompressedSize) DllImportEntry(BrotliEncoderSetParameter) DllImportEntry(CompressionNative_Crc32) DllImportEntry(CompressionNative_Deflate)