|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.IO.Hashing
{
/// <summary>
/// Provides an implementation of the CRC-32 algorithm.
/// By default, this implementation uses the ITU-T V.42 / IEEE 802.3 parameter set,
/// but other parameter sets can also be specified.
/// </summary>
/// <remarks>
/// <para>
/// For methods that return byte arrays or that write into spans of bytes, this implementation
/// emits the answer in the byte order that maintains the CRC residue relationship
/// (CRC(message concat CRC(message)) is a fixed value).
/// For CRC-32 as used in IEEE 802.3 this stable output is the byte sequence <c>{ 0x1C, 0xDF, 0x44, 0x21 }</c>,
/// the Little Endian representation of <c>0x2144DF1C</c>.
/// </para>
/// </remarks>
public sealed partial class Crc32 : NonCryptographicHashAlgorithm
{
private const int Size = sizeof(uint);
private uint _crc;
/// <summary>
/// Gets the parameter set used by this instance.
/// </summary>
/// <value>
/// The parameter set used by this instance.
/// </value>
public Crc32ParameterSet ParameterSet { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Crc32"/> class using the ITU-T V.42 / IEEE 802.3 parameters.
/// </summary>
public Crc32()
: base(Size)
{
ParameterSet = Crc32ParameterSet.Crc32;
_crc = ParameterSet.InitialValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="Crc32"/> class using the specified parameters.
/// </summary>
/// <param name="parameterSet">
/// The parameters to use for the CRC computation.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> is <see langword="null"/>.
/// </exception>
public Crc32(Crc32ParameterSet parameterSet)
: base(Size)
{
ArgumentNullException.ThrowIfNull(parameterSet);
ParameterSet = parameterSet;
_crc = parameterSet.InitialValue;
}
/// <summary>Initializes a new instance of the <see cref="Crc32"/> class using the state from another instance.</summary>
private Crc32(uint crc, Crc32ParameterSet parameterSet) : base(Size)
{
_crc = crc;
ParameterSet = parameterSet;
}
/// <summary>Returns a clone of the current instance, with a copy of the current instance's internal state.</summary>
/// <returns>A new instance that will produce the same sequence of values as the current instance.</returns>
public Crc32 Clone() => new(_crc, ParameterSet);
/// <summary>
/// Appends the contents of <paramref name="source"/> to the data already
/// processed for the current hash computation.
/// </summary>
/// <param name="source">The data to process.</param>
public override void Append(ReadOnlySpan<byte> source)
{
_crc = ParameterSet.Update(_crc, source);
}
/// <summary>
/// Resets the hash computation to the initial state.
/// </summary>
public override void Reset()
{
_crc = ParameterSet.InitialValue;
}
/// <summary>
/// Writes the computed hash value to <paramref name="destination"/>
/// without modifying accumulated state.
/// </summary>
/// <param name="destination">The buffer that receives the computed hash value.</param>
protected override void GetCurrentHashCore(Span<byte> destination)
{
ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination);
}
/// <summary>
/// Writes the computed hash value to <paramref name="destination"/>
/// then clears the accumulated state.
/// </summary>
protected override void GetHashAndResetCore(Span<byte> destination)
{
ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination);
_crc = ParameterSet.InitialValue;
}
/// <summary>Gets the current computed hash value without modifying accumulated state.</summary>
/// <returns>The hash value for the data already provided.</returns>
[CLSCompliant(false)]
public uint GetCurrentHashAsUInt32() => ParameterSet.Finalize(_crc);
/// <summary>
/// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <returns>The CRC-32 hash of the provided data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> is <see langword="null"/>.
/// </exception>
public static byte[] Hash(byte[] source)
{
ArgumentNullException.ThrowIfNull(source);
return Hash(new ReadOnlySpan<byte>(source));
}
/// <summary>
/// Computes the CRC-32 hash value for the provided data using the specified parameter set.
/// </summary>
/// <param name="parameterSet">The parameters to use for the CRC computation.</param>
/// <param name="source">The data to hash.</param>
/// <returns>The CRC-32 hash of the provided data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> or <paramref name="source"/> is <see langword="null"/>.
/// </exception>
public static byte[] Hash(Crc32ParameterSet parameterSet, byte[] source)
{
ArgumentNullException.ThrowIfNull(parameterSet);
ArgumentNullException.ThrowIfNull(source);
return Hash(parameterSet, new ReadOnlySpan<byte>(source));
}
/// <summary>
/// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <returns>The CRC-32 hash of the provided data.</returns>
public static byte[] Hash(ReadOnlySpan<byte> source) =>
HashCore(Crc32ParameterSet.Crc32, source);
/// <summary>
/// Computes the CRC-32 hash value for the provided data using the specified parameter set.
/// </summary>
/// <param name="parameterSet">The parameters to use for the CRC computation.</param>
/// <param name="source">The data to hash.</param>
/// <returns>The CRC-32 hash of the provided data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> is <see langword="null"/>.
/// </exception>
public static byte[] Hash(Crc32ParameterSet parameterSet, ReadOnlySpan<byte> source)
{
ArgumentNullException.ThrowIfNull(parameterSet);
return HashCore(parameterSet, source);
}
private static byte[] HashCore(Crc32ParameterSet parameterSet, ReadOnlySpan<byte> source)
{
byte[] ret = new byte[Size];
uint hash = HashToUInt32(parameterSet, source);
parameterSet.WriteCrcToSpan(hash, ret);
return ret;
}
/// <summary>
/// Attempts to compute the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters,
/// into the provided destination.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer that receives the computed hash value.</param>
/// <param name="bytesWritten">
/// On success, receives the number of bytes written to <paramref name="destination"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if <paramref name="destination"/> is long enough to receive
/// the computed hash value (4 bytes); otherwise, <see langword="false"/>.
/// </returns>
public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten) =>
TryHashCore(Crc32ParameterSet.Crc32, source, destination, out bytesWritten);
/// <summary>
/// Attempts to compute the CRC-32 hash of the provided data, using the specified parameter set,
/// into the provided destination.
/// </summary>
/// <param name="parameterSet">The parameters to use for the CRC computation.</param>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer that receives the computed hash value.</param>
/// <param name="bytesWritten">
/// On success, receives the number of bytes written to <paramref name="destination"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if <paramref name="destination"/> is long enough to receive
/// the computed hash value (4 bytes); otherwise, <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> is <see langword="null"/>.
/// </exception>
public static bool TryHash(
Crc32ParameterSet parameterSet,
ReadOnlySpan<byte> source,
Span<byte> destination,
out int bytesWritten)
{
ArgumentNullException.ThrowIfNull(parameterSet);
return TryHashCore(parameterSet, source, destination, out bytesWritten);
}
private static bool TryHashCore(
Crc32ParameterSet parameterSet,
ReadOnlySpan<byte> source,
Span<byte> destination,
out int bytesWritten)
{
if (destination.Length < Size)
{
bytesWritten = 0;
return false;
}
uint hash = HashToUInt32(parameterSet, source);
parameterSet.WriteCrcToSpan(hash, destination);
bytesWritten = Size;
return true;
}
/// <summary>
/// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters,
/// into the provided destination.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer that receives the computed hash value.</param>
/// <returns>
/// The number of bytes written to <paramref name="destination"/>.
/// </returns>
public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination) =>
HashCore(Crc32ParameterSet.Crc32, source, destination);
/// <summary>
/// Computes the CRC-32 hash of the provided data, using the specified parameters,
/// into the provided destination.
/// </summary>
/// <param name="parameterSet">The parameters to use for the CRC computation.</param>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer that receives the computed hash value.</param>
/// <returns>
/// The number of bytes written to <paramref name="destination"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> is <see langword="null"/>.
/// </exception>
public static int Hash(Crc32ParameterSet parameterSet, ReadOnlySpan<byte> source, Span<byte> destination)
{
ArgumentNullException.ThrowIfNull(parameterSet);
return HashCore(parameterSet, source, destination);
}
private static int HashCore(Crc32ParameterSet parameterSet, ReadOnlySpan<byte> source, Span<byte> destination)
{
if (destination.Length < Size)
{
ThrowDestinationTooShort();
}
uint hash = HashToUInt32(parameterSet, source);
parameterSet.WriteCrcToSpan(hash, destination);
return Size;
}
/// <summary>Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters.</summary>
/// <param name="source">The data to hash.</param>
/// <returns>The computed CRC-32 hash.</returns>
[CLSCompliant(false)]
public static uint HashToUInt32(ReadOnlySpan<byte> source)
{
// Rather than go through Crc32ParameterSet.Crc32 to end up in the optimized Update method here,
// just call the Update method directly.
// ITU-T V.42 / IEEE 802.3 uses a final XOR of 0xFFFFFFFF, so accelerate that as ~.
return ~Update(Crc32ParameterSet.Crc32.InitialValue, source);
}
/// <summary>Computes the CRC-32 hash of the provided data, using specified parameters.</summary>
/// <param name="parameterSet">The parameters to use for the CRC computation.</param>
/// <param name="source">The data to hash.</param>
/// <returns>The computed CRC-32 hash.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="parameterSet"/> is <see langword="null"/>.
/// </exception>
[CLSCompliant(false)]
public static uint HashToUInt32(Crc32ParameterSet parameterSet, ReadOnlySpan<byte> source)
{
ArgumentNullException.ThrowIfNull(parameterSet);
uint crc = parameterSet.Update(parameterSet.InitialValue, source);
return parameterSet.Finalize(crc);
}
internal static uint Update(uint crc, ReadOnlySpan<byte> source)
{
#if NET
if (CanBeVectorized(source))
{
return UpdateVectorized(crc, source);
}
#endif
return UpdateScalar(crc, source);
}
private static uint UpdateScalar(uint crc, ReadOnlySpan<byte> source)
{
#if NET
// Use ARM intrinsics for CRC if available. This is used for the trailing bytes on the vectorized path
// and is the primary method if the vectorized path is unavailable.
if (System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported)
{
return UpdateScalarArm64(crc, source);
}
if (System.Runtime.Intrinsics.Arm.Crc32.IsSupported)
{
return UpdateScalarArm32(crc, source);
}
#endif
ReadOnlySpan<uint> crcLookup = CrcLookup;
for (int i = 0; i < source.Length; i++)
{
byte idx = (byte)crc;
idx ^= source[i];
crc = crcLookup[idx] ^ (crc >> 8);
}
return crc;
}
}
}
|