diff --git a/Directory.Packages.props b/Directory.Packages.props index 9a51cc353..ced784452 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,6 +14,7 @@ + diff --git a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs index a7a8583d2..199f277b5 100644 --- a/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/AesDecoderStream.cs @@ -2,6 +2,8 @@ using System.IO; using System.Security.Cryptography; using System.Text; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Compressors.LZMA.Utilites; using SharpCompress.IO; @@ -283,5 +285,70 @@ private int HandleUnderflow(byte[] buffer, int offset, int count) return count; } + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken = default + ) + { + if (count == 0 || mWritten == mLimit) + { + return 0; + } + + if (mUnderflow > 0) + { + return HandleUnderflow(buffer, offset, count); + } + + // Need at least 16 bytes to proceed. + if (mEnding - mOffset < 16) + { + Buffer.BlockCopy(mBuffer, mOffset, mBuffer, 0, mEnding - mOffset); + mEnding -= mOffset; + mOffset = 0; + + do + { + cancellationToken.ThrowIfCancellationRequested(); + var read = await mStream + .ReadAsync(mBuffer, mEnding, mBuffer.Length - mEnding, cancellationToken) + .ConfigureAwait(false); + if (read == 0) + { + // We are not done decoding and have less than 16 bytes. + throw new EndOfStreamException(); + } + + mEnding += read; + } while (mEnding - mOffset < 16); + } + + // We shouldn't return more data than we are limited to. + if (count > mLimit - mWritten) + { + count = (int)(mLimit - mWritten); + } + + // We cannot transform less than 16 bytes into the target buffer, + // but we also cannot return zero, so we need to handle this. + if (count < 16) + { + return HandleUnderflow(buffer, offset, count); + } + + if (count > mEnding - mOffset) + { + count = mEnding - mOffset; + } + + // Otherwise we transform directly into the target buffer. + var processed = mDecoder.TransformBlock(mBuffer, mOffset, count & ~15, buffer, offset); + mOffset += processed; + mWritten += processed; + return processed; + } + #endregion } diff --git a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs index de2d284f8..05fa8b9ae 100644 --- a/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Bcj2DecoderStream.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA; @@ -191,6 +193,18 @@ public override int Read(byte[] buffer, int offset, int count) return count; } + public override Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + // Bcj2DecoderStream uses complex state machine with multiple streams + return Task.FromResult(Read(buffer, offset, count)); + } + public override int ReadByte() { if (_mFinished) diff --git a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs index 6b9c525f8..c65cc6834 100644 --- a/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs +++ b/src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs @@ -3,6 +3,8 @@ using System; using System.Buffers; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace SharpCompress.Compressors.LZMA.LZ; @@ -85,6 +87,12 @@ public void ReleaseStream() _stream = null; } + public async Task ReleaseStreamAsync(CancellationToken cancellationToken = default) + { + await FlushAsync(cancellationToken).ConfigureAwait(false); + _stream = null; + } + private void Flush() { if (_stream is null) @@ -104,6 +112,27 @@ private void Flush() _streamPos = _pos; } + private async Task FlushAsync(CancellationToken cancellationToken = default) + { + if (_stream is null) + { + return; + } + var size = _pos - _streamPos; + if (size == 0) + { + return; + } + await _stream + .WriteAsync(_buffer, _streamPos, size, cancellationToken) + .ConfigureAwait(false); + if (_pos >= _windowSize) + { + _pos = 0; + } + _streamPos = _pos; + } + public void CopyPending() { if (_pendingLen < 1) @@ -124,6 +153,26 @@ public void CopyPending() _pendingLen = rem; } + public async Task CopyPendingAsync(CancellationToken cancellationToken = default) + { + if (_pendingLen < 1) + { + return; + } + var rem = _pendingLen; + var pos = (_pendingDist < _pos ? _pos : _pos + _windowSize) - _pendingDist - 1; + while (rem > 0 && HasSpace) + { + if (pos >= _windowSize) + { + pos = 0; + } + await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false); + rem--; + } + _pendingLen = rem; + } + public void CopyBlock(int distance, int len) { var rem = len; @@ -157,6 +206,43 @@ public void CopyBlock(int distance, int len) _pendingDist = distance; } + public async Task CopyBlockAsync( + int distance, + int len, + CancellationToken cancellationToken = default + ) + { + var rem = len; + var pos = (distance < _pos ? _pos : _pos + _windowSize) - distance - 1; + var targetSize = HasSpace ? (int)Math.Min(rem, _limit - _total) : 0; + var sizeUntilWindowEnd = Math.Min(_windowSize - _pos, _windowSize - pos); + var sizeUntilOverlap = Math.Abs(pos - _pos); + var fastSize = Math.Min(Math.Min(sizeUntilWindowEnd, sizeUntilOverlap), targetSize); + if (fastSize >= 2) + { + _buffer.AsSpan(pos, fastSize).CopyTo(_buffer.AsSpan(_pos, fastSize)); + _pos += fastSize; + pos += fastSize; + _total += fastSize; + if (_pos >= _windowSize) + { + await FlushAsync(cancellationToken).ConfigureAwait(false); + } + rem -= fastSize; + } + while (rem > 0 && HasSpace) + { + if (pos >= _windowSize) + { + pos = 0; + } + await PutByteAsync(_buffer[pos++], cancellationToken).ConfigureAwait(false); + rem--; + } + _pendingLen = rem; + _pendingDist = distance; + } + public void PutByte(byte b) { _buffer[_pos++] = b; @@ -167,6 +253,16 @@ public void PutByte(byte b) } } + public async Task PutByteAsync(byte b, CancellationToken cancellationToken = default) + { + _buffer[_pos++] = b; + _total++; + if (_pos >= _windowSize) + { + await FlushAsync(cancellationToken).ConfigureAwait(false); + } + } + public byte GetByte(int distance) { var pos = _pos - distance - 1; @@ -207,6 +303,44 @@ public int CopyStream(Stream stream, int len) return len - size; } + public async Task CopyStreamAsync( + Stream stream, + int len, + CancellationToken cancellationToken = default + ) + { + var size = len; + while (size > 0 && _pos < _windowSize && _total < _limit) + { + cancellationToken.ThrowIfCancellationRequested(); + + var curSize = _windowSize - _pos; + if (curSize > _limit - _total) + { + curSize = (int)(_limit - _total); + } + if (curSize > size) + { + curSize = size; + } + var numReadBytes = await stream + .ReadAsync(_buffer, _pos, curSize, cancellationToken) + .ConfigureAwait(false); + if (numReadBytes == 0) + { + throw new DataErrorException(); + } + size -= numReadBytes; + _pos += numReadBytes; + _total += numReadBytes; + if (_pos >= _windowSize) + { + await FlushAsync(cancellationToken).ConfigureAwait(false); + } + } + return len - size; + } + public void SetLimit(long size) => _limit = _total + size; public bool HasSpace => _pos < _windowSize && _total < _limit; diff --git a/src/SharpCompress/Compressors/LZMA/LZipStream.cs b/src/SharpCompress/Compressors/LZMA/LZipStream.cs index 26d34d3a1..e7059003e 100644 --- a/src/SharpCompress/Compressors/LZMA/LZipStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LZipStream.cs @@ -1,6 +1,8 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Common; using SharpCompress.Crypto; using SharpCompress.IO; @@ -157,6 +159,11 @@ public override int Read(byte[] buffer, int offset, int count) => #if !NETFRAMEWORK && !NETSTANDARD2_0 + public override ValueTask ReadAsync( + Memory buffer, + CancellationToken cancellationToken = default + ) => _stream.ReadAsync(buffer, cancellationToken); + public override int Read(Span buffer) => _stream.Read(buffer); public override void Write(ReadOnlySpan buffer) @@ -179,6 +186,25 @@ public override void WriteByte(byte value) ++_writeCount; } + public override Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken = default + ) => _stream.ReadAsync(buffer, offset, count, cancellationToken); + + public override async Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + await _stream.WriteAsync(buffer, offset, count, cancellationToken); + _writeCount += count; + } + #endregion /// diff --git a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs index 4baa9061b..fed40533d 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs @@ -1,6 +1,7 @@ #nullable disable using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using SharpCompress.Compressors.LZMA.LZ; using SharpCompress.Compressors.LZMA.RangeCoder; @@ -199,6 +200,9 @@ public Decoder() } } +#if !NETFRAMEWORK && !NETSTANDARD2_0 + [MemberNotNull(nameof(_outWindow))] +#endif private void CreateDictionary() { if (_dictionarySize < 0) @@ -309,6 +313,42 @@ ICodeProgress progress _outWindow = null; } + public async System.Threading.Tasks.Task CodeAsync( + Stream inStream, + Stream outStream, + long inSize, + long outSize, + ICodeProgress progress, + System.Threading.CancellationToken cancellationToken = default + ) + { + if (_outWindow is null) + { + CreateDictionary(); + } + _outWindow.Init(outStream); + if (outSize > 0) + { + _outWindow.SetLimit(outSize); + } + else + { + _outWindow.SetLimit(long.MaxValue - _outWindow.Total); + } + + var rangeDecoder = new RangeCoder.Decoder(); + rangeDecoder.Init(inStream); + + await CodeAsync(_dictionarySize, _outWindow, rangeDecoder, cancellationToken) + .ConfigureAwait(false); + + await _outWindow.ReleaseStreamAsync(cancellationToken).ConfigureAwait(false); + rangeDecoder.ReleaseStream(); + + _outWindow.Dispose(); + _outWindow = null; + } + internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder) { var dictionarySizeCheck = Math.Max(dictionarySize, 1); @@ -435,6 +475,143 @@ internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder r return false; } + internal async System.Threading.Tasks.Task CodeAsync( + int dictionarySize, + OutWindow outWindow, + RangeCoder.Decoder rangeDecoder, + System.Threading.CancellationToken cancellationToken = default + ) + { + var dictionarySizeCheck = Math.Max(dictionarySize, 1); + + await outWindow.CopyPendingAsync(cancellationToken).ConfigureAwait(false); + + while (outWindow.HasSpace) + { + cancellationToken.ThrowIfCancellationRequested(); + + var posState = (uint)outWindow.Total & _posStateMask; + if ( + _isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState] + .Decode(rangeDecoder) == 0 + ) + { + byte b; + var prevByte = outWindow.GetByte(0); + if (!_state.IsCharState()) + { + b = _literalDecoder.DecodeWithMatchByte( + rangeDecoder, + (uint)outWindow.Total, + prevByte, + outWindow.GetByte((int)_rep0) + ); + } + else + { + b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow.Total, prevByte); + } + await outWindow.PutByteAsync(b, cancellationToken).ConfigureAwait(false); + _state.UpdateChar(); + } + else + { + uint len; + if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1) + { + if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0) + { + if ( + _isRep0LongDecoders[ + (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState + ] + .Decode(rangeDecoder) == 0 + ) + { + _state.UpdateShortRep(); + await outWindow + .PutByteAsync(outWindow.GetByte((int)_rep0), cancellationToken) + .ConfigureAwait(false); + continue; + } + } + else + { + uint distance; + if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0) + { + distance = _rep1; + } + else + { + if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0) + { + distance = _rep2; + } + else + { + distance = _rep3; + _rep3 = _rep2; + } + _rep2 = _rep1; + } + _rep1 = _rep0; + _rep0 = distance; + } + len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN; + _state.UpdateRep(); + } + else + { + _rep3 = _rep2; + _rep2 = _rep1; + _rep1 = _rep0; + len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState); + _state.UpdateMatch(); + var posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder); + if (posSlot >= Base.K_START_POS_MODEL_INDEX) + { + var numDirectBits = (int)((posSlot >> 1) - 1); + _rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < Base.K_END_POS_MODEL_INDEX) + { + _rep0 += BitTreeDecoder.ReverseDecode( + _posDecoders, + _rep0 - posSlot - 1, + rangeDecoder, + numDirectBits + ); + } + else + { + _rep0 += ( + rangeDecoder.DecodeDirectBits(numDirectBits - Base.K_NUM_ALIGN_BITS) + << Base.K_NUM_ALIGN_BITS + ); + _rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder); + } + } + else + { + _rep0 = posSlot; + } + } + if (_rep0 >= outWindow.Total || _rep0 >= dictionarySizeCheck) + { + if (_rep0 == 0xFFFFFFFF) + { + return true; + } + throw new DataErrorException(); + } + await outWindow + .CopyBlockAsync((int)_rep0, (int)len, cancellationToken) + .ConfigureAwait(false); + } + } + return false; + } + public void SetDecoderProperties(byte[] properties) { if (properties.Length < 1) @@ -470,29 +647,4 @@ public void Train(Stream stream) } _outWindow.Train(stream); } - - /* - public override bool CanRead { get { return true; }} - public override bool CanWrite { get { return true; }} - public override bool CanSeek { get { return true; }} - public override long Length { get { return 0; }} - public override long Position - { - get { return 0; } - set { } - } - public override void Flush() { } - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - public override void Write(byte[] buffer, int offset, int count) - { - } - public override long Seek(long offset, System.IO.SeekOrigin origin) - { - return 0; - } - public override void SetLength(long value) {} - */ } diff --git a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs index 4d80704e7..eaef5fd3b 100644 --- a/src/SharpCompress/Compressors/LZMA/LzmaStream.cs +++ b/src/SharpCompress/Compressors/LZMA/LzmaStream.cs @@ -3,6 +3,8 @@ using System; using System.Buffers.Binary; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.Compressors.LZMA.LZ; using SharpCompress.IO; @@ -423,6 +425,82 @@ private void DecodeChunkHeader() } } + private async Task DecodeChunkHeaderAsync(CancellationToken cancellationToken = default) + { + var controlBuffer = new byte[1]; + await _inputStream.ReadAsync(controlBuffer, 0, 1, cancellationToken).ConfigureAwait(false); + var control = controlBuffer[0]; + _inputPosition++; + + if (control == 0x00) + { + _endReached = true; + return; + } + + if (control >= 0xE0 || control == 0x01) + { + _needProps = true; + _needDictReset = false; + _outWindow.Reset(); + } + else if (_needDictReset) + { + throw new DataErrorException(); + } + + if (control >= 0x80) + { + _uncompressedChunk = false; + + _availableBytes = (control & 0x1F) << 16; + var buffer = new byte[2]; + await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false); + _availableBytes += (buffer[0] << 8) + buffer[1] + 1; + _inputPosition += 2; + + await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false); + _rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1; + _inputPosition += 2; + + if (control >= 0xC0) + { + _needProps = false; + await _inputStream + .ReadAsync(controlBuffer, 0, 1, cancellationToken) + .ConfigureAwait(false); + Properties[0] = controlBuffer[0]; + _inputPosition++; + + _decoder = new Decoder(); + _decoder.SetDecoderProperties(Properties); + } + else if (_needProps) + { + throw new DataErrorException(); + } + else if (control >= 0xA0) + { + _decoder = new Decoder(); + _decoder.SetDecoderProperties(Properties); + } + + _rangeDecoder.Init(_inputStream); + } + else if (control > 0x02) + { + throw new DataErrorException(); + } + else + { + _uncompressedChunk = true; + var buffer = new byte[2]; + await _inputStream.ReadAsync(buffer, 0, 2, cancellationToken).ConfigureAwait(false); + _availableBytes = (buffer[0] << 8) + buffer[1] + 1; + _inputPosition += 2; + } + } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); @@ -435,5 +513,128 @@ public override void Write(byte[] buffer, int offset, int count) } } + public override async Task ReadAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + if (_endReached) + { + return 0; + } + + var total = 0; + while (total < count) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_availableBytes == 0) + { + if (_isLzma2) + { + await DecodeChunkHeaderAsync(cancellationToken).ConfigureAwait(false); + } + else + { + _endReached = true; + } + if (_endReached) + { + break; + } + } + + var toProcess = count - total; + if (toProcess > _availableBytes) + { + toProcess = (int)_availableBytes; + } + + _outWindow.SetLimit(toProcess); + if (_uncompressedChunk) + { + _inputPosition += await _outWindow + .CopyStreamAsync(_inputStream, toProcess, cancellationToken) + .ConfigureAwait(false); + } + else if ( + await _decoder + .CodeAsync(_dictionarySize, _outWindow, _rangeDecoder, cancellationToken) + .ConfigureAwait(false) + && _outputSize < 0 + ) + { + _availableBytes = _outWindow.AvailableBytes; + } + + var read = _outWindow.Read(buffer, offset, toProcess); + total += read; + offset += read; + _position += read; + _availableBytes -= read; + + if (_availableBytes == 0 && !_uncompressedChunk) + { + if ( + !_rangeDecoder.IsFinished + || (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit) + ) + { + _outWindow.SetLimit(toProcess + 1); + if ( + !await _decoder + .CodeAsync( + _dictionarySize, + _outWindow, + _rangeDecoder, + cancellationToken + ) + .ConfigureAwait(false) + ) + { + _rangeDecoder.ReleaseStream(); + throw new DataErrorException(); + } + } + + _rangeDecoder.ReleaseStream(); + + _inputPosition += _rangeDecoder._total; + if (_outWindow.HasPending) + { + throw new DataErrorException(); + } + } + } + + if (_endReached) + { + if (_inputSize >= 0 && _inputPosition != _inputSize) + { + throw new DataErrorException(); + } + if (_outputSize >= 0 && _position != _outputSize) + { + throw new DataErrorException(); + } + } + + return total; + } + + public override Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(buffer, offset, count); + return Task.CompletedTask; + } + public byte[] Properties { get; } = new byte[5]; } diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs index 6b3797921..015ff7447 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcBuilderStream.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Threading; +using System.Threading.Tasks; using SharpCompress.IO; namespace SharpCompress.Compressors.LZMA.Utilites; @@ -101,4 +103,22 @@ public override void Write(byte[] buffer, int offset, int count) _mCrc = Crc.Update(_mCrc, buffer, offset, count); _mTarget.Write(buffer, offset, count); } + + public override async Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + if (_mFinished) + { + throw new InvalidOperationException("CRC calculation has been finished."); + } + + Processed += count; + _mCrc = Crc.Update(_mCrc, buffer, offset, count); + await _mTarget.WriteAsync(buffer, offset, count, cancellationToken); + } } diff --git a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs index c10efda1e..c90ebfda3 100644 --- a/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs +++ b/src/SharpCompress/Compressors/LZMA/Utilites/CrcCheckStream.cs @@ -1,6 +1,9 @@ using System; +using System.Buffers; using System.Diagnostics; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace SharpCompress.Compressors.LZMA.Utilites; @@ -11,7 +14,7 @@ public class CrcCheckStream : Stream private uint _mCurrentCrc; private bool _mClosed; - private readonly long[] _mBytes = new long[256]; + private readonly long[] _mBytes = ArrayPool.Shared.Rent(256); private long _mLength; public CrcCheckStream(uint crc) @@ -65,6 +68,7 @@ protected override void Dispose(bool disposing) finally { base.Dispose(disposing); + ArrayPool.Shared.Return(_mBytes); } } @@ -101,4 +105,16 @@ public override void Write(byte[] buffer, int offset, int count) _mCurrentCrc = Crc.Update(_mCurrentCrc, buffer, offset, count); } + + public override Task WriteAsync( + byte[] buffer, + int offset, + int count, + CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + Write(buffer, offset, count); + return Task.CompletedTask; + } } diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 7c8294c5b..97a31080c 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -38,6 +38,9 @@ + + + diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index b85a38f73..3d0d443d6 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -335,9 +335,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.20, )", - "resolved": "8.0.20", - "contentHash": "Rhcto2AjGvTO62+/VTmBpumBOmqIGp7nYEbTbmEXkCq4yPGxV8whju3/HsIA/bKyo2+DggaYk5+/8sxb1AbPTw==" + "requested": "[8.0.21, )", + "resolved": "8.0.21", + "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs new file mode 100644 index 000000000..9f19401d7 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs @@ -0,0 +1,606 @@ +using System; +using System.Buffers; +using System.IO; +using System.Threading.Tasks; +using SharpCompress.Compressors.LZMA; +using Xunit; + +namespace SharpCompress.Test.Streams; + +public class LzmaStreamAsyncTests +{ + [Fact] + public async Task TestLzma2Decompress1ByteAsync() + { + var properties = new byte[] { 0x01 }; + var compressedData = new byte[] { 0x01, 0x00, 0x00, 0x58, 0x00 }; + var lzma2Stream = new MemoryStream(compressedData); + + var decompressor = new LzmaStream(properties, lzma2Stream, 5, 1); + var buffer = new byte[1]; + var bytesRead = await decompressor.ReadAsync(buffer, 0, 1).ConfigureAwait(false); + Assert.Equal(1, bytesRead); + Assert.Equal((byte)'X', buffer[0]); + } + + private static byte[] LzmaData { get; } = + [ + 0x5D, + 0x00, + 0x20, + 0x00, + 0x00, + 0x48, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x24, + 0x18, + 0x2F, + 0xEB, + 0x20, + 0x78, + 0xBA, + 0x78, + 0x70, + 0xDC, + 0x43, + 0x2C, + 0x32, + 0xC9, + 0xC3, + 0x97, + 0x4D, + 0x10, + 0x74, + 0xE2, + 0x20, + 0xBF, + 0x5A, + 0xB4, + 0xB3, + 0xC4, + 0x31, + 0x80, + 0x26, + 0x3E, + 0x6A, + 0xEA, + 0x51, + 0xFC, + 0xE4, + 0x8D, + 0x54, + 0x96, + 0x05, + 0xCC, + 0x78, + 0x59, + 0xAC, + 0xD4, + 0x21, + 0x65, + 0x8F, + 0xA9, + 0xC8, + 0x0D, + 0x9B, + 0xE2, + 0xC2, + 0xF9, + 0x7C, + 0x3C, + 0xDD, + 0x4D, + 0x38, + 0x04, + 0x0B, + 0xF8, + 0x0B, + 0x68, + 0xA5, + 0x93, + 0x6C, + 0x64, + 0xAC, + 0xCF, + 0x71, + 0x68, + 0xE8, + 0x69, + 0x25, + 0xC6, + 0x17, + 0x28, + 0xF1, + 0x7C, + 0xF1, + 0xDC, + 0x47, + 0x51, + 0x4D, + 0x1E, + 0x0E, + 0x0B, + 0x80, + 0x37, + 0x24, + 0x58, + 0x80, + 0xF7, + 0xB4, + 0xAC, + 0x54, + 0xF1, + 0x0F, + 0x7F, + 0x0F, + 0x0F, + 0xF5, + 0x9C, + 0xDE, + 0x54, + 0x4F, + 0xA3, + 0x7B, + 0x20, + 0xC5, + 0xA8, + 0x18, + 0x3B, + 0xED, + 0xDC, + 0x04, + 0xF6, + 0xFB, + 0x86, + 0xE0, + 0xAB, + 0xB6, + 0x87, + 0x99, + 0x92, + 0x43, + 0x7B, + 0x2C, + 0xCC, + 0x31, + 0x83, + 0x90, + 0xFF, + 0xF1, + 0x76, + 0x03, + 0x90, + ]; + + /// + /// The decoded data for . + /// + private static byte[] LzmaResultData { get; } = + [ + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFA, + 0x61, + 0x18, + 0x5F, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3D, + 0x61, + 0xE5, + 0x5E, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xE2, + 0x61, + 0x18, + 0x5F, + 0x04, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x29, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x14, + 0x62, + 0x18, + 0x5F, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x09, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x61, + 0xE5, + 0x5E, + 0x05, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x00, + 0x00, + 0x00, + 0xCB, + 0x15, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, + 0x61, + 0xE5, + 0x5E, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x00, + 0x00, + 0x00, + 0xCB, + 0x15, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3D, + 0x61, + 0xE5, + 0x5E, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x12, + 0x00, + 0x00, + 0x00, + 0x02, + 0x00, + 0xB4, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x96, + 0x40, + 0x5C, + 0x08, + 0x00, + 0x00, + 0x00, + 0x60, + 0x00, + 0x00, + 0x00, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x83, + 0x12, + 0x00, + 0xD4, + 0x99, + 0x00, + 0x00, + 0x43, + 0x95, + 0x00, + 0x00, + 0xEB, + 0x7A, + 0x00, + 0x00, + 0x40, + 0x6F, + 0x00, + 0x00, + 0xD2, + 0x6F, + 0x00, + 0x00, + 0x67, + 0x74, + 0x00, + 0x00, + 0x02, + 0x69, + 0x00, + 0x00, + 0x76, + 0x79, + 0x00, + 0x00, + 0x98, + 0x66, + 0x00, + 0x00, + 0x23, + 0x25, + 0x00, + 0x00, + 0x01, + 0x00, + 0xFD, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x3B, + 0x2F, + 0xC0, + 0x5F, + 0x09, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x00, + 0x00, + 0x00, + 0x69, + 0x00, + 0x3D, + 0x00, + 0x0A, + 0x00, + 0x00, + 0x00, + ]; + + [Fact] + public async Task TestLzmaBufferAsync() + { + var input = new MemoryStream(LzmaData); + using var output = new MemoryStream(); + var properties = new byte[5]; + await input.ReadAsync(properties, 0, 5).ConfigureAwait(false); + + var fileLengthBytes = new byte[8]; + await input.ReadAsync(fileLengthBytes, 0, 8).ConfigureAwait(false); + var fileLength = BitConverter.ToInt64(fileLengthBytes, 0); + + var coder = new Decoder(); + coder.SetDecoderProperties(properties); + coder.Code(input, output, input.Length, fileLength, null); + + Assert.Equal(output.ToArray(), LzmaResultData); + } + + [Fact] + public async Task TestLzmaStreamEncodingWritesDataAsync() + { + using var inputStream = new MemoryStream(LzmaResultData); + using MemoryStream outputStream = new(); + using var lzmaStream = new LzmaStream(LzmaEncoderProperties.Default, false, outputStream); + await inputStream.CopyToAsync(lzmaStream).ConfigureAwait(false); + lzmaStream.Close(); + Assert.NotEqual(0, outputStream.Length); + } + + [Fact] + public async Task TestLzmaEncodingAccuracyAsync() + { + var input = new MemoryStream(LzmaResultData); + var compressed = new MemoryStream(); + var lzmaEncodingStream = new LzmaStream(LzmaEncoderProperties.Default, false, compressed); + await input.CopyToAsync(lzmaEncodingStream).ConfigureAwait(false); + lzmaEncodingStream.Close(); + compressed.Position = 0; + + var output = new MemoryStream(); + await DecompressLzmaStreamAsync( + lzmaEncodingStream.Properties, + compressed, + compressed.Length, + output, + LzmaResultData.LongLength + ) + .ConfigureAwait(false); + + Assert.Equal(output.ToArray(), LzmaResultData); + } + + private static async Task DecompressLzmaStreamAsync( + byte[] properties, + Stream compressedStream, + long compressedSize, + Stream decompressedStream, + long decompressedSize + ) + { + var lzmaStream = new LzmaStream( + properties, + compressedStream, + compressedSize, + -1, + null, + false + ); + + var buffer = new byte[1024]; + long totalRead = 0; + while (totalRead < decompressedSize) + { + var toRead = (int)Math.Min(buffer.Length, decompressedSize - totalRead); + var read = await lzmaStream.ReadAsync(buffer, 0, toRead).ConfigureAwait(false); + if (read > 0) + { + await decompressedStream.WriteAsync(buffer, 0, read).ConfigureAwait(false); + totalRead += read; + } + else + { + break; + } + } + } +} diff --git a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs index fd909b790..5b00140bc 100644 --- a/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs +++ b/tests/SharpCompress.Test/Streams/LzmaStreamTests.cs @@ -581,7 +581,7 @@ long decompressedSize false ); - var buffer = ArrayPool.Shared.Rent(1024); + var buffer = new byte[1024]; long totalRead = 0; while (totalRead < decompressedSize) { @@ -597,6 +597,5 @@ long decompressedSize break; } } - ArrayPool.Shared.Return(buffer); } } diff --git a/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs new file mode 100644 index 000000000..3893f5399 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/RewindableStreamAsyncTest.cs @@ -0,0 +1,96 @@ +using System.IO; +using System.Threading.Tasks; +using SharpCompress.IO; +using Xunit; + +namespace SharpCompress.Test.Streams; + +public class RewindableStreamAsyncTest +{ + [Fact] + public async Task TestRewindAsync() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + bw.Write(1); + bw.Write(2); + bw.Write(3); + bw.Write(4); + bw.Write(5); + bw.Write(6); + bw.Write(7); + bw.Flush(); + ms.Position = 0; + var stream = new SharpCompressStream(ms, bufferSize: 0x10000); + + Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + + ((IStreamStack)stream).StackSeek(0); + long pos = stream.Position; + Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false)); + + ((IStreamStack)stream).StackSeek(pos); + Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + } + + [Fact] + public async Task TestIncompleteRewindAsync() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + bw.Write(1); + bw.Write(2); + bw.Write(3); + bw.Write(4); + bw.Write(5); + bw.Write(6); + bw.Write(7); + bw.Flush(); + ms.Position = 0; + var stream = new SharpCompressStream(ms, bufferSize: 0x10000); + + Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + ((IStreamStack)stream).StackSeek(0); + + Assert.Equal(1, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(2, await ReadInt32Async(stream).ConfigureAwait(false)); + long pos = stream.Position; + + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false)); + ((IStreamStack)stream).StackSeek(pos); + + Assert.Equal(3, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(4, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(5, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(6, await ReadInt32Async(stream).ConfigureAwait(false)); + Assert.Equal(7, await ReadInt32Async(stream).ConfigureAwait(false)); + } + + private static async Task ReadInt32Async(Stream stream) + { + var buffer = new byte[4]; + var bytesRead = await stream.ReadAsync(buffer, 0, 4).ConfigureAwait(false); + if (bytesRead != 4) + { + throw new EndOfStreamException(); + } + return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + } +} diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs index a33560a6e..d806246da 100644 --- a/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamAsyncTests.cs @@ -28,148 +28,111 @@ private static void CreateData(MemoryStream ms) [Fact] public async Task BufferReadAsyncTest() { - byte[] data = ArrayPool.Shared.Rent(0x100000); - byte[] test = ArrayPool.Shared.Rent(0x1000); - try + byte[] data = new byte[0x100000]; + byte[] test = new byte[0x1000]; + using (MemoryStream ms = new MemoryStream(data)) { - using (MemoryStream ms = new MemoryStream(data)) - { - CreateData(ms); + CreateData(ms); - using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) - { - IStreamStack stack = (IStreamStack)scs; - - scs.Seek(0x1000, SeekOrigin.Begin); - Assert.Equal(0x1000, scs.Position); // position in the SharpCompressionStream - Assert.Equal(0x1000, ms.Position); // initial seek + full buffer read - - await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 - Assert.Equal(0x2000, scs.Position); // stream has correct position - Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct - Assert.Equal(0x11000, ms.Position); // seek plus read bytes - - scs.Seek(0x500, SeekOrigin.Begin); // seek before the buffer start - await scs.ReadAsync(test, 0, test.Length); // read bytes 0x500 to 0x1500 - Assert.Equal(0x1500, scs.Position); // stream has correct position - Assert.True(data.Skip(0x500).Take(test.Length).SequenceEqual(test)); // is the data correct - Assert.Equal(0x10500, ms.Position); // seek plus read bytes - } + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + scs.Seek(0x1000, SeekOrigin.Begin); + Assert.Equal(0x1000, scs.Position); // position in the SharpCompressionStream + Assert.Equal(0x1000, ms.Position); // initial seek + full buffer read + + await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); // stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct + Assert.Equal(0x11000, ms.Position); // seek plus read bytes + + scs.Seek(0x500, SeekOrigin.Begin); // seek before the buffer start + await scs.ReadAsync(test, 0, test.Length); // read bytes 0x500 to 0x1500 + Assert.Equal(0x1500, scs.Position); // stream has correct position + Assert.True(data.Skip(0x500).Take(test.Length).SequenceEqual(test)); // is the data correct + Assert.Equal(0x10500, ms.Position); // seek plus read bytes } } - finally - { - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test); - } } [Fact] public async Task BufferReadAndSeekAsyncTest() { - byte[] data = ArrayPool.Shared.Rent(0x100000); - byte[] test = ArrayPool.Shared.Rent(0x1000); - try + byte[] data = new byte[0x100000]; + byte[] test = new byte[0x1000]; + using (MemoryStream ms = new MemoryStream(data)) { - using (MemoryStream ms = new MemoryStream(data)) - { - CreateData(ms); + CreateData(ms); - using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) - { - IStreamStack stack = (IStreamStack)scs; - - await scs.ReadAsync(test, 0, test.Length); // read bytes 0 to 0x1000 - Assert.True(data.Take(test.Length).SequenceEqual(test)); // is the data correct - Assert.Equal(0x1000, scs.Position); // stream has correct position - Assert.Equal(0x10000, ms.Position); // moved the base stream on by buffer size - - await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 - Assert.Equal(0x2000, scs.Position); // stream has correct position - Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct - Assert.Equal(0x10000, ms.Position); // the base stream has not moved - - // rewind the buffer - stack.Rewind(0x1000); // rewind buffer back by 0x1000 bytes - - // repeat the previous test - await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 - Assert.Equal(0x2000, scs.Position); // stream has correct position - Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct - Assert.Equal(0x10000, ms.Position); // the base stream has not moved - } + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + IStreamStack stack = (IStreamStack)scs; + + await scs.ReadAsync(test, 0, test.Length); // read bytes 0 to 0x1000 + Assert.True(data.Take(test.Length).SequenceEqual(test)); // is the data correct + Assert.Equal(0x1000, scs.Position); // stream has correct position + Assert.Equal(0x10000, ms.Position); // moved the base stream on by buffer size + + await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); // stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct + Assert.Equal(0x10000, ms.Position); // the base stream has not moved + + // rewind the buffer + stack.Rewind(0x1000); // rewind buffer back by 0x1000 bytes + + // repeat the previous test + await scs.ReadAsync(test, 0, test.Length); // read bytes 0x1000 to 0x2000 + Assert.Equal(0x2000, scs.Position); // stream has correct position + Assert.True(data.Skip(test.Length).Take(test.Length).SequenceEqual(test)); // is the data correct + Assert.Equal(0x10000, ms.Position); // the base stream has not moved } } - finally - { - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test); - } } [Fact] public async Task MultipleAsyncReadsTest() { - byte[] data = ArrayPool.Shared.Rent(0x100000); - byte[] test1 = ArrayPool.Shared.Rent(0x800); - byte[] test2 = ArrayPool.Shared.Rent(0x800); - try + byte[] data = new byte[0x100000]; + byte[] test1 = new byte[0x800]; + byte[] test2 = new byte[0x800]; + using (MemoryStream ms = new MemoryStream(data)) { - using (MemoryStream ms = new MemoryStream(data)) - { - CreateData(ms); + CreateData(ms); - using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) - { - // Read first chunk - await scs.ReadAsync(test1, 0, test1.Length); - Assert.Equal(0x800, scs.Position); - Assert.True(data.Take(test1.Length).SequenceEqual(test1)); // first read is correct - - // Read second chunk - await scs.ReadAsync(test2, 0, test2.Length); - Assert.Equal(0x1000, scs.Position); - Assert.True(data.Skip(test1.Length).Take(test2.Length).SequenceEqual(test2)); // second read is correct - } + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + // Read first chunk + await scs.ReadAsync(test1, 0, test1.Length); + Assert.Equal(0x800, scs.Position); + Assert.True(data.Take(test1.Length).SequenceEqual(test1)); // first read is correct + + // Read second chunk + await scs.ReadAsync(test2, 0, test2.Length); + Assert.Equal(0x1000, scs.Position); + Assert.True(data.Skip(test1.Length).Take(test2.Length).SequenceEqual(test2)); // second read is correct } } - finally - { - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test1); - ArrayPool.Shared.Return(test2); - } } [Fact] public async Task LargeBufferAsyncReadTest() { - byte[] data = ArrayPool.Shared.Rent(0x200000); - byte[] test = ArrayPool.Shared.Rent(0x8000); - try + byte[] data = new byte[0x200000]; + byte[] test = new byte[0x8000]; + using (MemoryStream ms = new MemoryStream(data)) { - using (MemoryStream ms = new MemoryStream(data)) - { - CreateData(ms); + CreateData(ms); - using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000)) + { + for (int i = 0; i < 10; i++) { - for (int i = 0; i < 10; i++) - { - await scs.ReadAsync(test, 0, test.Length); - long expectedPosition = (long)(i + 1) * test.Length; - Assert.Equal(expectedPosition, scs.Position); - Assert.True( - data.Skip(i * test.Length).Take(test.Length).SequenceEqual(test) - ); - } + await scs.ReadAsync(test, 0, test.Length); + long expectedPosition = (long)(i + 1) * test.Length; + Assert.Equal(expectedPosition, scs.Position); + Assert.True(data.Skip(i * test.Length).Take(test.Length).SequenceEqual(test)); } } } - finally - { - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test); - } } } diff --git a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs index 5161076bb..1c4a8b571 100644 --- a/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs +++ b/tests/SharpCompress.Test/Streams/SharpCompressStreamTest.cs @@ -27,8 +27,8 @@ private static void createData(MemoryStream ms) [Fact] public void BufferReadTest() { - byte[] data = ArrayPool.Shared.Rent(0x100000); - byte[] test = ArrayPool.Shared.Rent(0x1000); + byte[] data = new byte[0x100000]; + byte[] test = new byte[0x1000]; using (MemoryStream ms = new MemoryStream(data)) { createData(ms); @@ -53,16 +53,13 @@ public void BufferReadTest() Assert.Equal(0x10500, ms.Position); //seek plus read bytes } } - - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test); } [Fact] public void BufferReadAndSeekTest() { - byte[] data = ArrayPool.Shared.Rent(0x100000); - byte[] test = ArrayPool.Shared.Rent(0x1000); + byte[] data = new byte[0x100000]; + byte[] test = new byte[0x1000]; using (MemoryStream ms = new MemoryStream(data)) { createData(ms); @@ -91,8 +88,5 @@ public void BufferReadAndSeekTest() Assert.Equal(0x10000, ms.Position); //the base stream has not moved } } - - ArrayPool.Shared.Return(data); - ArrayPool.Shared.Return(test); } } diff --git a/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs new file mode 100644 index 000000000..3512b8b76 --- /dev/null +++ b/tests/SharpCompress.Test/Streams/ZLibBaseStreamAsyncTests.cs @@ -0,0 +1,112 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using AwesomeAssertions; +using SharpCompress.Compressors; +using SharpCompress.Compressors.Deflate; +using SharpCompress.IO; +using Xunit; + +namespace SharpCompress.Test.Streams; + +public class ZLibBaseStreamAsyncTests +{ + [Fact] + public async Task TestChunkedZlibCompressesEverythingAsync() + { + var plainData = new byte[] + { + 0xf7, + 0x1b, + 0xda, + 0x0f, + 0xb6, + 0x2b, + 0x3d, + 0x91, + 0xd7, + 0xe1, + 0xb5, + 0x11, + 0x34, + 0x5a, + 0x51, + 0x3f, + 0x8b, + 0xce, + 0x49, + 0xd2, + }; + var buf = new byte[plainData.Length * 2]; + + var plainStream1 = new MemoryStream(plainData); + var compressor1 = new DeflateStream(plainStream1, CompressionMode.Compress); + // This is enough to read the entire data + var realCompressedSize = await compressor1 + .ReadAsync(buf, 0, plainData.Length * 2) + .ConfigureAwait(false); + + var plainStream2 = new MemoryStream(plainData); + var compressor2 = new DeflateStream(plainStream2, CompressionMode.Compress); + var total = 0; + var r = -1; // Jumpstart + while (r != 0) + { + // Reading in chunks + r = await compressor2.ReadAsync(buf, 0, plainData.Length).ConfigureAwait(false); + total += r; + } + + Assert.Equal(total, realCompressedSize); + } + + [Fact] + public async Task Zlib_should_read_the_previously_written_message_async() + { + var message = new string('a', 131073); // 131073 causes the failure, but 131072 (-1) doesn't + var bytes = Encoding.ASCII.GetBytes(message); + + using var inputStream = new MemoryStream(bytes); + using var compressedStream = new MemoryStream(); + using var byteBufferStream = new BufferedStream(inputStream); // System.IO + await CompressAsync(byteBufferStream, compressedStream, compressionLevel: 1) + .ConfigureAwait(false); + compressedStream.Position = 0; + + using var decompressedStream = new MemoryStream(); + await DecompressAsync(compressedStream, decompressedStream).ConfigureAwait(false); + + byteBufferStream.Position = 0; + var result = Encoding.ASCII.GetString( + await GetBytesAsync(byteBufferStream).ConfigureAwait(false) + ); + result.Should().Be(message); + } + + private async Task CompressAsync(Stream input, Stream output, int compressionLevel) + { + using var zlibStream = new ZlibStream( + SharpCompressStream.Create(output, leaveOpen: true), + CompressionMode.Compress, + (CompressionLevel)compressionLevel + ); + zlibStream.FlushMode = FlushType.Sync; + await input.CopyToAsync(zlibStream).ConfigureAwait(false); + } + + private async Task DecompressAsync(Stream input, Stream output) + { + using var zlibStream = new ZlibStream( + SharpCompressStream.Create(input, leaveOpen: true), + CompressionMode.Decompress + ); + await zlibStream.CopyToAsync(output).ConfigureAwait(false); + } + + private async Task GetBytesAsync(BufferedStream stream) + { + var bytes = new byte[stream.Length]; + await stream.ReadAsync(bytes, 0, (int)stream.Length).ConfigureAwait(false); + return bytes; + } +} diff --git a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs index 7d7ed4306..0bc93d833 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderAsyncTests.cs @@ -207,13 +207,10 @@ public async Task Tar_Corrupted_Async() Assert.Throws(() => reader.MoveToNextEntry()); } -#if !NETFRAMEWORK +#if LINUX [Fact] public async Task Tar_GZip_With_Symlink_Entries_Async() { - var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform( - System.Runtime.InteropServices.OSPlatform.Windows - ); using Stream stream = File.OpenRead( Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz") ); @@ -232,38 +229,32 @@ await reader.WriteEntryToDirectoryAsync( Overwrite = true, WriteSymbolicLink = (sourcePath, targetPath) => { - if (!isWindows) + var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); + if (File.Exists(sourcePath)) { - var link = new Mono.Unix.UnixSymbolicLinkInfo(sourcePath); - if (File.Exists(sourcePath)) - { - link.Delete(); // equivalent to ln -s -f - } - link.CreateSymbolicLinkTo(targetPath); + link.Delete(); // equivalent to ln -s -f } + link.CreateSymbolicLinkTo(targetPath); }, } ); - if (!isWindows) + if (reader.Entry.LinkTarget != null) { - if (reader.Entry.LinkTarget != null) + var path = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key.NotNull()); + var link = new Mono.Unix.UnixSymbolicLinkInfo(path); + if (link.HasContents) { - var path = Path.Combine(SCRATCH_FILES_PATH, reader.Entry.Key.NotNull()); - var link = new Mono.Unix.UnixSymbolicLinkInfo(path); - if (link.HasContents) - { - // need to convert the link to an absolute path for comparison - var target = reader.Entry.LinkTarget; - var realTarget = Path.GetFullPath( - Path.Combine($"{Path.GetDirectoryName(path)}", target) - ); + // need to convert the link to an absolute path for comparison + var target = reader.Entry.LinkTarget; + var realTarget = Path.GetFullPath( + Path.Combine($"{Path.GetDirectoryName(path)}", target) + ); - Assert.Equal(realTarget, link.GetContents().ToString()); - } - else - { - Assert.True(false, "Symlink has no target"); - } + Assert.Equal(realTarget, link.GetContents().ToString()); + } + else + { + Assert.True(false, "Symlink has no target"); } } }