この記事の目的
C#でBrotliEncoder/BrotliDecoderを使った際に苦戦したので、試行錯誤の結果を自分以外の人に共有したい。
前提条件
- .Net 6を想定。
- エンコードとデコードのInputは
ReadOnlySequence<byte>
、OutputはIBufferWriter<byte>
とする。
サンプルコード
Compress
public static void Compress(ReadOnlySequence<byte> sequence, IBufferWriter<byte> writer)
{
using var encoder = new BrotliEncoder(11, 24);
var reader = new SequenceReader<byte>(sequence);
for (; ; )
{
var status = encoder.Compress(reader.UnreadSpan, writer.GetSpan(), out var consumed, out var written, false);
if (status == OperationStatus.InvalidData) throw new Exception("invalid data");
reader.Advance(consumed);
writer.Advance(written);
if (status == OperationStatus.Done) break;
}
for (; ; )
{
var status = encoder.Compress(ReadOnlySpan<byte>.Empty, writer.GetSpan(), out _, out var written, true);
if (status == OperationStatus.InvalidData) throw new Exception("invalid data");
writer.Advance(written);
if (written == 0) break;
}
}
訂正 (2022/10/08)
neueccさんにご指摘いただき、Compressが正しく処理できていないことが分かりました
status
がOperationStatus.Done
なら圧縮完了だと理解していたのですが、正しくは
The entire input buffer has been processed and the operation is complete.
とドキュメントにも記載されている通り、inputの処理が完了したのみで、flush処理を別途行う必要がありました...
neueccさんありがとうございました
Decompress
public static void Decompress(ReadOnlySequence<byte> sequence, IBufferWriter<byte> writer)
{
var reader = new SequenceReader<byte>(sequence);
using var decoder = new BrotliDecoder();
for (; ; )
{
var status = decoder.Decompress(reader.UnreadSpan, writer.GetSpan(), out var consumed, out var written);
if (status == OperationStatus.InvalidData) throw new Exception("invalid data");
reader.Advance(consumed);
writer.Advance(written);
if (written == 0) break;
}
}
ハマったポイント
-
OperationStatus.DestinationTooSmall
はエラーというよりも、destination
に最大まで書き込んだというお知らせなので、新しいdestination
を指定すればよい。 -
BrotliEncoder.Compressで-> isFinalBlock: trueで最後に処理すればOKでしたisFinalBlock
にfalseを指定した場合、Decompress時にデータの末尾になってもOperationStatus.Done
ではなくOperationStatus.NeedMoreData
が常に返ってくる。