Merge branch 'adam/async-lzma' into adam/async-xz

This commit is contained in:
Adam Hathcock 2025-11-01 10:28:27 +00:00
commit 7dd0da5fd7
39 changed files with 2503 additions and 181 deletions

View file

@ -14,6 +14,7 @@
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.21" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" />
</ItemGroup>

View file

@ -8,4 +8,5 @@ public enum ArchiveType
SevenZip,
GZip,
Arc,
Arj,
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.Arc;
using SharpCompress.Common.Arj.Headers;
namespace SharpCompress.Common.Arj
{
public class ArjEntry : Entry
{
private readonly ArjFilePart _filePart;
internal ArjEntry(ArjFilePart filePart)
{
_filePart = filePart;
}
public override long Crc => _filePart.Header.OriginalCrc32;
public override string? Key => _filePart?.Header.Name;
public override string? LinkTarget => null;
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
public override CompressionType CompressionType
{
get
{
if (_filePart.Header.CompressionMethod == CompressionMethod.Stored)
{
return CompressionType.None;
}
return CompressionType.ArjLZ77;
}
}
public override long Size => _filePart?.Header.OriginalSize ?? 0;
public override DateTime? LastModifiedTime => _filePart.Header.DateTimeModified.DateTime;
public override DateTime? CreatedTime => _filePart.Header.DateTimeCreated.DateTime;
public override DateTime? LastAccessedTime => _filePart.Header.DateTimeAccessed.DateTime;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => _filePart.Header.FileType == FileType.Directory;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.Arj.Headers;
using SharpCompress.Compressors.Arj;
using SharpCompress.IO;
namespace SharpCompress.Common.Arj
{
public class ArjFilePart : FilePart
{
private readonly Stream _stream;
internal ArjLocalHeader Header { get; set; }
internal ArjFilePart(ArjLocalHeader localArjHeader, Stream seekableStream)
: base(localArjHeader.ArchiveEncoding)
{
_stream = seekableStream;
Header = localArjHeader;
}
internal override string? FilePartName => Header.Name;
internal override Stream GetCompressedStream()
{
if (_stream != null)
{
Stream compressedStream;
switch (Header.CompressionMethod)
{
case CompressionMethod.Stored:
compressedStream = new ReadOnlySubStream(
_stream,
Header.DataStartPosition,
Header.CompressedSize
);
break;
case CompressionMethod.CompressedFastest:
byte[] compressedData = new byte[Header.CompressedSize];
_stream.Position = Header.DataStartPosition;
_stream.Read(compressedData, 0, compressedData.Length);
byte[] decompressedData = LHDecoder.DecodeFastest(
compressedData,
(int)Header.OriginalSize // ARJ can only handle files up to 2GB, so casting to int should not be an issue.
);
compressedStream = new MemoryStream(decompressedData);
break;
default:
throw new NotSupportedException(
"CompressionMethod: " + Header.CompressionMethod
);
}
return compressedStream;
}
return _stream.NotNull();
}
internal override Stream GetRawStream() => _stream;
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Readers;
namespace SharpCompress.Common.Arj
{
public class ArjVolume : Volume
{
public ArjVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
: base(stream, readerOptions, index) { }
}
}

View file

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.Crypto;
namespace SharpCompress.Common.Arj.Headers
{
public enum ArjHeaderType
{
MainHeader,
LocalHeader,
}
public abstract class ArjHeader
{
private const int FIRST_HDR_SIZE = 34;
private const ushort ARJ_MAGIC = 0xEA60;
public ArjHeader(ArjHeaderType type)
{
ArjHeaderType = type;
}
public ArjHeaderType ArjHeaderType { get; }
public byte Flags { get; set; }
public FileType FileType { get; set; }
public abstract ArjHeader? Read(Stream reader);
public byte[] ReadHeader(Stream stream)
{
// check for magic bytes
Span<byte> magic = stackalloc byte[2];
if (stream.Read(magic) != 2)
{
return Array.Empty<byte>();
}
var magicValue = (ushort)(magic[0] | magic[1] << 8);
if (magicValue != ARJ_MAGIC)
{
throw new InvalidDataException("Not an ARJ file (wrong magic bytes)");
}
// read header_size
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
var headerSize = (ushort)(headerBytes[0] | headerBytes[1] << 8);
if (headerSize < 1)
{
return Array.Empty<byte>();
}
var body = new byte[headerSize];
var read = stream.Read(body, 0, headerSize);
if (read < headerSize)
{
return Array.Empty<byte>();
}
byte[] crc = new byte[4];
read = stream.Read(crc, 0, 4);
var checksum = Crc32Stream.Compute(body);
// Compute the hash value
if (checksum != BitConverter.ToUInt32(crc, 0))
{
throw new InvalidDataException("Header checksum is invalid");
}
return body;
}
protected List<byte[]> ReadExtendedHeaders(Stream reader)
{
List<byte[]> extendedHeader = new List<byte[]>();
byte[] buffer = new byte[2];
while (true)
{
int bytesRead = reader.Read(buffer, 0, 2);
if (bytesRead < 2)
{
throw new EndOfStreamException(
"Unexpected end of stream while reading extended header size."
);
}
var extHeaderSize = (ushort)(buffer[0] | (buffer[1] << 8));
if (extHeaderSize == 0)
{
return extendedHeader;
}
byte[] header = new byte[extHeaderSize];
bytesRead = reader.Read(header, 0, extHeaderSize);
if (bytesRead < extHeaderSize)
{
throw new EndOfStreamException(
"Unexpected end of stream while reading extended header data."
);
}
byte[] crc = new byte[4];
bytesRead = reader.Read(crc, 0, 4);
if (bytesRead < 4)
{
throw new EndOfStreamException(
"Unexpected end of stream while reading extended header CRC."
);
}
var checksum = Crc32Stream.Compute(header);
if (checksum != BitConverter.ToUInt32(crc, 0))
{
throw new InvalidDataException("Extended header checksum is invalid");
}
extendedHeader.Add(header);
}
}
// Flag helpers
public bool IsGabled => (Flags & 0x01) != 0;
public bool IsAnsiPage => (Flags & 0x02) != 0;
public bool IsVolume => (Flags & 0x04) != 0;
public bool IsArjProtected => (Flags & 0x08) != 0;
public bool IsPathSym => (Flags & 0x10) != 0;
public bool IsBackup => (Flags & 0x20) != 0;
public bool IsSecured => (Flags & 0x40) != 0;
public bool IsAltName => (Flags & 0x80) != 0;
public static FileType FileTypeFromByte(byte value)
{
return Enum.IsDefined(typeof(FileType), value)
? (FileType)value
: Headers.FileType.Unknown;
}
}
}

View file

@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace SharpCompress.Common.Arj.Headers
{
public class ArjLocalHeader : ArjHeader
{
public ArchiveEncoding ArchiveEncoding { get; }
public long DataStartPosition { get; protected set; }
public byte ArchiverVersionNumber { get; set; }
public byte MinVersionToExtract { get; set; }
public HostOS HostOS { get; set; }
public CompressionMethod CompressionMethod { get; set; }
public DosDateTime DateTimeModified { get; set; } = new DosDateTime(0);
public long CompressedSize { get; set; }
public long OriginalSize { get; set; }
public long OriginalCrc32 { get; set; }
public int FileSpecPosition { get; set; }
public int FileAccessMode { get; set; }
public byte FirstChapter { get; set; }
public byte LastChapter { get; set; }
public long ExtendedFilePosition { get; set; }
public DosDateTime DateTimeAccessed { get; set; } = new DosDateTime(0);
public DosDateTime DateTimeCreated { get; set; } = new DosDateTime(0);
public long OriginalSizeEvenForVolumes { get; set; }
public string Name { get; set; } = string.Empty;
public string Comment { get; set; } = string.Empty;
private const byte StdHdrSize = 30;
private const byte R9HdrSize = 46;
public ArjLocalHeader(ArchiveEncoding archiveEncoding)
: base(ArjHeaderType.LocalHeader)
{
ArchiveEncoding =
archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
}
public override ArjHeader? Read(Stream stream)
{
var body = ReadHeader(stream);
if (body.Length > 0)
{
ReadExtendedHeaders(stream);
var header = LoadFrom(body);
header.DataStartPosition = stream.Position;
return header;
}
return null;
}
public ArjLocalHeader LoadFrom(byte[] headerBytes)
{
int offset = 0;
int ReadInt16()
{
if (offset + 1 >= headerBytes.Length)
{
throw new EndOfStreamException();
}
var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8;
offset += 2;
return v;
}
long ReadInt32()
{
if (offset + 3 >= headerBytes.Length)
{
throw new EndOfStreamException();
}
long v =
headerBytes[offset] & 0xFF
| (headerBytes[offset + 1] & 0xFF) << 8
| (headerBytes[offset + 2] & 0xFF) << 16
| (headerBytes[offset + 3] & 0xFF) << 24;
offset += 4;
return v;
}
byte headerSize = headerBytes[offset++];
ArchiverVersionNumber = headerBytes[offset++];
MinVersionToExtract = headerBytes[offset++];
HostOS hostOS = (HostOS)headerBytes[offset++];
Flags = headerBytes[offset++];
CompressionMethod = CompressionMethodFromByte(headerBytes[offset++]);
FileType = FileTypeFromByte(headerBytes[offset++]);
offset++; // Skip 1 byte
var rawTimestamp = ReadInt32();
DateTimeModified =
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
CompressedSize = ReadInt32();
OriginalSize = ReadInt32();
OriginalCrc32 = ReadInt32();
FileSpecPosition = ReadInt16();
FileAccessMode = ReadInt16();
FirstChapter = headerBytes[offset++];
LastChapter = headerBytes[offset++];
ExtendedFilePosition = 0;
OriginalSizeEvenForVolumes = 0;
if (headerSize > StdHdrSize)
{
ExtendedFilePosition = ReadInt32();
if (headerSize >= R9HdrSize)
{
rawTimestamp = ReadInt32();
DateTimeAccessed =
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
rawTimestamp = ReadInt32();
DateTimeCreated =
rawTimestamp != 0 ? new DosDateTime(rawTimestamp) : new DosDateTime(0);
OriginalSizeEvenForVolumes = ReadInt32();
}
}
Name = Encoding.ASCII.GetString(
headerBytes,
offset,
Array.IndexOf(headerBytes, (byte)0, offset) - offset
);
offset += Name.Length + 1;
Comment = Encoding.ASCII.GetString(
headerBytes,
offset,
Array.IndexOf(headerBytes, (byte)0, offset) - offset
);
offset += Comment.Length + 1;
return this;
}
public static CompressionMethod CompressionMethodFromByte(byte value)
{
return value switch
{
0 => CompressionMethod.Stored,
1 => CompressionMethod.CompressedMost,
2 => CompressionMethod.Compressed,
3 => CompressionMethod.CompressedFaster,
4 => CompressionMethod.CompressedFastest,
8 => CompressionMethod.NoDataNoCrc,
9 => CompressionMethod.NoData,
_ => CompressionMethod.Unknown,
};
}
}
}

View file

@ -0,0 +1,138 @@
using System;
using System.IO;
using System.Text;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Crypto;
namespace SharpCompress.Common.Arj.Headers
{
public class ArjMainHeader : ArjHeader
{
private const int FIRST_HDR_SIZE = 34;
private const ushort ARJ_MAGIC = 0xEA60;
public ArchiveEncoding ArchiveEncoding { get; }
public int ArchiverVersionNumber { get; private set; }
public int MinVersionToExtract { get; private set; }
public HostOS HostOs { get; private set; }
public int SecurityVersion { get; private set; }
public DosDateTime CreationDateTime { get; private set; } = new DosDateTime(0);
public long CompressedSize { get; private set; }
public long ArchiveSize { get; private set; }
public long SecurityEnvelope { get; private set; }
public int FileSpecPosition { get; private set; }
public int SecurityEnvelopeLength { get; private set; }
public int EncryptionVersion { get; private set; }
public int LastChapter { get; private set; }
public int ArjProtectionFactor { get; private set; }
public int Flags2 { get; private set; }
public string Name { get; private set; } = string.Empty;
public string Comment { get; private set; } = string.Empty;
public ArjMainHeader(ArchiveEncoding archiveEncoding)
: base(ArjHeaderType.MainHeader)
{
ArchiveEncoding =
archiveEncoding ?? throw new ArgumentNullException(nameof(archiveEncoding));
}
public override ArjHeader? Read(Stream stream)
{
var body = ReadHeader(stream);
ReadExtendedHeaders(stream);
return LoadFrom(body);
}
public ArjMainHeader LoadFrom(byte[] headerBytes)
{
var offset = 1;
byte ReadByte()
{
if (offset >= headerBytes.Length)
{
throw new EndOfStreamException();
}
return (byte)(headerBytes[offset++] & 0xFF);
}
int ReadInt16()
{
if (offset + 1 >= headerBytes.Length)
{
throw new EndOfStreamException();
}
var v = headerBytes[offset] & 0xFF | (headerBytes[offset + 1] & 0xFF) << 8;
offset += 2;
return v;
}
long ReadInt32()
{
if (offset + 3 >= headerBytes.Length)
{
throw new EndOfStreamException();
}
long v =
headerBytes[offset] & 0xFF
| (headerBytes[offset + 1] & 0xFF) << 8
| (headerBytes[offset + 2] & 0xFF) << 16
| (headerBytes[offset + 3] & 0xFF) << 24;
offset += 4;
return v;
}
string ReadNullTerminatedString(byte[] x, int startIndex)
{
var result = new StringBuilder();
int i = startIndex;
while (i < x.Length && x[i] != 0)
{
result.Append((char)x[i]);
i++;
}
// Skip the null terminator
i++;
if (i < x.Length)
{
byte[] remainder = new byte[x.Length - i];
Array.Copy(x, i, remainder, 0, remainder.Length);
x = remainder;
}
return result.ToString();
}
ArchiverVersionNumber = ReadByte();
MinVersionToExtract = ReadByte();
var hostOsByte = ReadByte();
HostOs = hostOsByte <= 11 ? (HostOS)hostOsByte : HostOS.Unknown;
Flags = ReadByte();
SecurityVersion = ReadByte();
FileType = FileTypeFromByte(ReadByte());
offset++; // skip reserved
CreationDateTime = new DosDateTime((int)ReadInt32());
CompressedSize = ReadInt32();
ArchiveSize = ReadInt32();
SecurityEnvelope = ReadInt32();
FileSpecPosition = ReadInt16();
SecurityEnvelopeLength = ReadInt16();
EncryptionVersion = ReadByte();
LastChapter = ReadByte();
Name = ReadNullTerminatedString(headerBytes, offset);
Comment = ReadNullTerminatedString(headerBytes, offset + 1 + Name.Length);
return this;
}
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpCompress.Common.Arj.Headers
{
public enum CompressionMethod
{
Stored = 0,
CompressedMost = 1,
Compressed = 2,
CompressedFaster = 3,
CompressedFastest = 4,
NoDataNoCrc = 8,
NoData = 9,
Unknown,
}
}

View file

@ -0,0 +1,37 @@
using System;
namespace SharpCompress.Common.Arj.Headers
{
public class DosDateTime
{
public DateTime DateTime { get; }
public DosDateTime(long dosValue)
{
// Ensure only the lower 32 bits are used
int value = unchecked((int)(dosValue & 0xFFFFFFFF));
var date = (value >> 16) & 0xFFFF;
var time = value & 0xFFFF;
var day = date & 0x1F;
var month = (date >> 5) & 0x0F;
var year = ((date >> 9) & 0x7F) + 1980;
var second = (time & 0x1F) * 2;
var minute = (time >> 5) & 0x3F;
var hour = (time >> 11) & 0x1F;
try
{
DateTime = new DateTime(year, month, day, hour, minute, second);
}
catch
{
DateTime = DateTime.MinValue;
}
}
public override string ToString() => DateTime.ToString("yyyy-MM-dd HH:mm:ss");
}
}

View file

@ -0,0 +1,13 @@
namespace SharpCompress.Common.Arj.Headers
{
public enum FileType : byte
{
Binary = 0,
Text7Bit = 1,
CommentHeader = 2,
Directory = 3,
VolumeLabel = 4,
ChapterLabel = 5,
Unknown = 255,
}
}

View file

@ -0,0 +1,19 @@
namespace SharpCompress.Common.Arj.Headers
{
public enum HostOS
{
MsDos = 0,
PrimOS = 1,
Unix = 2,
Amiga = 3,
MacOs = 4,
OS2 = 5,
AppleGS = 6,
AtariST = 7,
NeXT = 8,
VaxVMS = 9,
Win95 = 10,
Win32 = 11,
Unknown = 255,
}
}

View file

@ -29,4 +29,5 @@ public enum CompressionType
Crushed,
Distilled,
ZStandard,
ArjLZ77,
}

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SharpCompress.Compressors.Arj
{
[CLSCompliant(true)]
public class BitReader
{
private readonly byte[] data;
private int bytePos = 0;
private int bitPos = 0;
public BitReader(byte[] input)
{
data = input;
}
public int ReadBits(int count)
{
int result = 0;
for (int i = 0; i < count; i++)
{
if (bytePos >= data.Length)
{
throw new EndOfStreamException();
}
int bit = (data[bytePos] >> (7 - bitPos)) & 1;
result = (result << 1) | bit;
bitPos++;
if (bitPos == 8)
{
bitPos = 0;
bytePos++;
}
}
return result;
}
}
[CLSCompliant(true)]
public static class LHDecoder
{
private const int THRESHOLD = 3;
private static int DecodeVal(BitReader r, int from, int to)
{
int add = 0;
int bit = from;
while (bit < to && r.ReadBits(1) == 1)
{
add |= 1 << bit;
bit++;
}
int res = bit > 0 ? r.ReadBits(bit) : 0;
return res + add;
}
public static byte[] DecodeFastest(byte[] data, int originalSize)
{
var res = new List<byte>(originalSize);
var r = new BitReader(data);
while (res.Count < originalSize)
{
int len = DecodeVal(r, 0, 7);
if (len == 0)
{
byte nextChar = (byte)r.ReadBits(8);
res.Add(nextChar);
}
else
{
int repCount = len + THRESHOLD - 1;
int backPtr = DecodeVal(r, 9, 13);
if (backPtr >= res.Count)
{
throw new InvalidDataException("invalid back_ptr");
}
int i = res.Count - 1 - backPtr;
for (int j = 0; j < repCount; j++)
{
res.Add(res[i]);
i++;
}
}
}
return res.ToArray();
}
}
}

View file

@ -2,6 +2,8 @@ using System;
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 @@ internal sealed class AesDecoderStream : DecoderStream2, IStreamStack
return count;
}
public override async Task<int> 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
}

View file

@ -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 @@ internal class Bcj2DecoderStream : DecoderStream2, IStreamStack
return count;
}
public override Task<int> 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)

View file

@ -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 @@ internal class OutWindow : IDisposable
_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 @@ internal class OutWindow : IDisposable
_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 @@ internal class OutWindow : IDisposable
_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 @@ internal class OutWindow : IDisposable
_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 @@ internal class OutWindow : IDisposable
}
}
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 @@ internal class OutWindow : IDisposable
return len - size;
}
public async Task<int> 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;

