File: System\IO\Hashing\Crc64.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.
 
using System.Buffers.Binary;
 
namespace System.IO.Hashing
{
    /// <summary>
    ///   Provides an implementation of the CRC-64 algorithm.
    ///   By default, this implementation uses the ECMA-182 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-64 as described in ECMA-182, Annex B, this stable output is the byte sequence
    ///     <c>{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }</c>,
    ///     the Big Endian representation of <c>0x0000000000000000</c>.
    ///   </para>
    ///   <para>
    ///     There are multiple, incompatible, definitions of a 64-bit cyclic redundancy
    ///     check (CRC) algorithm. When interoperating with another system, ensure that you
    ///     are using the same definition. The definition used by this implementation is not
    ///     compatible with the cyclic redundancy check described in ISO 3309.
    ///   </para>
    /// </remarks>
    public sealed class Crc64 : NonCryptographicHashAlgorithm
    {
        private const int Size = sizeof(ulong);
 
        private ulong _crc;
 
        /// <summary>
        ///   Gets the parameter set used by this instance.
        /// </summary>
        /// <value>
        ///   The parameter set used by this instance.
        /// </value>
        public Crc64ParameterSet ParameterSet { get; }
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="Crc64"/> class using the ECMA-182 parameters.
        /// </summary>
        public Crc64()
            : base(Size)
        {
            ParameterSet = Crc64ParameterSet.Crc64;
            _crc = ParameterSet.InitialValue;
        }
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="Crc64"/> 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 Crc64(Crc64ParameterSet parameterSet)
            : base(Size)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
 
            ParameterSet = parameterSet;
            _crc = parameterSet.InitialValue;
        }
 
        /// <summary>Initializes a new instance of the <see cref="Crc64"/> class using the state from another instance.</summary>
        private Crc64(ulong crc, Crc64ParameterSet 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 Crc64 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 ulong GetCurrentHashAsUInt64() => ParameterSet.Finalize(_crc);
 
        /// <summary>
        ///   Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>The CRC-64 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-64 hash of the provided data, using the ECMA-182 parameters.
        /// </summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>The CRC-64 hash of the provided data.</returns>
        public static byte[] Hash(ReadOnlySpan<byte> source) =>
            HashCore(Crc64ParameterSet.Crc64, source);
 
        /// <summary>
        ///   Computes the CRC-64 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-64 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(Crc64ParameterSet parameterSet, byte[] source)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
            ArgumentNullException.ThrowIfNull(source);
 
            return Hash(parameterSet, new ReadOnlySpan<byte>(source));
        }
 
        /// <summary>
        ///   Computes the CRC-64 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-64 hash of the provided data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="parameterSet"/> is <see langword="null"/>.
        /// </exception>
        public static byte[] Hash(Crc64ParameterSet parameterSet, ReadOnlySpan<byte> source)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
 
            return HashCore(parameterSet, source);
        }
 
        private static byte[] HashCore(Crc64ParameterSet parameterSet, ReadOnlySpan<byte> source)
        {
            byte[] ret = new byte[Size];
            ulong hash = HashToUInt64(parameterSet, source);
            parameterSet.WriteCrcToSpan(hash, ret);
            return ret;
        }
 
        /// <summary>
        ///   Attempts to compute the CRC-64 hash of the provided data, using the ECMA-182 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 (8 bytes); otherwise, <see langword="false"/>.
        /// </returns>
        public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten) =>
            TryHashCore(Crc64ParameterSet.Crc64, source, destination, out bytesWritten);
 
        /// <summary>
        ///   Attempts to compute the CRC-64 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 (8 bytes); otherwise, <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="parameterSet"/> is <see langword="null"/>.
        /// </exception>
        public static bool TryHash(
            Crc64ParameterSet parameterSet,
            ReadOnlySpan<byte> source,
            Span<byte> destination,
            out int bytesWritten)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
 
            return TryHashCore(parameterSet, source, destination, out bytesWritten);
        }
 
        private static bool TryHashCore(
            Crc64ParameterSet parameterSet,
            ReadOnlySpan<byte> source,
            Span<byte> destination,
            out int bytesWritten)
        {
            if (destination.Length < Size)
            {
                bytesWritten = 0;
                return false;
            }
 
            ulong hash = HashToUInt64(parameterSet, source);
            parameterSet.WriteCrcToSpan(hash, destination);
            bytesWritten = Size;
            return true;
        }
 
        /// <summary>
        ///   Computes the CRC-64 hash of the provided data, using the ECMA-182 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(Crc64ParameterSet.Crc64, source, destination);
 
        /// <summary>
        ///   Computes the CRC-64 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(Crc64ParameterSet parameterSet, ReadOnlySpan<byte> source, Span<byte> destination)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
 
            return HashCore(parameterSet, source, destination);
        }
 
        private static int HashCore(Crc64ParameterSet parameterSet, ReadOnlySpan<byte> source, Span<byte> destination)
        {
            if (destination.Length < Size)
            {
                ThrowDestinationTooShort();
            }
 
            ulong hash = HashToUInt64(parameterSet, source);
            parameterSet.WriteCrcToSpan(hash, destination);
            return Size;
        }
 
        /// <summary>Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters.</summary>
        /// <param name="source">The data to hash.</param>
        /// <returns>The computed CRC-64 hash.</returns>
        [CLSCompliant(false)]
        public static ulong HashToUInt64(ReadOnlySpan<byte> source)
        {
            // Rather than go through Crc64ParameterSet.Crc64 to end up in the optimized Update method here,
            // just call the Update method directly.
            // ECMA-182 uses a final XOR of zero, so directly return the result.
            Crc64ParameterSet parameterSet = Crc64ParameterSet.Crc64;
            return parameterSet.Update(parameterSet.InitialValue, source);
        }
 
        /// <summary>Computes the CRC-64 hash of the provided data, using the 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-64 hash.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="parameterSet"/> is <see langword="null"/>.
        /// </exception>
        [CLSCompliant(false)]
        public static ulong HashToUInt64(Crc64ParameterSet parameterSet, ReadOnlySpan<byte> source)
        {
            ArgumentNullException.ThrowIfNull(parameterSet);
 
            ulong crc = parameterSet.Update(parameterSet.InitialValue, source);
            return parameterSet.Finalize(crc);
        }
    }
}