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");
}
}
}