diff --git a/src/SharpCompress/Compressors/Xz/XZBlock.Async.cs b/src/SharpCompress/Compressors/Xz/XZBlock.Async.cs index cc3c31fe..0175faaf 100644 --- a/src/SharpCompress/Compressors/Xz/XZBlock.Async.cs +++ b/src/SharpCompress/Compressors/Xz/XZBlock.Async.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Buffers; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common; -using SharpCompress.Compressors.Xz.Filters; namespace SharpCompress.Compressors.Xz; @@ -32,6 +30,7 @@ public sealed partial class XZBlock if (!_endOfStream) { bytesRead = await _decomStream + .NotNull() .ReadAsync(buffer, offset, count, cancellationToken) .ConfigureAwait(false); UpdateCheck(buffer, offset, bytesRead); @@ -60,13 +59,21 @@ public sealed partial class XZBlock var bytes = (BaseStream.Position - _startPosition) % 4; if (bytes > 0) { - var paddingBytes = new byte[4 - bytes]; - await BaseStream - .ReadAsync(paddingBytes, 0, paddingBytes.Length, cancellationToken) - .ConfigureAwait(false); - if (paddingBytes.Any(b => b != 0)) + var size = 4 - (int)bytes; + var paddingBytes = ArrayPool.Shared.Rent(size); + try { - throw new InvalidFormatException("Padding bytes were non-null"); + await BaseStream + .ReadExactAsync(paddingBytes, 0, size, cancellationToken) + .ConfigureAwait(false); + if (paddingBytes.Any(b => b != 0)) + { + throw new InvalidFormatException("Padding bytes were non-null"); + } + } + finally + { + ArrayPool.Shared.Return(paddingBytes); } } _paddingSkipped = true; @@ -74,12 +81,19 @@ public sealed partial class XZBlock private async ValueTask CheckCrcAsync(CancellationToken cancellationToken = default) { - var crc = new byte[_checkSize]; - await BaseStream - .ReadExactAsync(crc, 0, _checkSize, cancellationToken) - .ConfigureAwait(false); - VerifyCheck(crc); - _crcChecked = true; + var crc = ArrayPool.Shared.Rent(_checkSize); + try + { + await BaseStream + .ReadExactAsync(crc, 0, _checkSize, cancellationToken) + .ConfigureAwait(false); + VerifyCheck(crc); + _crcChecked = true; + } + finally + { + ArrayPool.Shared.Return(crc); + } } private async ValueTask LoadHeaderAsync(CancellationToken cancellationToken = default) @@ -99,12 +113,19 @@ public sealed partial class XZBlock private async ValueTask ReadHeaderSizeAsync(CancellationToken cancellationToken = default) { - var buffer = new byte[1]; - await BaseStream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); - _blockHeaderSizeByte = buffer[0]; - if (_blockHeaderSizeByte == 0) + var buffer = ArrayPool.Shared.Rent(1); + try { - throw new XZIndexMarkerReachedException(); + await BaseStream.ReadExactAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); + _blockHeaderSizeByte = buffer[0]; + if (_blockHeaderSizeByte == 0) + { + throw new XZIndexMarkerReachedException(); + } + } + finally + { + ArrayPool.Shared.Return(buffer); } } diff --git a/src/SharpCompress/Compressors/Xz/XZBlock.cs b/src/SharpCompress/Compressors/Xz/XZBlock.cs index 778f33b5..4221e3c4 100644 --- a/src/SharpCompress/Compressors/Xz/XZBlock.cs +++ b/src/SharpCompress/Compressors/Xz/XZBlock.cs @@ -1,13 +1,10 @@ -#nullable disable - using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Compressors.Xz.Filters; @@ -16,20 +13,20 @@ namespace SharpCompress.Compressors.Xz; [CLSCompliant(false)] public sealed partial class XZBlock : XZReadOnlyStream { - public int BlockHeaderSize => (_blockHeaderSizeByte + 1) * 4; + private int BlockHeaderSize => (_blockHeaderSizeByte + 1) * 4; public ulong? CompressedSize { get; private set; } public ulong? UncompressedSize { get; private set; } - public Stack Filters { get; private set; } = new(); - public bool HeaderIsLoaded { get; private set; } + private readonly Stack _filters = new(); + private bool HeaderIsLoaded { get; set; } private readonly CheckType _checkType; private readonly int _checkSize; private uint _crc32 = Crc32.DefaultSeed; private ulong _crc64 = Crc64.DefaultSeed; - private readonly SHA256 _sha256; + private readonly SHA256? _sha256; private bool _streamConnected; private int _numFilters; private byte _blockHeaderSizeByte; - private Stream _decomStream; + private Stream? _decomStream; private bool _endOfStream; private bool _paddingSkipped; private bool _crcChecked; @@ -62,7 +59,7 @@ public sealed partial class XZBlock : XZReadOnlyStream if (!_endOfStream) { - bytesRead = _decomStream.Read(buffer, offset, count); + bytesRead = _decomStream.NotNull().Read(buffer, offset, count); UpdateCheck(buffer, offset, bytesRead); } @@ -125,54 +122,80 @@ public sealed partial class XZBlock : XZReadOnlyStream _crc64 = Crc64.CalculateHash(_crc64, Crc64.Table, bytes); break; case CheckType.SHA256: - _sha256.TransformBlock(buffer, offset, count, null, 0); + _sha256.NotNull().TransformBlock(buffer, offset, count, null, 0); break; } } private void VerifyCheck(byte[] expected) { - var actual = _checkType switch + switch (_checkType) { - CheckType.NONE => Array.Empty(), - CheckType.CRC32 => GetLittleEndianBytes(~_crc32), - CheckType.CRC64 => GetLittleEndianBytes(_crc64), - CheckType.SHA256 => FinalizeSha256Check(), - _ => throw new InvalidFormatException("Unsupported XZ check type"), - }; + case CheckType.NONE: + break; + case CheckType.CRC32: + GetLittleEndianBytes(~_crc32, expected); + break; + case CheckType.CRC64: + GetLittleEndianBytes(~_crc64, expected); + break; + case CheckType.SHA256: + FinalizeSha256Check(expected); + break; + default: + throw new InvalidFormatException("Unsupported XZ check type"); + } + } - if (!expected.SequenceEqual(actual)) + private static void GetLittleEndianBytes(uint value, byte[] expected) + { + var bytes = ArrayPool.Shared.Rent(sizeof(uint)); + try + { + BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); + if (!expected.SequenceEqual(bytes.AsSpan().Slice(0, sizeof(uint)))) + { + throw new InvalidFormatException("Block check corrupt"); + } + } + finally + { + ArrayPool.Shared.Return(bytes); + } + } + + private static void GetLittleEndianBytes(ulong value, byte[] expected) + { + var bytes = ArrayPool.Shared.Rent(sizeof(ulong)); + try + { + BinaryPrimitives.WriteUInt64LittleEndian(bytes, value); + if (!expected.SequenceEqual(bytes.AsSpan().Slice(0, sizeof(ulong)))) + { + throw new InvalidFormatException("Block check corrupt"); + } + } + finally + { + ArrayPool.Shared.Return(bytes); + } + } + + private void FinalizeSha256Check(byte[] expected) + { + _sha256.NotNull().TransformFinalBlock(Array.Empty(), 0, 0); + if (!expected.SequenceEqual(_sha256.NotNull().Hash)) { throw new InvalidFormatException("Block check corrupt"); } } - private static byte[] GetLittleEndianBytes(uint value) - { - var bytes = new byte[sizeof(uint)]; - BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); - return bytes; - } - - private static byte[] GetLittleEndianBytes(ulong value) - { - var bytes = new byte[sizeof(ulong)]; - BinaryPrimitives.WriteUInt64LittleEndian(bytes, value); - return bytes; - } - - private byte[] FinalizeSha256Check() - { - _sha256.TransformFinalBlock(Array.Empty(), 0, 0); - return _sha256.Hash; - } - private void ConnectStream() { _decomStream = BaseStream; - while (Filters.Any()) + while (_filters.Any()) { - var filter = Filters.Pop(); + var filter = _filters.Pop(); filter.SetBaseStream(_decomStream); _decomStream = filter; } @@ -270,7 +293,7 @@ public sealed partial class XZBlock : XZReadOnlyStream } filter.ValidateFilter(); - Filters.Push(filter); + _filters.Push(filter); } if (nonLastSizeChangers > 2) {