View file

@ -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 sealed class LZipStream : Stream, IStreamStack
#if !NETFRAMEWORK && !NETSTANDARD2_0
public override ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
) => _stream.ReadAsync(buffer, cancellationToken);
public override int Read(Span<byte> buffer) => _stream.Read(buffer);
public override void Write(ReadOnlySpan<byte> buffer)
@ -179,6 +186,25 @@ public sealed class LZipStream : Stream, IStreamStack
++_writeCount;
}
public override Task<int> 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
/// <summary>

View file

@ -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 class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
}
}
#if !NETFRAMEWORK && !NETSTANDARD2_0
[MemberNotNull(nameof(_outWindow))]
#endif
private void CreateDictionary()
{
if (_dictionarySize < 0)
@ -309,6 +313,42 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
_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 @@ public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
return false;
}
internal async System.Threading.Tasks.Task<bool> 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 class Decoder : ICoder, ISetDecoderProperties // ,System.IO.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) {}
*/
}

View file

@ -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 @@ public class LzmaStream : Stream, IStreamStack
}
}
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 class LzmaStream : Stream, IStreamStack
}
}
public override async Task<int> 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];
}

View file

@ -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 @@ internal class CrcBuilderStream : Stream, IStreamStack
_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);
}
}

View file

@ -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<long>.Shared.Rent(256);
private long _mLength;
public CrcCheckStream(uint crc)
@ -65,6 +68,7 @@ public class CrcCheckStream : Stream
finally
{
base.Dispose(disposing);
ArrayPool<long>.Shared.Return(_mBytes);
}
}
@ -101,4 +105,16 @@ public class CrcCheckStream : Stream
_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;
}
}

