diff --git a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs index 9665d5fd477038..144f371463c203 100644 --- a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs +++ b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs @@ -26,6 +26,8 @@ public override void Reset() { } public sealed partial class Crc32 : System.IO.Hashing.NonCryptographicHashAlgorithm { public Crc32() : base (default(int)) { } + public Crc32(System.IO.Hashing.Crc32ParameterSet parameterSet) : base (default(int)) { } + public System.IO.Hashing.Crc32ParameterSet ParameterSet { get { throw null; } } public override void Append(System.ReadOnlySpan source) { } public System.IO.Hashing.Crc32 Clone() { throw null; } [System.CLSCompliantAttribute(false)] @@ -33,30 +35,74 @@ public override void Append(System.ReadOnlySpan source) { } protected override void GetCurrentHashCore(System.Span destination) { } protected override void GetHashAndResetCore(System.Span destination) { } public static byte[] Hash(byte[] source) { throw null; } + public static byte[] Hash(System.IO.Hashing.Crc32ParameterSet parameterSet, byte[] source) { throw null; } + public static byte[] Hash(System.IO.Hashing.Crc32ParameterSet parameterSet, System.ReadOnlySpan source) { throw null; } + public static int Hash(System.IO.Hashing.Crc32ParameterSet parameterSet, System.ReadOnlySpan source, System.Span destination) { throw null; } public static byte[] Hash(System.ReadOnlySpan source) { throw null; } public static int Hash(System.ReadOnlySpan source, System.Span destination) { throw null; } [System.CLSCompliantAttribute(false)] + public static uint HashToUInt32(System.IO.Hashing.Crc32ParameterSet parameterSet, System.ReadOnlySpan source) { throw null; } + [System.CLSCompliantAttribute(false)] public static uint HashToUInt32(System.ReadOnlySpan source) { throw null; } public override void Reset() { } + public static bool TryHash(System.IO.Hashing.Crc32ParameterSet parameterSet, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryHash(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } } + public partial class Crc32ParameterSet + { + internal Crc32ParameterSet() { } + public static System.IO.Hashing.Crc32ParameterSet Crc32 { get { throw null; } } + public static System.IO.Hashing.Crc32ParameterSet Crc32C { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public uint FinalXorValue { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public uint InitialValue { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public uint Polynomial { get { throw null; } } + public bool ReflectValues { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public static System.IO.Hashing.Crc32ParameterSet Create(uint polynomial, uint initialValue, uint finalXorValue, bool reflectValues) { throw null; } + } public sealed partial class Crc64 : System.IO.Hashing.NonCryptographicHashAlgorithm { public Crc64() : base (default(int)) { } - public System.IO.Hashing.Crc64 Clone() { throw null; } + public Crc64(System.IO.Hashing.Crc64ParameterSet parameterSet) : base (default(int)) { } + public System.IO.Hashing.Crc64ParameterSet ParameterSet { get { throw null; } } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.Crc64 Clone() { throw null; } [System.CLSCompliantAttribute(false)] public ulong GetCurrentHashAsUInt64() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } protected override void GetHashAndResetCore(System.Span destination) { } public static byte[] Hash(byte[] source) { throw null; } + public static byte[] Hash(System.IO.Hashing.Crc64ParameterSet parameterSet, byte[] source) { throw null; } + public static byte[] Hash(System.IO.Hashing.Crc64ParameterSet parameterSet, System.ReadOnlySpan source) { throw null; } + public static int Hash(System.IO.Hashing.Crc64ParameterSet parameterSet, System.ReadOnlySpan source, System.Span destination) { throw null; } public static byte[] Hash(System.ReadOnlySpan source) { throw null; } public static int Hash(System.ReadOnlySpan source, System.Span destination) { throw null; } [System.CLSCompliantAttribute(false)] + public static ulong HashToUInt64(System.IO.Hashing.Crc64ParameterSet parameterSet, System.ReadOnlySpan source) { throw null; } + [System.CLSCompliantAttribute(false)] public static ulong HashToUInt64(System.ReadOnlySpan source) { throw null; } public override void Reset() { } + public static bool TryHash(System.IO.Hashing.Crc64ParameterSet parameterSet, System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } public static bool TryHash(System.ReadOnlySpan source, System.Span destination, out int bytesWritten) { throw null; } } + public partial class Crc64ParameterSet + { + internal Crc64ParameterSet() { } + public static System.IO.Hashing.Crc64ParameterSet Crc64 { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public ulong FinalXorValue { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public ulong InitialValue { get { throw null; } } + public static System.IO.Hashing.Crc64ParameterSet Nvme { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public ulong Polynomial { get { throw null; } } + public bool ReflectValues { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public static System.IO.Hashing.Crc64ParameterSet Create(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectValues) { throw null; } + } public abstract partial class NonCryptographicHashAlgorithm { protected NonCryptographicHashAlgorithm(int hashLengthInBytes) { } diff --git a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj index 2e0728ff532707..06555e0e92fc41 100644 --- a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj +++ b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj @@ -16,8 +16,14 @@ System.IO.Hashing.XxHash32 + + + + + + diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs index 95dda7501518a1..47cd790cd451fa 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs @@ -1,53 +1,74 @@ // 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 { /// - /// Provides an implementation of the CRC-32 algorithm, as used in - /// ITU-T V.42 and IEEE 802.3. + /// 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. /// /// /// /// For methods that return byte arrays or that write into spans of bytes, this implementation - /// emits the answer in the Little Endian byte order so that the CRC residue relationship - /// (CRC(message concat CRC(message))) is a fixed value) holds. - /// For CRC-32 this stable output is the byte sequence { 0x1C, 0xDF, 0x44, 0x21 }, + /// 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 { 0x1C, 0xDF, 0x44, 0x21 }, /// the Little Endian representation of 0x2144DF1C. /// - /// - /// There are multiple, incompatible, definitions of a 32-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 ITU-T I.363.5. - /// /// public sealed partial class Crc32 : NonCryptographicHashAlgorithm { - private const uint InitialState = 0xFFFF_FFFFu; private const int Size = sizeof(uint); - private uint _crc = InitialState; + private uint _crc; + + /// + /// Gets the parameter set used by this instance. + /// + /// + /// The parameter set used by this instance. + /// + public Crc32ParameterSet ParameterSet { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class using the ITU-T V.42 / IEEE 802.3 parameters. /// public Crc32() : base(Size) { + ParameterSet = Crc32ParameterSet.Crc32; + _crc = ParameterSet.InitialValue; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// + /// The parameters to use for the CRC computation. + /// + /// + /// is . + /// + public Crc32(Crc32ParameterSet parameterSet) + : base(Size) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + ParameterSet = parameterSet; + _crc = parameterSet.InitialValue; } /// Initializes a new instance of the class using the state from another instance. - private Crc32(uint crc) : base(Size) + private Crc32(uint crc, Crc32ParameterSet parameterSet) : base(Size) { _crc = crc; + ParameterSet = parameterSet; } /// Returns a clone of the current instance, with a copy of the current instance's internal state. /// A new instance that will produce the same sequence of values as the current instance. - public Crc32 Clone() => new(_crc); + public Crc32 Clone() => new(_crc, ParameterSet); /// /// Appends the contents of to the data already @@ -56,7 +77,7 @@ private Crc32(uint crc) : base(Size) /// The data to process. public override void Append(ReadOnlySpan source) { - _crc = Update(_crc, source); + _crc = ParameterSet.Update(_crc, source); } /// @@ -64,7 +85,7 @@ public override void Append(ReadOnlySpan source) /// public override void Reset() { - _crc = InitialState; + _crc = ParameterSet.InitialValue; } /// @@ -74,8 +95,7 @@ public override void Reset() /// The buffer that receives the computed hash value. protected override void GetCurrentHashCore(Span destination) { - // The finalization step of the CRC is to perform the ones' complement. - BinaryPrimitives.WriteUInt32LittleEndian(destination, ~_crc); + ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination); } /// @@ -84,17 +104,17 @@ protected override void GetCurrentHashCore(Span destination) /// protected override void GetHashAndResetCore(Span destination) { - BinaryPrimitives.WriteUInt32LittleEndian(destination, ~_crc); - _crc = InitialState; + ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination); + _crc = ParameterSet.InitialValue; } /// Gets the current computed hash value without modifying accumulated state. /// The hash value for the data already provided. [CLSCompliant(false)] - public uint GetCurrentHashAsUInt32() => ~_crc; + public uint GetCurrentHashAsUInt32() => ParameterSet.Finalize(_crc); /// - /// Computes the CRC-32 hash of the provided data. + /// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters. /// /// The data to hash. /// The CRC-32 hash of the provided data. @@ -109,20 +129,57 @@ public static byte[] Hash(byte[] source) } /// - /// Computes the CRC-32 hash of the provided data. + /// Computes the CRC-32 hash value for the provided data using the specified parameter set. /// + /// The parameters to use for the CRC computation. /// The data to hash. /// The CRC-32 hash of the provided data. - public static byte[] Hash(ReadOnlySpan source) + /// + /// or is . + /// + public static byte[] Hash(Crc32ParameterSet parameterSet, byte[] source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + ArgumentNullException.ThrowIfNull(source); + + return Hash(parameterSet, new ReadOnlySpan(source)); + } + + /// + /// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters. + /// + /// The data to hash. + /// The CRC-32 hash of the provided data. + public static byte[] Hash(ReadOnlySpan source) => + HashCore(Crc32ParameterSet.Crc32, source); + + /// + /// Computes the CRC-32 hash value for the provided data using the specified parameter set. + /// + /// The parameters to use for the CRC computation. + /// The data to hash. + /// The CRC-32 hash of the provided data. + /// + /// is . + /// + public static byte[] Hash(Crc32ParameterSet parameterSet, ReadOnlySpan source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return HashCore(parameterSet, source); + } + + private static byte[] HashCore(Crc32ParameterSet parameterSet, ReadOnlySpan source) { byte[] ret = new byte[Size]; - uint hash = HashToUInt32(source); - BinaryPrimitives.WriteUInt32LittleEndian(ret, hash); + uint hash = HashToUInt32(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, ret); return ret; } /// - /// Attempts to compute the CRC-32 hash of the provided data into the provided destination. + /// 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. /// /// The data to hash. /// The buffer that receives the computed hash value. @@ -133,7 +190,42 @@ public static byte[] Hash(ReadOnlySpan source) /// if is long enough to receive /// the computed hash value (4 bytes); otherwise, . /// - public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) + public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) => + TryHashCore(Crc32ParameterSet.Crc32, source, destination, out bytesWritten); + + /// + /// Attempts to compute the CRC-32 hash of the provided data, using the specified parameter set, + /// into the provided destination. + /// + /// The parameters to use for the CRC computation. + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// if is long enough to receive + /// the computed hash value (4 bytes); otherwise, . + /// + /// + /// is . + /// + public static bool TryHash( + Crc32ParameterSet parameterSet, + ReadOnlySpan source, + Span destination, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return TryHashCore(parameterSet, source, destination, out bytesWritten); + } + + private static bool TryHashCore( + Crc32ParameterSet parameterSet, + ReadOnlySpan source, + Span destination, + out int bytesWritten) { if (destination.Length < Size) { @@ -141,40 +233,85 @@ public static bool TryHash(ReadOnlySpan source, Span destination, ou return false; } - uint hash = HashToUInt32(source); - BinaryPrimitives.WriteUInt32LittleEndian(destination, hash); + uint hash = HashToUInt32(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, destination); bytesWritten = Size; return true; } /// - /// Computes the CRC-32 hash of the provided data into the provided destination. + /// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters, + /// into the provided destination. + /// + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// The number of bytes written to . + /// + public static int Hash(ReadOnlySpan source, Span destination) => + HashCore(Crc32ParameterSet.Crc32, source, destination); + + /// + /// Computes the CRC-32 hash of the provided data, using the specified parameters, + /// into the provided destination. /// + /// The parameters to use for the CRC computation. /// The data to hash. /// The buffer that receives the computed hash value. /// /// The number of bytes written to . /// - public static int Hash(ReadOnlySpan source, Span destination) + /// + /// is . + /// + public static int Hash(Crc32ParameterSet parameterSet, ReadOnlySpan source, Span destination) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return HashCore(parameterSet, source, destination); + } + + private static int HashCore(Crc32ParameterSet parameterSet, ReadOnlySpan source, Span destination) { if (destination.Length < Size) { ThrowDestinationTooShort(); } - uint hash = HashToUInt32(source); - BinaryPrimitives.WriteUInt32LittleEndian(destination, hash); + uint hash = HashToUInt32(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, destination); return Size; } - /// Computes the CRC-32 hash of the provided data. + /// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters. + /// The data to hash. + /// The computed CRC-32 hash. + [CLSCompliant(false)] + public static uint HashToUInt32(ReadOnlySpan 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); + } + + /// Computes the CRC-32 hash of the provided data, using specified parameters. + /// The parameters to use for the CRC computation. /// The data to hash. /// The computed CRC-32 hash. + /// + /// is . + /// [CLSCompliant(false)] - public static uint HashToUInt32(ReadOnlySpan source) => - ~Update(InitialState, source); + public static uint HashToUInt32(Crc32ParameterSet parameterSet, ReadOnlySpan source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + uint crc = parameterSet.Update(parameterSet.InitialValue, source); + return parameterSet.Finalize(crc); + } - private static uint Update(uint crc, ReadOnlySpan source) + internal static uint Update(uint crc, ReadOnlySpan source) { #if NET if (CanBeVectorized(source)) diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.Table.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.Table.cs new file mode 100644 index 00000000000000..ad5970a1fddf23 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.Table.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.IO.Hashing +{ + public partial class Crc32ParameterSet + { + private static uint[] GenerateLookupTable(uint polynomial, bool reflectInput) + { + uint[] table = new uint[256]; + + if (!reflectInput) + { + uint crc = 0x80000000u; + + for (int i = 1; i < 256; i <<= 1) + { + if ((crc & 0x80000000u) != 0) + { + crc = (crc << 1) ^ polynomial; + } + else + { + crc <<= 1; + } + + for (int j = 0; j < i; j++) + { + table[i + j] = crc ^ table[j]; + } + } + } + else + { + for (int i = 1; i < 256; i++) + { + uint r = ReverseBits((uint)i); + + const uint LastBit = 0x80000000u; + + for (int j = 0; j < 8; j++) + { + if ((r & LastBit) != 0) + { + r = (r << 1) ^ polynomial; + } + else + { + r <<= 1; + } + } + + table[i] = ReverseBits(r); + } + } + + return table; + } + + private sealed class ReflectedTableBasedCrc32 : Crc32ParameterSet + { + private readonly uint[] _lookupTable; + + internal ReflectedTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: true) + { + _lookupTable = GenerateLookupTable(polynomial, reflectInput: true); + } + + internal override uint Update(uint value, ReadOnlySpan source) + { + uint[] lookupTable = _lookupTable; + uint crc = value; + + Debug.Assert(lookupTable.Length == 256); + + foreach (byte dataByte in source) + { + byte idx = (byte)(crc ^ dataByte); + crc = lookupTable[idx] ^ (crc >> 8); + } + + return crc; + } + } + + private sealed class ForwardTableBasedCrc32 : Crc32ParameterSet + { + private readonly uint[] _lookupTable; + + internal ForwardTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: false) + { + _lookupTable = GenerateLookupTable(polynomial, reflectInput: false); + } + + internal override uint Update(uint value, ReadOnlySpan source) + { + uint[] lookupTable = _lookupTable; + uint crc = value; + + Debug.Assert(lookupTable.Length == 256); + + foreach (byte dataByte in source) + { + byte idx = (byte)((crc >> 24) ^ dataByte); + crc = lookupTable[idx] ^ (crc << 8); + } + + return crc; + } + } + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs new file mode 100644 index 00000000000000..fe4d7a24cffe88 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.IO.Hashing +{ + public partial class Crc32ParameterSet + { + /// + /// Gets the parameter set for the variant of CRC-32 as used in + /// ITU-T V.42 and IEEE 802.3. + /// + /// + /// The parameter set for the variant of CRC-32 as used in + /// ITU-T V.42 and IEEE 802.3. + /// + public static Crc32ParameterSet Crc32 => + field ??= new Ieee8023ParameterSet(); + + /// + /// Gets the parameter set for the CRC-32C variant of CRC-32. + /// + /// + /// The parameter set for the CRC-32C variant of CRC-32. + /// + public static Crc32ParameterSet Crc32C => + field ??= MakeCrc32CParameterSet(); + + private static Crc32ParameterSet MakeCrc32CParameterSet() + { +#if NET + if (System.Runtime.Intrinsics.X86.Sse42.IsSupported || System.Runtime.Intrinsics.Arm.Crc32.IsSupported) + { + return new Crc32CParameterSet(); + } +#endif + + return Create( + polynomial: 0x1edc6f41, + initialValue: 0xffffffff, + finalXorValue: 0xffffffff, + reflectValues: true); + } + + private sealed class Ieee8023ParameterSet : Crc32ParameterSet + { + public Ieee8023ParameterSet() + : base(0x04c11db7, 0xffffffff, 0xffffffff, reflectValues: true) + { + } + + internal override uint Update(uint value, ReadOnlySpan source) => Hashing.Crc32.Update(value, source); + } + +#if NET + private sealed class Crc32CParameterSet : Crc32ParameterSet + { + public Crc32CParameterSet() + : base(0x1edc6f41, 0xffffffff, 0xffffffff, reflectValues: true) + { + } + + internal override uint Update(uint value, ReadOnlySpan source) => UpdateIntrinsic(value, source); + + private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) + { + if (System.Runtime.Intrinsics.X86.Sse42.IsSupported) + { + if (System.Runtime.Intrinsics.X86.Sse42.X64.IsSupported) + { + ReadOnlySpan ulongData = MemoryMarshal.Cast(source); + ulong crc64 = crc; + + foreach (ulong value in ulongData) + { + crc64 = System.Runtime.Intrinsics.X86.Sse42.X64.Crc32(crc64, value); + } + + crc = (uint)crc64; + source = source.Slice(ulongData.Length * sizeof(ulong)); + } + + ReadOnlySpan uintData = MemoryMarshal.Cast(source); + + foreach (uint value in uintData) + { + crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, value); + } + + // SSE 4.2 defines a ushort version as well, but that will only save us one byte, + // so not worth the branch and cast. + + ReadOnlySpan remainingBytes = source.Slice(uintData.Length * sizeof(uint)); + + foreach (byte value in remainingBytes) + { + crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, value); + } + } + else + { + Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported); + ref byte ptr = ref MemoryMarshal.GetReference(source); + int offset = 0; + + if (System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported) + { + int longLength = source.Length & ~0x7; // Exclude trailing bytes not a multiple of 8 + + for (; offset < longLength; offset += sizeof(ulong)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32C( + crc, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + } + } + + int intLength = source.Length & ~0x3; // Exclude trailing bytes not a multiple of 4 + + for (; offset < intLength; offset += sizeof(uint)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C( + crc, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + } + + ReadOnlySpan remainingBytes = source.Slice(offset); + + foreach (byte value in remainingBytes) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C(crc, value); + } + } + + return crc; + } + } +#endif + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs new file mode 100644 index 00000000000000..ade85cb127ec36 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs @@ -0,0 +1,115 @@ +// 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 +{ + /// + /// Represents a set of parameters that define the behavior of a CRC-32 hash algorithm. + /// + /// + /// + /// The parameter-set instance precomputes values to be used in the CRC calculation. + /// As such, callers are expected to create a single instance of the parameter set and + /// reuse it for multiple hash calculations. + /// + /// + public partial class Crc32ParameterSet + { + /// Gets the polynomial value used for the CRC calculation. + /// The polynomial value used for the CRC calculation. + [CLSCompliant(false)] + public uint Polynomial { get; } + + /// Gets the initial value (seed) for the CRC calculation. + /// The initial value (seed) for the CRC calculation. + [CLSCompliant(false)] + public uint InitialValue { get; } + + /// Gets the value to XOR with the final CRC result. + /// The value to XOR with the final CRC result. + /// For reflected-output CRC values, the final XOR is done after the bit-reflection. + [CLSCompliant(false)] + public uint FinalXorValue { get; } + + /// + /// Gets a value indicating whether the input and output bytes are most-significant-bit (MSB) first, or last. + /// + /// + /// if the MSB is the least significant bit of the last byte; + /// if the MSB is the most significant bit of the first byte. + /// + public bool ReflectValues { get; } + + private Crc32ParameterSet(uint polynomial, uint initialValue, uint finalXorValue, bool reflectValues) + { + Polynomial = polynomial; + InitialValue = initialValue; + FinalXorValue = finalXorValue; + ReflectValues = reflectValues; + } + + /// Creates a new with the specified parameters. + /// The polynomial value used for the CRC calculation. + /// The initial value (seed) for the CRC calculation. + /// The value to XOR with the final CRC result. + /// + /// if the input values are least-significant-bit (LSB) first; + /// if the input values are most-significant-bit (MSB) first. + /// + /// A new instance. + [CLSCompliant(false)] + public static Crc32ParameterSet Create( + uint polynomial, + uint initialValue, + uint finalXorValue, + bool reflectValues) + { + return reflectValues ? + new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue) : + new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue); + } + + internal void WriteCrcToSpan(uint crc, Span destination) + { + if (ReflectValues) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination, crc); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(destination, crc); + } + } + + internal virtual uint Update(uint value, ReadOnlySpan source) => + throw new NotImplementedException(); + + internal uint Finalize(uint value) + { + uint crc = value; + + // If, in the future, refIn!=refOut is supported, then the + // answer should (probably) be bit-reversed here before the final XOR. + + return crc ^ FinalXorValue; + } + + private static uint ReverseBits(uint value) + { +#if NET + if (System.Runtime.Intrinsics.Arm.ArmBase.IsSupported) + { + return System.Runtime.Intrinsics.Arm.ArmBase.ReverseElementBits(value); + } +#endif + + value = ((value & 0xAAAAAAAA) >> 1) | ((value & 0x55555555) << 1); + value = ((value & 0xCCCCCCCC) >> 2) | ((value & 0x33333333) << 2); + value = ((value & 0xF0F0F0F0) >> 4) | ((value & 0x0F0F0F0F) << 4); + + return BinaryPrimitives.ReverseEndianness(value); + } + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs index 9de2a30a752b6e..df403e157b9a05 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs @@ -6,15 +6,18 @@ namespace System.IO.Hashing { /// - /// Provides an implementation of the CRC-64 algorithm as described in ECMA-182, Annex B. + /// 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. /// /// /// - /// For methods that return byte arrays or that write into spans of bytes, - /// this implementation emits the answer in the Big Endian byte order so that - /// the CRC residue relationship (CRC(message concat CRC(message))) is a fixed value) holds. - /// For CRC-64 this stable output is the byte sequence - /// { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }. + /// 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 + /// { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + /// the Big Endian representation of 0x0000000000000000. /// /// /// There are multiple, incompatible, definitions of a 64-bit cyclic redundancy @@ -25,28 +28,56 @@ namespace System.IO.Hashing /// public sealed partial class Crc64 : NonCryptographicHashAlgorithm { - private const ulong InitialState = 0UL; private const int Size = sizeof(ulong); - private ulong _crc = InitialState; + private ulong _crc; /// - /// Initializes a new instance of the class. + /// Gets the parameter set used by this instance. + /// + /// + /// The parameter set used by this instance. + /// + public Crc64ParameterSet ParameterSet { get; } + + /// + /// Initializes a new instance of the class using the ECMA-182 parameters. /// public Crc64() : base(Size) { + ParameterSet = Crc64ParameterSet.Crc64; + _crc = ParameterSet.InitialValue; + } + + /// + /// Initializes a new instance of the class using the specified parameters. + /// + /// + /// The parameters to use for the CRC computation. + /// + /// + /// is . + /// + public Crc64(Crc64ParameterSet parameterSet) + : base(Size) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + ParameterSet = parameterSet; + _crc = parameterSet.InitialValue; } /// Initializes a new instance of the class using the state from another instance. - private Crc64(ulong crc) : base(Size) + private Crc64(ulong crc, Crc64ParameterSet parameterSet) : base(Size) { _crc = crc; + ParameterSet = parameterSet; } /// Returns a clone of the current instance, with a copy of the current instance's internal state. /// A new instance that will produce the same sequence of values as the current instance. - public Crc64 Clone() => new(_crc); + public Crc64 Clone() => new(_crc, ParameterSet); /// /// Appends the contents of to the data already @@ -55,7 +86,7 @@ private Crc64(ulong crc) : base(Size) /// The data to process. public override void Append(ReadOnlySpan source) { - _crc = Update(_crc, source); + _crc = ParameterSet.Update(_crc, source); } /// @@ -63,7 +94,7 @@ public override void Append(ReadOnlySpan source) /// public override void Reset() { - _crc = InitialState; + _crc = ParameterSet.InitialValue; } /// @@ -73,7 +104,7 @@ public override void Reset() /// The buffer that receives the computed hash value. protected override void GetCurrentHashCore(Span destination) { - BinaryPrimitives.WriteUInt64BigEndian(destination, _crc); + ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination); } /// @@ -82,17 +113,17 @@ protected override void GetCurrentHashCore(Span destination) /// protected override void GetHashAndResetCore(Span destination) { - BinaryPrimitives.WriteUInt64BigEndian(destination, _crc); - _crc = InitialState; + ParameterSet.WriteCrcToSpan(ParameterSet.Finalize(_crc), destination); + _crc = ParameterSet.InitialValue; } /// Gets the current computed hash value without modifying accumulated state. /// The hash value for the data already provided. [CLSCompliant(false)] - public ulong GetCurrentHashAsUInt64() => _crc; + public ulong GetCurrentHashAsUInt64() => ParameterSet.Finalize(_crc); /// - /// Computes the CRC-64 hash of the provided data. + /// Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters. /// /// The data to hash. /// The CRC-64 hash of the provided data. @@ -107,21 +138,75 @@ public static byte[] Hash(byte[] source) } /// - /// Computes the CRC-64 hash of the provided data. + /// Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters. + /// + /// The data to hash. + /// The CRC-64 hash of the provided data. + public static byte[] Hash(ReadOnlySpan source) => + HashCore(Crc64ParameterSet.Crc64, source); + + /// + /// Computes the CRC-64 hash value for the provided data using the specified parameter set. /// + /// The parameters to use for the CRC computation. /// The data to hash. /// The CRC-64 hash of the provided data. - public static byte[] Hash(ReadOnlySpan source) + /// + /// or is . + /// + public static byte[] Hash(Crc64ParameterSet parameterSet, byte[] source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + ArgumentNullException.ThrowIfNull(source); + + return Hash(parameterSet, new ReadOnlySpan(source)); + } + + /// + /// Computes the CRC-64 hash value for the provided data using the specified parameter set. + /// + /// The parameters to use for the CRC computation. + /// The data to hash. + /// The CRC-64 hash of the provided data. + /// + /// is . + /// + public static byte[] Hash(Crc64ParameterSet parameterSet, ReadOnlySpan source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return HashCore(parameterSet, source); + } + + private static byte[] HashCore(Crc64ParameterSet parameterSet, ReadOnlySpan source) { byte[] ret = new byte[Size]; - ulong hash = HashToUInt64(source); - BinaryPrimitives.WriteUInt64BigEndian(ret, hash); + ulong hash = HashToUInt64(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, ret); return ret; } /// - /// Attempts to compute the CRC-64 hash of the provided data into the provided destination. + /// Attempts to compute the CRC-64 hash of the provided data, using the ECMA-182 parameters, + /// into the provided destination. + /// + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// if is long enough to receive + /// the computed hash value (8 bytes); otherwise, . + /// + public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) => + TryHashCore(Crc64ParameterSet.Crc64, source, destination, out bytesWritten); + + /// + /// Attempts to compute the CRC-64 hash of the provided data, using the specified parameter set, + /// into the provided destination. /// + /// The parameters to use for the CRC computation. /// The data to hash. /// The buffer that receives the computed hash value. /// @@ -131,7 +216,25 @@ public static byte[] Hash(ReadOnlySpan source) /// if is long enough to receive /// the computed hash value (8 bytes); otherwise, . /// - public static bool TryHash(ReadOnlySpan source, Span destination, out int bytesWritten) + /// + /// is . + /// + public static bool TryHash( + Crc64ParameterSet parameterSet, + ReadOnlySpan source, + Span destination, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return TryHashCore(parameterSet, source, destination, out bytesWritten); + } + + private static bool TryHashCore( + Crc64ParameterSet parameterSet, + ReadOnlySpan source, + Span destination, + out int bytesWritten) { if (destination.Length < Size) { @@ -139,40 +242,85 @@ public static bool TryHash(ReadOnlySpan source, Span destination, ou return false; } - ulong hash = HashToUInt64(source); - BinaryPrimitives.WriteUInt64BigEndian(destination, hash); + ulong hash = HashToUInt64(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, destination); bytesWritten = Size; return true; } /// - /// Computes the CRC-64 hash of the provided data into the provided destination. + /// Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters, + /// into the provided destination. /// /// The data to hash. /// The buffer that receives the computed hash value. /// /// The number of bytes written to . /// - public static int Hash(ReadOnlySpan source, Span destination) + public static int Hash(ReadOnlySpan source, Span destination) => + HashCore(Crc64ParameterSet.Crc64, source, destination); + + /// + /// Computes the CRC-64 hash of the provided data, using the specified parameters, + /// into the provided destination. + /// + /// The parameters to use for the CRC computation. + /// The data to hash. + /// The buffer that receives the computed hash value. + /// + /// The number of bytes written to . + /// + /// + /// is . + /// + public static int Hash(Crc64ParameterSet parameterSet, ReadOnlySpan source, Span destination) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + return HashCore(parameterSet, source, destination); + } + + private static int HashCore(Crc64ParameterSet parameterSet, ReadOnlySpan source, Span destination) { if (destination.Length < Size) { ThrowDestinationTooShort(); } - ulong hash = HashToUInt64(source); - BinaryPrimitives.WriteUInt64BigEndian(destination, hash); + ulong hash = HashToUInt64(parameterSet, source); + parameterSet.WriteCrcToSpan(hash, destination); return Size; } - /// Computes the CRC-64 hash of the provided data. + /// Computes the CRC-64 hash of the provided data, using the ECMA-182 parameters. /// The data to hash. /// The computed CRC-64 hash. [CLSCompliant(false)] - public static ulong HashToUInt64(ReadOnlySpan source) => - Update(InitialState, source); + public static ulong HashToUInt64(ReadOnlySpan 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. + return Update(Crc64ParameterSet.Crc64.InitialValue, source); + } + + /// Computes the CRC-64 hash of the provided data, using the specified parameters. + /// The parameters to use for the CRC computation. + /// The data to hash. + /// The computed CRC-64 hash. + /// + /// is . + /// + [CLSCompliant(false)] + public static ulong HashToUInt64(Crc64ParameterSet parameterSet, ReadOnlySpan source) + { + ArgumentNullException.ThrowIfNull(parameterSet); + + ulong crc = parameterSet.Update(parameterSet.InitialValue, source); + return parameterSet.Finalize(crc); + } - private static ulong Update(ulong crc, ReadOnlySpan source) + internal static ulong Update(ulong crc, ReadOnlySpan source) { #if NET if (CanBeVectorized(source)) diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.Table.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.Table.cs new file mode 100644 index 00000000000000..820fc8093fbdc9 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.Table.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.IO.Hashing +{ + public partial class Crc64ParameterSet + { + private static ulong[] GenerateLookupTable(ulong polynomial, bool reflectInput) + { + ulong[] table = new ulong[256]; + + if (!reflectInput) + { + ulong crc = 0x8000000000000000ul; + + for (int i = 1; i < 256; i <<= 1) + { + if ((crc & 0x8000000000000000ul) != 0) + { + crc = (crc << 1) ^ polynomial; + } + else + { + crc <<= 1; + } + + for (int j = 0; j < i; j++) + { + table[i + j] = crc ^ table[j]; + } + } + } + else + { + for (int i = 1; i < 256; i++) + { + ulong r = ReverseBits((ulong)i); + + const ulong LastBit = 0x8000000000000000ul; + + for (int j = 0; j < 8; j++) + { + if ((r & LastBit) != 0) + { + r = (r << 1) ^ polynomial; + } + else + { + r <<= 1; + } + } + + table[i] = ReverseBits(r); + } + } + + return table; + } + + private sealed class ReflectedTableBasedCrc64 : Crc64ParameterSet + { + private readonly ulong[] _lookupTable; + + internal ReflectedTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: true) + { + _lookupTable = GenerateLookupTable(polynomial, reflectInput: true); + } + + internal override ulong Update(ulong value, ReadOnlySpan data) + { + ulong[] lookupTable = _lookupTable; + ulong crc = value; + + Debug.Assert(lookupTable.Length == 256); + + foreach (byte dataByte in data) + { + byte idx = (byte)(crc ^ dataByte); + crc = lookupTable[idx] ^ (crc >> 8); + } + + return crc; + } + } + + private sealed class ForwardTableBasedCrc64 : Crc64ParameterSet + { + private readonly ulong[] _lookupTable; + + internal ForwardTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: false) + { + _lookupTable = GenerateLookupTable(polynomial, reflectInput: false); + } + + internal override ulong Update(ulong value, ReadOnlySpan data) + { + ulong[] lookupTable = _lookupTable; + ulong crc = value; + + foreach (byte dataByte in data) + { + byte idx = (byte)((crc >> 56) ^ dataByte); + crc = lookupTable[idx] ^ (crc << 8); + } + + return crc; + } + } + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.WellKnown.cs new file mode 100644 index 00000000000000..241e3c60a9811d --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.WellKnown.cs @@ -0,0 +1,36 @@ +// 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 +{ + public partial class Crc64ParameterSet + { + /// + /// Gets the parameter set for the ECMA-182 variant of CRC-64. + /// + /// + /// The parameter set for the ECMA-182 variant of CRC-64. + /// + public static Crc64ParameterSet Crc64 => + field ??= new Ecma182ParameterSet(); + + /// + /// Gets the parameter set used for CRC-64 in Non-Volatile Memory Express (NVMe). + /// + /// + /// The parameter set used for CRC-64 in Non-Volatile Memory Express (NVMe). + /// + public static Crc64ParameterSet Nvme => + field ??= Create(0xAD93D23594C93659, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true); + + private sealed class Ecma182ParameterSet : Crc64ParameterSet + { + public Ecma182ParameterSet() + : base(0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false) + { + } + + internal override ulong Update(ulong value, ReadOnlySpan data) => Hashing.Crc64.Update(value, data); + } + } +} diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs new file mode 100644 index 00000000000000..2115236e83a3c1 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs @@ -0,0 +1,115 @@ +// 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 +{ + /// + /// Represents a set of parameters that define the behavior of a CRC-64 hash algorithm. + /// + /// + /// + /// The parameter-set instance precomputes values to be used in the CRC calculation. + /// As such, callers are expected to create a single instance of the parameter set and + /// reuse it for multiple hash calculations. + /// + /// + public partial class Crc64ParameterSet + { + /// Gets the polynomial value used for the CRC calculation. + /// The polynomial value used for the CRC calculation. + [CLSCompliant(false)] + public ulong Polynomial { get; } + + /// Gets the initial value (seed) for the CRC calculation. + /// The initial value (seed) for the CRC calculation. + [CLSCompliant(false)] + public ulong InitialValue { get; } + + /// Gets the value to XOR with the final CRC result. + /// The value to XOR with the final CRC result. + /// For reflected-output CRC values, the final XOR is done after the bit-reflection. + [CLSCompliant(false)] + public ulong FinalXorValue { get; } + + /// + /// Gets a value indicating whether the input and output bytes are most-significant-bit (MSB) first, or last. + /// + /// + /// if the MSB is the least significant bit of the last byte; + /// if the MSB is the most significant bit of the first byte. + /// + public bool ReflectValues { get; } + + private Crc64ParameterSet(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectValues) + { + Polynomial = polynomial; + InitialValue = initialValue; + FinalXorValue = finalXorValue; + ReflectValues = reflectValues; + } + + /// Creates a new with the specified parameters. + /// The polynomial value used for the CRC calculation. + /// The initial value (seed) for the CRC calculation. + /// The value to XOR with the final CRC result. + /// + /// if the input values are least-significant-bit (LSB) first; + /// if the input values are most-significant-bit (MSB) first. + /// + /// A new instance. + [CLSCompliant(false)] + public static Crc64ParameterSet Create( + ulong polynomial, + ulong initialValue, + ulong finalXorValue, + bool reflectValues) + { + return reflectValues ? + new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue) : + new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue); + } + + internal void WriteCrcToSpan(ulong crc, Span destination) + { + if (ReflectValues) + { + BinaryPrimitives.WriteUInt64LittleEndian(destination, crc); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(destination, crc); + } + } + + internal virtual ulong Update(ulong value, ReadOnlySpan source) => + throw new NotImplementedException(); + + internal ulong Finalize(ulong value) + { + ulong crc = value; + + // If, in the future, refIn!=refOut is supported, then the + // answer should (probably) be bit-reversed here before the final XOR. + + return crc ^ FinalXorValue; + } + + private static ulong ReverseBits(ulong value) + { +#if NET + if (System.Runtime.Intrinsics.Arm.ArmBase.Arm64.IsSupported) + { + return System.Runtime.Intrinsics.Arm.ArmBase.Arm64.ReverseElementBits(value); + } +#endif + + value = ((value & 0xAAAAAAAAAAAAAAAA) >> 1) | ((value & 0x5555555555555555) << 1); + value = ((value & 0xCCCCCCCCCCCCCCCC) >> 2) | ((value & 0x3333333333333333) << 2); + value = ((value & 0xF0F0F0F0F0F0F0F0) >> 4) | ((value & 0x0F0F0F0F0F0F0F0F) << 4); + + return BinaryPrimitives.ReverseEndianness(value); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs new file mode 100644 index 00000000000000..6b268d17ae5192 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public static class Crc32ParameterSetTests + { + [Theory] + [InlineData(0x814141ab, 0x00000000, false, 0x00000000, 0x3010bf7f, 0x00000000, "CRC-32/AIXM")] + [InlineData(0xf4acfb13, 0xffffffff, true, 0xffffffff, 0x1697d06a, 0x904cddbf, "CRC-32/AUTOSAR")] + [InlineData(0xa833982b, 0xffffffff, true, 0xffffffff, 0x87315576, 0x45270551, "CRC-32/BASE91-D")] + [InlineData(0x04c11db7, 0xffffffff, false, 0xffffffff, 0xfc891918, 0xc704dd7b, "CRC-32/BZIP2")] + [InlineData(0x8001801b, 0x00000000, true, 0x00000000, 0x6ec2edc4, 0x00000000, "CRC-32/CD-ROM-EDC")] + [InlineData(0x04c11db7, 0x00000000, false, 0xffffffff, 0x765e7680, 0xc704dd7b, "CRC-32/CKSUM")] + [InlineData(0x1edc6f41, 0xffffffff, true, 0xffffffff, 0xe3069283, 0xb798b438, "CRC-32/ISCSI")] + [InlineData(0x04c11db7, 0xffffffff, true, 0xffffffff, 0xcbf43926, 0xdebb20e3, "CRC-32/ISO-HDLC")] + [InlineData(0x04c11db7, 0xffffffff, true, 0x00000000, 0x340bc6d9, 0x00000000, "CRC-32/JAMCRC")] + [InlineData(0x741b8cd7, 0xffffffff, true, 0x00000000, 0xd2c22f51, 0x00000000, "CRC-32/MEF")] + [InlineData(0x04c11db7, 0xffffffff, false, 0x00000000, 0x0376e6e7, 0x00000000, "CRC-32/MPEG-2")] + [InlineData(0x000000af, 0x00000000, false, 0x00000000, 0xbd0be338, 0x00000000, "CRC-32/XFER")] + public static void KnownAnswers( + uint poly, + uint init, + bool refInOut, + uint xorOut, + uint check, + uint residue, + string displayName) + { + _ = displayName; + Crc32ParameterSet crc32 = Crc32ParameterSet.Create(poly, init, xorOut, refInOut); + Assert.Equal(poly, crc32.Polynomial); + Assert.Equal(init, crc32.InitialValue); + Assert.Equal(refInOut, crc32.ReflectValues); + Assert.Equal(xorOut, crc32.FinalXorValue); + + Crc32 hasher = new Crc32(crc32); + hasher.Append("123456789"u8); + Assert.Equal(check, hasher.GetCurrentHashAsUInt32()); + byte[] ret = hasher.GetCurrentHash(); + hasher.Append(ret); + uint final = hasher.GetCurrentHashAsUInt32(); + + // https://reveng.sourceforge.io/crc-catalogue/all.htm defines the residue + // as the value before the final XOR is applied, so we need to final-XOR here + // to get the values to match. + uint finalResidue = final ^ xorOut; + Assert.Equal(residue, finalResidue); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs index bb0d366282755d..43c1fd446d1db7 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs @@ -168,5 +168,20 @@ public void VerifyHashToUInt32(TestCase testCase) AssertEqualHashNumber(testCase.OutputHex, Crc32.HashToUInt32(testCase.Input), littleEndian: true); } + + [Fact] + public void ParameterSetIsRequired() + { + byte[] array = new byte[4]; + AssertExtensions.Throws("parameterSet", () => new Crc32((Crc32ParameterSet)null!)); + AssertExtensions.Throws("parameterSet", () => Crc32.Hash((Crc32ParameterSet)null!, array)); + AssertExtensions.Throws("parameterSet", () => Crc32.Hash((Crc32ParameterSet)null!, array.AsSpan())); + AssertExtensions.Throws("parameterSet", () => Crc32.Hash((Crc32ParameterSet)null!, array, array)); + AssertExtensions.Throws("parameterSet", () => Crc32.HashToUInt32((Crc32ParameterSet)null!, array)); + + int integer = 823; + AssertExtensions.Throws("parameterSet", () => Crc32.TryHash((Crc32ParameterSet)null!, array, array, out integer)); + Assert.Equal(823, integer); + } } } diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs new file mode 100644 index 00000000000000..d5c7a82783bbe8 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public class Crc32Driver : Crc32DriverBase + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Crc32; + + internal override string EmptyOutput => "00000000"; + internal override string Residue => "1CDF4421"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "1BDF05A5", + "Zero" => "1CDF4421", + "Self-test 123456789" => "2639F4CB", + "The quick brown fox jumps over the lazy dog" => "39A34F41", + "Lorem ipsum 128" => "931A6737", + "Lorem ipsum 144" => "2B719549", + "Lorem ipsum 1001" => "0464ED5F", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class CustomCrc32Driver : Crc32Driver + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Create( + polynomial: 0x04C11DB7, + initialValue: 0xFFFFFFFF, + finalXorValue: 0xFFFFFFFF, + reflectValues: true); + } + + public sealed class Crc32Tests_ParameterSet_Crc32 : Crc32Tests_Parameterized + { + [Fact] + public void StaticProperty_IsSingleton() + { + Crc32ParameterSet instance1 = Crc32ParameterSet.Crc32; + Crc32ParameterSet instance2 = Crc32ParameterSet.Crc32; + Assert.Same(instance1, instance2); + } + + [Fact] + public void StaticProperty_HasExpectedValues() + { + Crc32ParameterSet crc32 = Crc32ParameterSet.Crc32; + Assert.Equal(0x04C11DB7u, crc32.Polynomial); + Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); + Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); + Assert.True(crc32.ReflectValues); + } + } + + public sealed class Crc32Tests_ParameterSet_Custom_Crc32 : Crc32Tests_Parameterized + { + [Fact] + public void CreateIsNotSingleton() + { + Assert.NotSame(s_parameterSet, Crc32ParameterSet.Crc32); + } + + [Fact] + public void StaticProperty_HasExpectedValues() + { + Crc32ParameterSet crc32 = Crc32ParameterSet.Crc32; + Assert.Equal(0x04C11DB7u, crc32.Polynomial); + Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); + Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); + Assert.True(crc32.ReflectValues); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs new file mode 100644 index 00000000000000..9b035a1ae920d2 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public class Crc32CDriver : Crc32DriverBase + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Crc32C; + + internal override string EmptyOutput => "00000000"; + internal override string Residue => "C74B6748"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "52D016A0", + "Zero" => "C74B6748", + "Self-test 123456789" => "839206E3", + "The quick brown fox jumps over the lazy dog" => "04046222", + "Lorem ipsum 128" => "189C3883", + "Lorem ipsum 144" => "E7A2AA7A", + "Lorem ipsum 1001" => "104CDF35", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc32Tests_ParameterSet_Crc32C : Crc32Tests_Parameterized + { + [Fact] + public void StaticProperty_IsSingleton() + { + Crc32ParameterSet instance1 = Crc32ParameterSet.Crc32C; + Crc32ParameterSet instance2 = Crc32ParameterSet.Crc32C; + Assert.Same(instance1, instance2); + } + + [Fact] + public void StaticProperty_HasExpectedValues() + { + Crc32ParameterSet crc32 = Crc32ParameterSet.Crc32C; + Assert.Equal(0x1EDC6F41u, crc32.Polynomial); + Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); + Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); + Assert.True(crc32.ReflectValues); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs new file mode 100644 index 00000000000000..3d26be11a883ce --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs @@ -0,0 +1,84 @@ +// 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.Tests +{ + public class Crc32CksumDriver : Crc32DriverBase + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Create( + polynomial: 0x04C11DB7, + initialValue: 0x00000000, + finalXorValue: 0xFFFFFFFF, + reflectValues: false); + + internal override string EmptyOutput => "FFFFFFFF"; + internal override string Residue => "38FB2284"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "FB3EE248", + "Zero" => "FFFFFFFF", + "Self-test 123456789" => "765E7680", + "The quick brown fox jumps over the lazy dog" => "36B78081", + "Lorem ipsum 128" => "CD8EF435", + "Lorem ipsum 144" => "04BD8AF7", + "Lorem ipsum 1001" => "CD98BE63", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc32MefDriver : Crc32DriverBase + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Create( + polynomial: 0x741b8cd7, + initialValue: 0xFFFFFFFF, + finalXorValue: 0x00000000, + reflectValues: true); + + internal override string EmptyOutput => "FFFFFFFF"; + internal override string Residue => "00000000"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "47173856", + "Zero" => "3B324308", + "Self-test 123456789" => "512FC2D2", + "The quick brown fox jumps over the lazy dog" => "6F24DE1F", + "Lorem ipsum 128" => "F4D0B046", + "Lorem ipsum 144" => "14416454", + "Lorem ipsum 1001" => "152A4D10", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc32CDRomEdcDriver : Crc32DriverBase + { + internal override Crc32ParameterSet ParameterSet => Crc32ParameterSet.Create( + polynomial: 0x8001801B, + initialValue: 0x00000000, + finalXorValue: 0x00000000, + reflectValues: true); + + internal override string EmptyOutput => "00000000"; + internal override string Residue => "00000000"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "01019190", + "Zero" => "00000000", + "Self-test 123456789" => "C4EDC26E", + "The quick brown fox jumps over the lazy dog" => "7E1EF9D9", + "Lorem ipsum 128" => "896BC2A4", + "Lorem ipsum 144" => "E204176B", + "Lorem ipsum 1001" => "AC86A81C", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc32Tests_ParameterSet_Custom_Cksum : Crc32Tests_Parameterized; + public class Crc32Tests_ParameterSet_Custom_CDRomEdc : Crc32Tests_Parameterized; + public class Crc32Tests_ParameterSet_Custom_Mef : Crc32Tests_Parameterized; +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs new file mode 100644 index 00000000000000..c268dfd71583a4 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public abstract class Crc32Tests_Parameterized : NonCryptoHashTestDriver + where T : Crc32DriverBase, new() + { + private static readonly Crc32DriverBase s_driver = new T(); + private protected static readonly Crc32ParameterSet s_parameterSet = s_driver.ParameterSet; + + public Crc32Tests_Parameterized() + : base(TestCaseBase.FromHexString(s_driver.EmptyOutput)) + { + } + + public static IEnumerable TestCases + { + get + { + object[] arr = new object[1]; + string residue = s_driver.Residue; + + foreach ((string Name, object Input) testCase in TestCaseDefinitions) + { + string outputHex = testCase.Name switch + { + "Empty" => s_driver.EmptyOutput, + _ => s_driver.GetExpectedOutput(testCase.Name), + }; + + if (outputHex != null) + { + string inputHex; + + if (testCase.Input is byte[] array) + { + arr[0] = new TestCase(testCase.Name, array, outputHex); + inputHex = TestCaseBase.ToHexString(array); + } + else + { + inputHex = (string)testCase.Input; + arr[0] = new TestCase(testCase.Name, inputHex, outputHex); + } + + yield return arr; + + // If, in the future, refIn!=refOut is supported, then the residue and inverse residue test cases + // would need to be skipped, as they are only valid when refIn==refOut. + { + arr[0] = new TestCase(testCase.Name + " Residue", inputHex + outputHex, residue); + yield return arr; + + if (s_parameterSet.FinalXorValue == uint.MaxValue) + { + byte[] outputBytes = TestCaseBase.FromHexString(outputHex); + + for (int i = 0; i < outputBytes.Length; i++) + { + outputBytes[i] ^= 0xFF; + } + + arr[0] = new TestCase( + testCase.Name + " Inverse Residue", + inputHex + TestCaseBase.ToHexString(outputBytes), + "FFFFFFFF"); + + yield return arr; + } + } + } + } + } + } + + private static (string Name, object Input)[] TestCaseDefinitions { get; } = + new (string Name, object Input)[] + { + ("Empty", ""), + ("One", "01"), + ("Zero", "00000000"), + ("Self-test 123456789", "123456789"u8.ToArray()), + ( + "The quick brown fox jumps over the lazy dog", + "The quick brown fox jumps over the lazy dog"u8.ToArray() + ), + // Test a multiple of 64 bytes for vector optimizations + ( + "Lorem ipsum 128", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi quis iaculis nisl. Sed ornare sapien non nulla hendrerit viverra."u8.ToArray() + ), + // Test a multiple of 64 bytes + 16 bytes for vector optimizations + ( + "Lorem ipsum 144", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla libero est, semper in pharetra at, cursus id nulla. Class aptent taciti volutpat."u8.ToArray() + ), + // Test data that is > 64 bytes but not a multiple of 16 for vector optimizations + ( + "Lorem ipsum 1001", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ac urna vitae nibh sagittis porttitor et vel ante. Ut molestie sit amet velit ac mattis. Sed ullamcorper nunc non neque imperdiet, vehicula bibendum sapien efficitur. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse potenti. Duis sem dui, malesuada non pharetra at, feugiat id mi. Nulla facilisi. Fusce a scelerisque magna. Ut leo justo, auctor quis nisi et, sollicitudin pretium odio. Sed eu nibh mollis, pretium lectus nec, posuere nulla. Morbi ac euismod purus. Morbi rhoncus leo est, at volutpat nunc pretium in. Aliquam erat volutpat. Curabitur eu lacus mollis, varius lectus ut, tincidunt eros. Nullam a velit hendrerit, euismod magna id, fringilla sem. Phasellus scelerisque hendrerit est, vel imperdiet enim auctor a. Aenean vel ultricies nunc. Suspendisse ac tincidunt urna. Nulla tempor dolor ut ligula accumsan, tempus auctor massa gravida. Aenean non odio et augue pellena."u8.ToArray() + ), + }; + + protected override NonCryptographicHashAlgorithm CreateInstance() => new Crc32(s_parameterSet); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((Crc32)instance).Clone(); + + protected override byte[] StaticOneShot(byte[] source) => Crc32.Hash(s_parameterSet, source); + + protected override byte[] StaticOneShot(ReadOnlySpan source) => Crc32.Hash(s_parameterSet, source); + + protected override int StaticOneShot(ReadOnlySpan source, Span destination) => + Crc32.Hash(s_parameterSet, source, destination); + + protected override bool TryStaticOneShot(ReadOnlySpan source, Span destination, out int bytesWritten) => + Crc32.TryHash(s_parameterSet, source, destination, out bytesWritten); + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceAppendAllocate(TestCase testCase) + { + InstanceAppendAllocateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceAppendAllocateAndReset(TestCase testCase) + { + InstanceAppendAllocateAndResetDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceMultiAppendGetCurrentHash(TestCase testCase) + { + InstanceMultiAppendGetCurrentHashDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceVerifyEmptyState(TestCase testCase) + { + InstanceVerifyEmptyStateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceVerifyResetState(TestCase testCase) + { + InstanceVerifyResetStateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotArray(TestCase testCase) + { + StaticVerifyOneShotArrayDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotSpanToArray(TestCase testCase) + { + StaticVerifyOneShotSpanToArrayDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotSpanToSpan(TestCase testCase) + { + StaticVerifyOneShotSpanToSpanDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyTryOneShot(TestCase testCase) + { + StaticVerifyTryOneShotDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void VerifyHashToUInt32(TestCase testCase) + { + var alg = new Crc32(s_parameterSet); + alg.Append(testCase.Input); + AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt32(), littleEndian: s_parameterSet.ReflectValues); + AssertEqualHashNumber(testCase.OutputHex, Crc32.HashToUInt32(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectValues); + } + } + + public abstract class Crc32DriverBase + { + internal abstract Crc32ParameterSet ParameterSet { get; } + + internal abstract string EmptyOutput { get; } + internal abstract string Residue { get; } + + internal abstract string? GetExpectedOutput(string testCaseName); + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs b/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs new file mode 100644 index 00000000000000..8fd170465d0c30 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public static class Crc64ParameterSetTests + { + [Theory] + [InlineData(0x42F0E1EBA9EA3693ul, 0x0000000000000000ul, false, 0x0000000000000000ul, 0x6C40DF5F0B497347ul, 0x0000000000000000ul, "CRC-64/ECMA-182")] + [InlineData(0x000000000000001Bul, 0xFFFFFFFFFFFFFFFFul, true, 0xFFFFFFFFFFFFFFFFul, 0xB90956C775A41001ul, 0x5300000000000000ul, "CRC-64/GO-ISO")] + [InlineData(0x259C84CBA6426349ul, 0xFFFFFFFFFFFFFFFFul, true, 0x0000000000000000ul, 0x75D4B74F024ECEEAul, 0x0000000000000000ul, "CRC-64/MS")] + [InlineData(0xAD93D23594C93659ul, 0xFFFFFFFFFFFFFFFFul, true, 0xFFFFFFFFFFFFFFFFul, 0xAE8B14860A799888ul, 0xF310303B2B6F6E42uL, "CRC-64/NVME")] + [InlineData(0xAD93D23594C935A9ul, 0x0000000000000000ul, true, 0x0000000000000000ul, 0xE9C6D914C4B8D9CAul, 0x0000000000000000ul, "CRC-64/REDIS")] + [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, false, 0xFFFFFFFFFFFFFFFFul, 0x62EC59E3F1A4F00Aul, 0xFCACBEBD5931A992ul, "CRC-64/WE")] + [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, true, 0xFFFFFFFFFFFFFFFFul, 0x995DC9BBDF1939FAul, 0x49958C9ABD7D353Ful, "CRC-64/XZ")] + public static void KnownAnswers( + ulong poly, + ulong init, + bool refInOut, + ulong xorOut, + ulong check, + ulong residue, + string displayName) + { + _ = displayName; + Crc64ParameterSet crc64 = Crc64ParameterSet.Create(poly, init, xorOut, refInOut); + Assert.Equal(poly, crc64.Polynomial); + Assert.Equal(init, crc64.InitialValue); + Assert.Equal(refInOut, crc64.ReflectValues); + Assert.Equal(xorOut, crc64.FinalXorValue); + + Crc64 hasher = new Crc64(crc64); + hasher.Append("123456789"u8); + Assert.Equal(check, hasher.GetCurrentHashAsUInt64()); + byte[] ret = hasher.GetCurrentHash(); + hasher.Append(ret); + ulong final = hasher.GetCurrentHashAsUInt64(); + + // https://reveng.sourceforge.io/crc-catalogue/all.htm defines the residue + // as the value before the final XOR is applied, so we need to final-XOR here + // to get the values to match. + ulong finalResidue = final ^ xorOut; + Assert.Equal(residue, finalResidue); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs index fdc01953bd5a38..e3683caba6c506 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs @@ -177,5 +177,20 @@ public void VerifyHashToUInt64(TestCase testCase) AssertEqualHashNumber(testCase.OutputHex, Crc64.HashToUInt64(testCase.Input)); } + + [Fact] + public void ParameterSetIsRequired() + { + byte[] array = new byte[4]; + AssertExtensions.Throws("parameterSet", () => new Crc64((Crc64ParameterSet)null!)); + AssertExtensions.Throws("parameterSet", () => Crc64.Hash((Crc64ParameterSet)null!, array)); + AssertExtensions.Throws("parameterSet", () => Crc64.Hash((Crc64ParameterSet)null!, array.AsSpan())); + AssertExtensions.Throws("parameterSet", () => Crc64.Hash((Crc64ParameterSet)null!, array, array)); + AssertExtensions.Throws("parameterSet", () => Crc64.HashToUInt64((Crc64ParameterSet)null!, array)); + + int integer = 823; + AssertExtensions.Throws("parameterSet", () => Crc64.TryHash((Crc64ParameterSet)null!, array, array, out integer)); + Assert.Equal(823, integer); + } } } diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs new file mode 100644 index 00000000000000..392e522873cf88 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public abstract class Crc64Tests_Parameterized : NonCryptoHashTestDriver + where T : Crc64DriverBase, new() + { + private static readonly Crc64DriverBase s_driver = new T(); + private protected static readonly Crc64ParameterSet s_parameterSet = s_driver.ParameterSet; + + public Crc64Tests_Parameterized() + : base(TestCaseBase.FromHexString(s_driver.EmptyOutput)) + { + } + + public static IEnumerable TestCases + { + get + { + object[] arr = new object[1]; + string residue = s_driver.Residue; + + foreach ((string Name, object Input) testCase in TestCaseDefinitions) + { + string outputHex = testCase.Name switch + { + "Empty" => s_driver.EmptyOutput, + _ => s_driver.GetExpectedOutput(testCase.Name), + }; + + if (outputHex != null) + { + string inputHex; + + if (testCase.Input is byte[] array) + { + arr[0] = new TestCase(testCase.Name, array, outputHex); + inputHex = TestCaseBase.ToHexString(array); + } + else + { + inputHex = (string)testCase.Input; + arr[0] = new TestCase(testCase.Name, inputHex, outputHex); + } + + yield return arr; + + // If, in the future, refIn!=refOut is supported, then the residue and inverse residue test cases + // would need to be skipped, as they are only valid when refIn==refOut. + { + arr[0] = new TestCase(testCase.Name + " Residue", inputHex + outputHex, residue); + yield return arr; + + if (s_parameterSet.FinalXorValue == ulong.MaxValue) + { + byte[] outputBytes = TestCaseBase.FromHexString(outputHex); + + for (int i = 0; i < outputBytes.Length; i++) + { + outputBytes[i] ^= 0xFF; + } + + arr[0] = new TestCase( + testCase.Name + " Inverse Residue", + inputHex + TestCaseBase.ToHexString(outputBytes), + "FFFFFFFFFFFFFFFF"); + + yield return arr; + } + } + } + } + } + } + + private static (string Name, object Input)[] TestCaseDefinitions { get; } = + new (string Name, object Input)[] + { + ("Empty", ""), + ("One", "01"), + ("Zero", "00000000"), + ("{ 0x00 }", "00"), + ("{ 0x01, 0x00 }", "0100"), + ("Self-test 123456789", "123456789"u8.ToArray()), + ( + "The quick brown fox jumps over the lazy dog", + "The quick brown fox jumps over the lazy dog"u8.ToArray() + ), + // Test 256 bytes for vector optimizations + ( + "Lorem ipsum 256", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent non ipsum quis mauris euismod hendrerit id sed lacus. Duis quam neque, porta et volutpat nec, tempor eget nisl. Nunc quis leo quis nisi mattis molestie. Donec a diam velit. Sed a tempus nec."u8.ToArray() + ), + // Test a multiple of 128 bytes greater than 256 bytes + 16 bytes for vector optimizations + ( + "Lorem ipsum 272", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent non ipsum quis mauris euismod hendrerit id sed lacus. Duis quam neque, porta et volutpat nec, tempor eget nisl. Nunc quis leo quis nisi mattis molestie. Donec a diam velit. Sed a tempus nec1234567890abcdef."u8.ToArray() + ), + // Test a multiple of 128 bytes greater than 256 bytes for vector optimizations + ( + "Lorem ipsum 384", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lobortis non felis et pretium. Suspendisse commodo dignissim sagittis. Etiam vestibulum luctus mollis. Ut finibus, nisl in sodales sagittis, leo mauris sollicitudin odio, id sodales nisl ante vitae quam. Nunc ut mi at lacus ultricies efficitur vitae eu ligula. Donec tincidunt, nisi suscipit facilisis auctor, metus non."u8.ToArray() + ), + // Test data that is > 256 bytes but not a multiple of 16 for vector optimizations + ( + "Lorem ipsum 1001", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ac urna vitae nibh sagittis porttitor et vel ante. Ut molestie sit amet velit ac mattis. Sed ullamcorper nunc non neque imperdiet, vehicula bibendum sapien efficitur. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse potenti. Duis sem dui, malesuada non pharetra at, feugiat id mi. Nulla facilisi. Fusce a scelerisque magna. Ut leo justo, auctor quis nisi et, sollicitudin pretium odio. Sed eu nibh mollis, pretium lectus nec, posuere nulla. Morbi ac euismod purus. Morbi rhoncus leo est, at volutpat nunc pretium in. Aliquam erat volutpat. Curabitur eu lacus mollis, varius lectus ut, tincidunt eros. Nullam a velit hendrerit, euismod magna id, fringilla sem. Phasellus scelerisque hendrerit est, vel imperdiet enim auctor a. Aenean vel ultricies nunc. Suspendisse ac tincidunt urna. Nulla tempor dolor ut ligula accumsan, tempus auctor massa gravida. Aenean non odio et augue pellena."u8.ToArray() + ), + }; + + protected override NonCryptographicHashAlgorithm CreateInstance() => new Crc64(s_parameterSet); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((Crc64)instance).Clone(); + + protected override byte[] StaticOneShot(byte[] source) => Crc64.Hash(s_parameterSet, source); + + protected override byte[] StaticOneShot(ReadOnlySpan source) => Crc64.Hash(s_parameterSet, source); + + protected override int StaticOneShot(ReadOnlySpan source, Span destination) => + Crc64.Hash(s_parameterSet, source, destination); + + protected override bool TryStaticOneShot(ReadOnlySpan source, Span destination, out int bytesWritten) => + Crc64.TryHash(s_parameterSet, source, destination, out bytesWritten); + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceAppendAllocate(TestCase testCase) + { + InstanceAppendAllocateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceAppendAllocateAndReset(TestCase testCase) + { + InstanceAppendAllocateAndResetDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceMultiAppendGetCurrentHash(TestCase testCase) + { + InstanceMultiAppendGetCurrentHashDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceVerifyEmptyState(TestCase testCase) + { + InstanceVerifyEmptyStateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void InstanceVerifyResetState(TestCase testCase) + { + InstanceVerifyResetStateDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotArray(TestCase testCase) + { + StaticVerifyOneShotArrayDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotSpanToArray(TestCase testCase) + { + StaticVerifyOneShotSpanToArrayDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyOneShotSpanToSpan(TestCase testCase) + { + StaticVerifyOneShotSpanToSpanDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void StaticVerifyTryOneShot(TestCase testCase) + { + StaticVerifyTryOneShotDriver(testCase); + } + + [Theory] + [MemberData(nameof(TestCases))] + public void VerifyHashToUInt64(TestCase testCase) + { + var alg = new Crc64(s_parameterSet); + alg.Append(testCase.Input); + AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt64(), littleEndian: s_parameterSet.ReflectValues); + AssertEqualHashNumber(testCase.OutputHex, Crc64.HashToUInt64(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectValues); + } + } + + public abstract class Crc64DriverBase + { + internal abstract Crc64ParameterSet ParameterSet { get; } + + internal abstract string EmptyOutput { get; } + internal abstract string Residue { get; } + + internal abstract string? GetExpectedOutput(string testCaseName); + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs new file mode 100644 index 00000000000000..24495e0b63fc33 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public class Crc64Driver : Crc64DriverBase + { + internal override Crc64ParameterSet ParameterSet => Crc64ParameterSet.Crc64; + + internal override string EmptyOutput => "0000000000000000"; + internal override string Residue => "0000000000000000"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "42F0E1EBA9EA3693", + "Zero" => "0000000000000000", + "{ 0x00 }" => "0000000000000000", + "{ 0x01, 0x00 }" => "AF052A6B538EDF09", + "Self-test 123456789" => "6C40DF5F0B497347", + "The quick brown fox jumps over the lazy dog" => "41E05242FFA9883B", + "Lorem ipsum 256" => "DA70046E6B79DD83", + "Lorem ipsum 272" => "A94F5E9C5557F65A", + "Lorem ipsum 384" => "5768E3F2E9A63829", + "Lorem ipsum 1001" => "3ECF3A363FC5BD59", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc64Tests_ParameterSet_Crc64 : Crc64Tests_Parameterized + { + [Fact] + public void StaticProperty_IsSingleton() + { + Crc64ParameterSet instance1 = Crc64ParameterSet.Crc64; + Crc64ParameterSet instance2 = Crc64ParameterSet.Crc64; + Assert.Same(instance1, instance2); + } + + [Fact] + public void StaticProperty_HasExpectedValues() + { + Crc64ParameterSet crc64 = Crc64ParameterSet.Crc64; + Assert.Equal(0x42F0E1EBA9EA3693UL, crc64.Polynomial); + Assert.Equal(0UL, crc64.InitialValue); + Assert.Equal(0UL, crc64.FinalXorValue); + Assert.False(crc64.ReflectValues); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs new file mode 100644 index 00000000000000..a5cf2fb81bd8de --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public class Crc64WEDriver : Crc64DriverBase + { + internal override Crc64ParameterSet ParameterSet => Crc64ParameterSet.Create( + polynomial: 0x42f0e1eba9ea3693, + initialValue: 0xFFFFFFFFFFFFFFFF, + finalXorValue: 0xFFFFFFFFFFFFFFFF, + reflectValues: false); + + internal override string EmptyOutput => "0000000000000000"; + internal override string Residue => "03534142A6CE566D"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "D80C07CD676F836B", + "Zero" => "D2F9D878AC61A52F", + "{ 0x00 }" => "9AFCE626CE85B5F8", + "{ 0x01, 0x00 }" => "4E5F78E1BA3CD74B", + "Self-test 123456789" => "62EC59E3F1A4F00A", + "The quick brown fox jumps over the lazy dog" => "BCD8BB366D256116", + "Lorem ipsum 256" => "E103EC29594D7688", + "Lorem ipsum 272" => "10D41FA7ED684849", + "Lorem ipsum 384" => "225F96A9DD5ED822", + "Lorem ipsum 1001" => "033B46C6C3BC5254", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc64Tests_ParameterSet_Custom_WE : Crc64Tests_Parameterized; +} diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs new file mode 100644 index 00000000000000..91b04fb3d8fb7c --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Hashing.Tests +{ + public class Crc64NvmeDriver : Crc64DriverBase + { + internal override Crc64ParameterSet ParameterSet => Crc64ParameterSet.Nvme; + + internal override string EmptyOutput => "0000000000000000"; + internal override string Residue => "BD9190D4C4CFEF0C"; + + internal override string? GetExpectedOutput(string testCaseName) => + testCaseName switch + { + "One" => "510ED9DF8FA0B4AA", + "Zero" => "2450DAA1E511B86D", + "{ 0x00 }" => "2887ECEF4750DAD5", + "{ 0x01, 0x00 }" => "B195D6A2A931A405", + "Self-test 123456789" => "8898790A86148BAE", + "The quick brown fox jumps over the lazy dog" => "43C1544905546CD7", + "Lorem ipsum 256" => "89E7B0A9DD9C2926", + "Lorem ipsum 272" => "F62B865740AB6502", + "Lorem ipsum 384" => "2759B3D6521D1E41", + "Lorem ipsum 1001" => "12B2C874E65876D4", + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + }; + } + + public class Crc64Tests_ParameterSet_Nvme : Crc64Tests_Parameterized + { + [Fact] + public void StaticProperty_IsSingleton() + { + Crc64ParameterSet instance1 = Crc64ParameterSet.Nvme; + Crc64ParameterSet instance2 = Crc64ParameterSet.Nvme; + Assert.Same(instance1, instance2); + } + + [Fact] + public void StaticProperty_HasExpectedValues() + { + Crc64ParameterSet nvme = Crc64ParameterSet.Nvme; + Assert.Equal(0xAD93D23594C93659UL, nvme.Polynomial); + Assert.Equal(0xFFFFFFFFFFFFFFFFUL, nvme.InitialValue); + Assert.Equal(0xFFFFFFFFFFFFFFFFUL, nvme.FinalXorValue); + Assert.True(nvme.ReflectValues); + } + } +} diff --git a/src/libraries/System.IO.Hashing/tests/System.IO.Hashing.Tests.csproj b/src/libraries/System.IO.Hashing/tests/System.IO.Hashing.Tests.csproj index 2b89d83e62fd26..a7f828f9f409bc 100644 --- a/src/libraries/System.IO.Hashing/tests/System.IO.Hashing.Tests.csproj +++ b/src/libraries/System.IO.Hashing/tests/System.IO.Hashing.Tests.csproj @@ -7,8 +7,18 @@ + + + + + + + + + +