Skip to content

Commit f9aaa81

Browse files
authored
Merge pull request #263 from Cysharp/hadashiA/fix-brotli-infinite-loop
Fix infinite loop in BrotliEncoder
2 parents d1440ca + e0a7ae2 commit f9aaa81

2 files changed

Lines changed: 49 additions & 8 deletions

File tree

src/MemoryPack.Core/Compression/BrotliCompressor.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,17 @@ public void CopyTo<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> memoryPack
206206
var encoder = new BrotliEncoder(quality, window);
207207
try
208208
{
209-
var writtenNotAdvanced = 0;
209+
var bytesWritten = 0;
210210
foreach (var item in bufferWriter)
211211
{
212-
writtenNotAdvanced = CompressCore(ref encoder, item.Span, ref memoryPackWriter, initialLength: null, isFinalBlock: false);
212+
var span = item.Span;
213+
if (span.Length <= 0) continue;
214+
bytesWritten += CompressCore(ref encoder, span, ref memoryPackWriter, initialLength: null, isFinalBlock: false);
213215
}
214216

215217
// call BrotliEncoderOperation.Finish
216-
var finalBlockLength = (writtenNotAdvanced == 0) ? null : (int?)(writtenNotAdvanced + 10);
217-
CompressCore(ref encoder, ReadOnlySpan<byte>.Empty, ref memoryPackWriter, initialLength: finalBlockLength, isFinalBlock: true);
218+
var finalBlockMaxLength = BrotliUtils.BrotliEncoderMaxCompressedSize(bytesWritten) - bytesWritten;
219+
CompressCore(ref encoder, ReadOnlySpan<byte>.Empty, ref memoryPackWriter, initialLength: finalBlockMaxLength, isFinalBlock: true);
218220
}
219221
finally
220222
{
@@ -257,7 +259,7 @@ static int CompressCore<TBufferWriter>(ref BrotliEncoder encoder, ReadOnlySpan<b
257259
where TBufferWriter : class, IBufferWriter<byte>
258260
#endif
259261
{
260-
var writtenNotAdvanced = 0;
262+
var totalWritten = 0;
261263

262264
var lastResult = OperationStatus.DestinationTooSmall;
263265
while (lastResult == OperationStatus.DestinationTooSmall)
@@ -266,21 +268,20 @@ static int CompressCore<TBufferWriter>(ref BrotliEncoder encoder, ReadOnlySpan<b
266268
var dest = MemoryMarshal.CreateSpan(ref spanRef, destBufferWriter.BufferLength);
267269

268270
lastResult = encoder.Compress(source, dest, out int bytesConsumed, out int bytesWritten, isFinalBlock: isFinalBlock);
269-
writtenNotAdvanced += bytesConsumed;
271+
totalWritten += bytesWritten;
270272

271273
if (lastResult == OperationStatus.InvalidData) MemoryPackSerializationException.ThrowCompressionFailed();
272274
if (bytesWritten > 0)
273275
{
274276
destBufferWriter.Advance(bytesWritten);
275-
writtenNotAdvanced = 0;
276277
}
277278
if (bytesConsumed > 0)
278279
{
279280
source = source.Slice(bytesConsumed);
280281
}
281282
}
282283

283-
return writtenNotAdvanced;
284+
return totalWritten;
284285
}
285286

286287
public void Dispose()

tests/MemoryPack.Tests/BrotliTest.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using MemoryPack.Compression;
22
using System;
3+
using System.Buffers;
4+
using System.IO.Compression;
35

46
namespace MemoryPack.Tests;
57

@@ -14,6 +16,44 @@ public void LargeByteArray()
1416
data.MemDecmpDeserialize(bin);
1517
}
1618

19+
[Fact]
20+
public void EncodeEmptyCntent()
21+
{
22+
var buffer = new ArrayBufferWriter<byte>();
23+
using var state = MemoryPackWriterOptionalStatePool.Rent(null);
24+
var writer = new MemoryPackWriter<ArrayBufferWriter<byte>>(ref buffer, state);
25+
26+
using var compressor = new BrotliCompressor(CompressionLevel.Fastest);
27+
compressor.CopyTo(ref writer);
28+
29+
using var decompressor = new BrotliDecompressor();
30+
decompressor.Decompress(compressor.ToArray()).ToArray().Should().BeEmpty();
31+
}
32+
33+
[Fact]
34+
public void EncodeEmptyFinalBlock()
35+
{
36+
using var state = MemoryPackWriterOptionalStatePool.Rent(null);
37+
38+
var compressor = new BrotliCompressor(CompressionLevel.Fastest);
39+
var coWriter = new MemoryPackWriter<BrotliCompressor>(ref compressor, state);
40+
41+
var bytes = new byte[248];
42+
Random.Shared.NextBytes(bytes);
43+
coWriter.WriteUnmanagedArray(bytes);
44+
coWriter.Flush();
45+
46+
var buffer = new ArrayBufferWriter<byte>();
47+
compressor.CopyTo(buffer);
48+
49+
using var readerState = MemoryPackReaderOptionalStatePool.Rent(null);
50+
using var decompressor = new BrotliDecompressor();
51+
var decompressed = decompressor.Decompress(compressor.ToArray());
52+
var reader = new MemoryPackReader(in decompressed, readerState);
53+
54+
reader.ReadArray<byte>().Should().BeEquivalentTo(bytes);
55+
compressor.Dispose();
56+
}
1757
}
1858

1959
[MemoryPackable]

0 commit comments

Comments
 (0)