mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-09 02:26:47 +00:00
179 lines
5.0 KiB
C#
179 lines
5.0 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using SharpCompress.Crypto;
|
|
|
|
namespace SharpCompress.Common;
|
|
|
|
internal sealed class ChecksumValidationStream : Stream
|
|
{
|
|
private readonly Stream _stream;
|
|
private readonly ChecksumDescriptor _checksum;
|
|
private readonly string _entryName;
|
|
private readonly uint[] _crc32Table;
|
|
private uint _seed = Crc32Stream.DEFAULT_SEED;
|
|
private ushort _crc16;
|
|
private bool _validated;
|
|
|
|
internal ChecksumValidationStream(Stream stream, ChecksumDescriptor checksum, string? entryName)
|
|
{
|
|
_stream = stream;
|
|
_checksum = checksum;
|
|
_entryName = string.IsNullOrEmpty(entryName) ? "Entry" : entryName!;
|
|
_crc32Table = Crc32Stream.InitializeTable(Crc32Stream.DEFAULT_POLYNOMIAL);
|
|
}
|
|
|
|
public override bool CanRead => _stream.CanRead;
|
|
public override bool CanSeek => false;
|
|
public override bool CanWrite => false;
|
|
public override long Length => _stream.Length;
|
|
|
|
public override long Position
|
|
{
|
|
get => _stream.Position;
|
|
set => throw new NotSupportedException();
|
|
}
|
|
|
|
public override void Flush() => _stream.Flush();
|
|
|
|
public override Task FlushAsync(CancellationToken cancellationToken) =>
|
|
_stream.FlushAsync(cancellationToken);
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
var read = _stream.Read(buffer, offset, count);
|
|
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
|
return read;
|
|
}
|
|
|
|
#if !LEGACY_DOTNET
|
|
public override int Read(Span<byte> buffer)
|
|
{
|
|
var read = _stream.Read(buffer);
|
|
UpdateAndValidateAtEof(buffer[..read], read);
|
|
return read;
|
|
}
|
|
#endif
|
|
|
|
public override int ReadByte() => throw new NotSupportedException();
|
|
|
|
public override async Task<int> ReadAsync(
|
|
byte[] buffer,
|
|
int offset,
|
|
int count,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var read = await _stream
|
|
.ReadAsync(buffer, offset, count, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
|
|
return read;
|
|
}
|
|
|
|
#if !LEGACY_DOTNET
|
|
public override async ValueTask<int> ReadAsync(
|
|
Memory<byte> buffer,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
var read = await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
|
UpdateAndValidateAtEof(buffer.Span[..read], read);
|
|
return read;
|
|
}
|
|
#endif
|
|
|
|
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
|
|
|
public override void SetLength(long value) => throw new NotSupportedException();
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) =>
|
|
throw new NotSupportedException();
|
|
|
|
private void UpdateAndValidateAtEof(ReadOnlySpan<byte> buffer, int read)
|
|
{
|
|
if (read > 0)
|
|
{
|
|
UpdateChecksum(buffer);
|
|
return;
|
|
}
|
|
|
|
Validate();
|
|
}
|
|
|
|
private void UpdateChecksum(ReadOnlySpan<byte> buffer)
|
|
{
|
|
switch (_checksum.Kind)
|
|
{
|
|
case ChecksumKind.Crc32:
|
|
case ChecksumKind.Crc32NoFinalXor:
|
|
_seed = Crc32Stream.CalculateCrc(_crc32Table, _seed, buffer);
|
|
break;
|
|
case ChecksumKind.Crc16Arc:
|
|
_crc16 = CalculateCrc16Arc(_crc16, buffer);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void Validate()
|
|
{
|
|
if (_validated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_validated = true;
|
|
|
|
switch (_checksum.Kind)
|
|
{
|
|
case ChecksumKind.Crc32:
|
|
ValidateCrc32(finalXor: true);
|
|
break;
|
|
case ChecksumKind.Crc32NoFinalXor:
|
|
ValidateCrc32(finalXor: false);
|
|
break;
|
|
case ChecksumKind.Crc16Arc:
|
|
ValidateCrc16Arc();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void ValidateCrc32(bool finalXor)
|
|
{
|
|
var actual = finalXor ? ~_seed : _seed;
|
|
var expected = unchecked((uint)_checksum.ExpectedValue);
|
|
if (actual != expected)
|
|
{
|
|
throw new InvalidFormatException(
|
|
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expected:X8}, actual 0x{actual:X8}."
|
|
);
|
|
}
|
|
}
|
|
|
|
private void ValidateCrc16Arc()
|
|
{
|
|
var expected = unchecked((ushort)_checksum.ExpectedValue);
|
|
if (_crc16 != expected)
|
|
{
|
|
throw new InvalidFormatException(
|
|
$"CRC mismatch for entry '{_entryName}'. Expected 0x{expected:X4}, actual 0x{_crc16:X4}."
|
|
);
|
|
}
|
|
}
|
|
|
|
private static ushort CalculateCrc16Arc(ushort crc, ReadOnlySpan<byte> buffer)
|
|
{
|
|
foreach (var value in buffer)
|
|
{
|
|
crc ^= value;
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
|
|
}
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
}
|