View file

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Arj.Headers;
using SharpCompress.Readers;
using SharpCompress.Readers.Arj;
namespace SharpCompress.Factories
{
public class ArjFactory : Factory, IReaderFactory
{
public override string Name => "Arj";
public override ArchiveType? KnownArchiveType => ArchiveType.Arj;
public override IEnumerable<string> GetSupportedExtensions()
{
yield return "arj";
}
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var arjHeader = new ArjMainHeader(new ArchiveEncoding());
if (arjHeader.Read(stream) == null)
{
return false;
}
return true;
}
public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArjReader.Open(stream, options);
}
}

View file

@ -18,6 +18,7 @@ public abstract class Factory : IFactory
RegisterFactory(new GZipFactory());
RegisterFactory(new TarFactory());
RegisterFactory(new ArcFactory());
RegisterFactory(new ArjFactory());
}
private static readonly HashSet<Factory> _factories = new();

View file

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Arj;
using SharpCompress.Common.Arj.Headers;
using SharpCompress.Common.Zip;
using SharpCompress.Common.Zip.Headers;
namespace SharpCompress.Readers.Arj
{
public class ArjReader : AbstractReader<ArjEntry, ArjVolume>
{
private ArjReader(Stream stream, ReaderOptions options)
: base(options, ArchiveType.Arj) => Volume = new ArjVolume(stream, options, 0);
public override ArjVolume Volume { get; }
/// <summary>
/// Opens an ArjReader for Non-seeking usage with a single volume
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ArjReader Open(Stream stream, ReaderOptions? options = null)
{
stream.NotNull(nameof(stream));
return new ArjReader(stream, options ?? new ReaderOptions());
}
protected override IEnumerable<ArjEntry> GetEntries(Stream stream)
{
ArchiveEncoding encoding = new ArchiveEncoding();
var mainHeaderReader = new ArjMainHeader(encoding);
var localHeaderReader = new ArjLocalHeader(encoding);
var mainHeader = mainHeaderReader.Read(stream);
while (true)
{
var localHeader = localHeaderReader.Read(stream);
if (localHeader == null)
break;
yield return new ArjEntry(new ArjFilePart((ArjLocalHeader)localHeader, stream));
}
}
}
}

