File: System\IO\Hashing\Crc32.cs
Web Access
Project: src\src\libraries\System.IO.Hashing\src\System.IO.Hashing.csproj (System.IO.Hashing)
// 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;
        }
    }
}