private void WriteCompressedBlockHeader(ReadOnlySpan <byte> input, Span <byte> output, int compressedSize) { var blockSize = compressedSize + 4; // CRC BinaryPrimitives.WriteInt32LittleEndian(output.Slice(1), blockSize); output[0] = (byte)Constants.ChunkType.CompressedData; var crc = Crc32CAlgorithm.Compute(input); crc = Crc32CAlgorithm.ApplyMask(crc); BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(4), crc); }
public unsafe int Decompress(Span <byte> buffer) { Debug.Assert(_decompressor != null); var chunkType = _chunkType; var chunkSize = _chunkSize; var chunkBytesProcessed = _chunkBytesProcessed; fixed(byte *bufferStart = buffer) { var bufferEnd = bufferStart + buffer.Length; var bufferPtr = bufferStart; fixed(byte *inputStart = _input.Span) { var inputEnd = inputStart + _input.Length; var inputPtr = inputStart; while (bufferPtr < bufferEnd && (inputPtr < inputEnd || (chunkType == Constants.ChunkType.CompressedData && _decompressor.AllDataDecompressed))) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (chunkType) { case null: // Not in a chunk, read the chunk type and size var rawChunkHeader = ReadChunkHeader(ref inputPtr, inputEnd); if (rawChunkHeader == 0) { // Not enough data, get some more break; } chunkType = (Constants.ChunkType)(rawChunkHeader & 0xff); chunkSize = unchecked ((int)(rawChunkHeader >> 8)); chunkBytesProcessed = 0; _scratchLength = 0; _chunkCrc = 0; break; case Constants.ChunkType.CompressedData: { if (chunkBytesProcessed < 4) { _decompressor.Reset(); if (!ReadChunkCrc(ref inputPtr, inputEnd, ref chunkBytesProcessed)) { // Incomplete CRC break; } if (inputPtr >= inputEnd) { // No more data break; } } while (bufferPtr < bufferEnd && !_decompressor.EndOfFile) { if (_decompressor.NeedMoreData) { var availableInputBytes = unchecked ((int)(inputEnd - inputPtr)); if (availableInputBytes <= 0) { // No more data to give break; } var availableChunkBytes = Math.Min(availableInputBytes, chunkSize - chunkBytesProcessed); Debug.Assert(availableChunkBytes > 0); _decompressor.Decompress(new ReadOnlySpan <byte>(inputPtr, availableChunkBytes)); chunkBytesProcessed += availableChunkBytes; inputPtr += availableChunkBytes; } var decompressedBytes = _decompressor.Read(new Span <byte>(bufferPtr, unchecked ((int)(bufferEnd - bufferPtr)))); _chunkCrc = Crc32CAlgorithm.Append(_chunkCrc, new ReadOnlySpan <byte>(bufferPtr, decompressedBytes)); bufferPtr += decompressedBytes; } if (_decompressor.EndOfFile) { // Completed reading the chunk chunkType = null; var crc = Crc32CAlgorithm.ApplyMask(_chunkCrc); if (_expectedChunkCrc != crc) { throw new InvalidDataException("Chunk CRC mismatch."); } } break; } case Constants.ChunkType.UncompressedData: { if (chunkBytesProcessed < 4) { if (!ReadChunkCrc(ref inputPtr, inputEnd, ref chunkBytesProcessed)) { // Incomplete CRC break; } if (inputPtr >= inputEnd) { // No more data break; } } var chunkBytes = unchecked (Math.Min(Math.Min((int)(bufferEnd - bufferPtr), (int)(inputEnd - inputPtr)), chunkSize - chunkBytesProcessed)); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, unchecked ((uint)chunkBytes)); _chunkCrc = Crc32CAlgorithm.Append(_chunkCrc, new ReadOnlySpan <byte>(bufferPtr, chunkBytes)); bufferPtr += chunkBytes; inputPtr += chunkBytes; chunkBytesProcessed += chunkBytes; if (chunkBytesProcessed >= chunkSize) { // Completed reading the chunk chunkType = null; var crc = Crc32CAlgorithm.ApplyMask(_chunkCrc); if (_expectedChunkCrc != crc) { throw new InvalidDataException("Chunk CRC mismatch."); } } break; } default: { if (chunkType < Constants.ChunkType.SkippableChunk) { throw new InvalidDataException($"Unknown chunk type {(int) chunkType:x}"); } var chunkBytes = Math.Min(unchecked ((int)(inputEnd - inputPtr)), chunkSize - chunkBytesProcessed); inputPtr += chunkBytes; chunkBytesProcessed += chunkBytes; if (chunkBytesProcessed >= chunkSize) { // Completed reading the chunk chunkType = null; } break; } } } _chunkType = chunkType; _chunkSize = chunkSize; _chunkBytesProcessed = chunkBytesProcessed; _input = _input.Slice(unchecked ((int)(inputPtr - inputStart))); return(unchecked ((int)(bufferPtr - bufferStart))); } } }