View file

@ -70,7 +70,7 @@ public static class ReaderFactory
}
throw new InvalidFormatException(
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Arj, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
);
}
}

View file

@ -38,6 +38,9 @@
<PackageReference Include="ZstdSharp.Port" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'net481' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Text.Encoding.CodePages" />

View file

@ -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",

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Readers;
using Xunit;
namespace SharpCompress.Test.Arj
{
public class ArjReaderTests : ReaderTests
{
public ArjReaderTests()
{
UseExtensionInsteadOfNameToVerify = true;
UseCaseInsensitiveToVerify = true;
}
[Fact]
public void Arj_Uncompressed_Read() => Read("Arj.store.arj", CompressionType.None);
[Fact]
public void Arj_Method4_Read() => Read("Arj.method4.arj");
}
}

View file

@ -13,29 +13,53 @@ namespace SharpCompress.Test;
public abstract class ReaderTests : TestBase
{
protected void Read(string testArchive, ReaderOptions? options = null)
{
ReadCore(testArchive, options, ReadImpl);
}
protected void Read(
string testArchive,
CompressionType expectedCompression,
ReaderOptions? options = null
)
{
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
ReadCore(testArchive, options, (path, opts) => ReadImpl(path, expectedCompression, opts));
}
options ??= new ReaderOptions() { BufferSize = 0x20000 }; //test larger buffer size (need test rather than eyeballing debug logs :P)
private void ReadCore(
string testArchive,
ReaderOptions? options,
Action<string, ReaderOptions> readImpl
)
{
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
options ??= new ReaderOptions { BufferSize = 0x20000 };
options.LeaveStreamOpen = true;
ReadImpl(testArchive, expectedCompression, options);
readImpl(testArchive, options);
options.LeaveStreamOpen = false;
ReadImpl(testArchive, expectedCompression, options);
readImpl(testArchive, options);
VerifyFiles();
}
private void ReadImpl(string testArchive, ReaderOptions options)
{
ReadImplCore(testArchive, options, UseReader);
}
private void ReadImpl(
string testArchive,
CompressionType expectedCompression,
ReaderOptions options
)
{
ReadImplCore(testArchive, options, r => UseReader(r, expectedCompression));
}
private void ReadImplCore(string testArchive, ReaderOptions options, Action<IReader> useReader)
{
using var file = File.OpenRead(testArchive);
using var protectedStream = SharpCompressStream.Create(
@ -47,19 +71,17 @@ public abstract class ReaderTests : TestBase
using var testStream = new TestStream(protectedStream);
using (var reader = ReaderFactory.Open(testStream, options))
{
UseReader(reader, expectedCompression);
useReader(reader);
protectedStream.ThrowOnDispose = false;
Assert.False(testStream.IsDisposed, $"{nameof(testStream)} prematurely closed");
}
// Boolean XOR -- If the stream should be left open (true), then the stream should not be diposed (false)
// and if the stream should be closed (false), then the stream should be disposed (true)
var message =
$"{nameof(options.LeaveStreamOpen)} is set to '{options.LeaveStreamOpen}', so {nameof(testStream.IsDisposed)} should be set to '{!testStream.IsDisposed}', but is set to {testStream.IsDisposed}";
Assert.True(options.LeaveStreamOpen != testStream.IsDisposed, message);
}
public void UseReader(IReader reader, CompressionType expectedCompression)
protected void UseReader(IReader reader, CompressionType expectedCompression)
{
while (reader.MoveToNextEntry())
{
@ -74,6 +96,20 @@ public abstract class ReaderTests : TestBase
}
}
private void UseReader(IReader reader)
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
reader.WriteEntryToDirectory(
SCRATCH_FILES_PATH,
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
);
}
}
}
protected async Task ReadAsync(
string testArchive,
CompressionType expectedCompression,

View file

@ -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,
];
/// <summary>
/// The decoded data for <see cref="LzmaData"/>.
/// </summary>
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;
}
}
}
}

View file

@ -581,7 +581,7 @@ public class LzmaStreamTests
false
);
var buffer = ArrayPool<byte>.Shared.Rent(1024);
var buffer = new byte[1024];
long totalRead = 0;
while (totalRead < decompressedSize)
{
@ -597,6 +597,5 @@ public class LzmaStreamTests
break;
}
}
ArrayPool<byte>.Shared.Return(buffer);
}
}

View file

@ -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<int> 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);
}
}

View file

@ -28,148 +28,111 @@ public class SharpCompressStreamAsyncTests
[Fact]
public async Task BufferReadAsyncTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
byte[] test = ArrayPool<byte>.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);
using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
CreateData(ms);
scs.Seek(0x1000, SeekOrigin.Begin);
Assert.Equal(0x1000, scs.Position); // position in the SharpCompressionStream
Assert.Equal(0x1000, ms.Position); // initial seek + full buffer read
using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
IStreamStack stack = (IStreamStack)scs;
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(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
}
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<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test);
}
}
[Fact]
public async Task BufferReadAndSeekAsyncTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
byte[] test = ArrayPool<byte>.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);
using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
CreateData(ms);
IStreamStack stack = (IStreamStack)scs;
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 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
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
// 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
}
// 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<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test);
}
}
[Fact]
public async Task MultipleAsyncReadsTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
byte[] test1 = ArrayPool<byte>.Shared.Rent(0x800);
byte[] test2 = ArrayPool<byte>.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);
using (SharpCompressStream scs = new SharpCompressStream(ms, true, false, 0x10000))
{
CreateData(ms);
// 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
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
}
// 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<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test1);
ArrayPool<byte>.Shared.Return(test2);
}
}
[Fact]
public async Task LargeBufferAsyncReadTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x200000);
byte[] test = ArrayPool<byte>.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<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test);
}
}
}

View file

@ -27,8 +27,8 @@ public class SharpCompressStreamTests
[Fact]
public void BufferReadTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
byte[] test = ArrayPool<byte>.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 class SharpCompressStreamTests
Assert.Equal(0x10500, ms.Position); //seek plus read bytes
}
}
ArrayPool<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test);
}
[Fact]
public void BufferReadAndSeekTest()
{
byte[] data = ArrayPool<byte>.Shared.Rent(0x100000);
byte[] test = ArrayPool<byte>.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 class SharpCompressStreamTests
Assert.Equal(0x10000, ms.Position); //the base stream has not moved
}
}
ArrayPool<byte>.Shared.Return(data);
ArrayPool<byte>.Shared.Return(test);
}
}

View file

@ -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<byte[]> GetBytesAsync(BufferedStream stream)
{
var bytes = new byte[stream.Length];
await stream.ReadAsync(bytes, 0, (int)stream.Length).ConfigureAwait(false);
return bytes;
}
}

View file

@ -207,13 +207,10 @@ public class TarReaderAsyncTests : ReaderTests
Assert.Throws<IncompleteArchiveException>(() => 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 @@ public class TarReaderAsyncTests : ReaderTests
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");
}
}
}

Binary file not shown.

Binary file not shown.