From d937834dd3383e2b469833d883a977db0621968e Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 12 Jan 2026 17:32:25 -0800 Subject: [PATCH 1/6] Add parameterized CRC-32 and CRC-64. Currently, they are just done as byte-by-byte table lookup, except for the `Crc32` and `Crc64` parameter sets, which defer to the already-optimized implementations, and CRC-32/C, which uses x86 and ARM intrinsics. Generalized vectorization will be done as a followup. --- .../ref/System.IO.Hashing.cs | 51 +++- .../src/System.IO.Hashing.csproj | 6 + .../src/System/IO/Hashing/Crc32.cs | 221 ++++++++++++++---- .../IO/Hashing/Crc32ParameterSet.Table.cs | 116 +++++++++ .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 130 +++++++++++ .../System/IO/Hashing/Crc32ParameterSet.cs | 108 +++++++++ .../src/System/IO/Hashing/Crc64.cs | 217 ++++++++++++++--- .../IO/Hashing/Crc64ParameterSet.Table.cs | 114 +++++++++ .../IO/Hashing/Crc64ParameterSet.WellKnown.cs | 24 ++ .../System/IO/Hashing/Crc64ParameterSet.cs | 104 +++++++++ .../tests/Crc32ParameterSetTests.cs | 51 ++++ .../System.IO.Hashing/tests/Crc32Tests.cs | 15 ++ .../tests/Crc32Tests_ParameterSet_Crc32.cs | 80 +++++++ .../tests/Crc32Tests_ParameterSet_Crc32C.cs | 50 ++++ .../tests/Crc32Tests_ParameterSet_Custom.cs | 87 +++++++ .../tests/Crc32Tests_Parameterized.cs | 203 ++++++++++++++++ .../tests/Crc64ParameterSetTests.cs | 46 ++++ .../tests/Crc64Tests_Parameterized.cs | 212 +++++++++++++++++ .../tests/Crc64Tests_Parameterized_Crc64.cs | 53 +++++ .../tests/Crc64Tests_Parameterized_Custom.cs | 39 ++++ .../tests/Crc64Tests_Parameterized_Nvme.cs | 53 +++++ .../tests/System.IO.Hashing.Tests.csproj | 10 + 22 files changed, 1913 insertions(+), 77 deletions(-) create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.Table.cs create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.Table.cs create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.WellKnown.cs create mode 100644 src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs create mode 100644 src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs 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 0f9379ecbb01b8..1a7886f38b3672 100644 --- a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs +++ b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs @@ -9,6 +9,8 @@ namespace System.IO.Hashing 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)] @@ -16,30 +18,77 @@ 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 abstract 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 ReflectInput { get { throw null; } } + public bool ReflectOutput { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public static System.IO.Hashing.Crc32ParameterSet Create(uint polynomial, uint initialValue, uint finalXorValue, bool reflectInput, bool reflectOutput) { 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; } + [System.CLSCompliantAttribute(false)] + 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 abstract 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 ReflectInput { get { throw null; } } + public bool ReflectOutput { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public static System.IO.Hashing.Crc64ParameterSet Create(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectInput, bool reflectOutput) { 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 d41f622de28498..a5af9be7f82eb9 100644 --- a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj +++ b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj @@ -15,9 +15,15 @@ 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..70ec81ee4d28dc 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 CRC32 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 CRC32 hash value for the provided data using the specified parameter set. + /// + /// The data to hash. + /// The parameters to use for the CRC computation. + /// 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 the ITU-T V.42 / IEEE 802.3 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..bb7d01cb8e88a6 --- /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 abstract 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, bool reflectOutput) + : base(polynomial, initialValue, finalXorValue, reflectInput: true, reflectOutput) + { + _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, bool reflectOutput) + : base(polynomial, initialValue, finalXorValue, reflectInput: false, reflectOutput) + { + _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..0971f6c64f4a97 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -0,0 +1,130 @@ +// 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 abstract 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.Sse.IsSupported || System.Runtime.Intrinsics.Arm.Crc32.IsSupported) + { + return new Crc32CParameterSet(); + } +#endif + + return Create( + polynomial: 0x1edc6f41, + initialValue: 0xffffffff, + finalXorValue: 0xffffffff, + reflectInput: true, + reflectOutput: true); + } + + private sealed class Ieee8023ParameterSet : Crc32ParameterSet + { + public Ieee8023ParameterSet() + : base(0x04c11db7, 0xffffffff, 0xffffffff, true, 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, true, 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) + { + ReadOnlySpan uintData = System.Runtime.InteropServices.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.Arm64.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..7f56c94a9ff778 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs @@ -0,0 +1,108 @@ +// 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 +{ + 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 value is treated as 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 ReflectInput { get; } + + /// Gets a value indicating whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// if the output CRC is reflected; otherwise, . + public bool ReflectOutput { get; } + + private Crc32ParameterSet(uint polynomial, uint initialValue, uint finalXorValue, bool reflectInput, bool reflectOutput) + { + Polynomial = polynomial; + InitialValue = initialValue; + FinalXorValue = finalXorValue; + ReflectInput = reflectInput; + ReflectOutput = reflectOutput; + } + + /// 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. + /// Whether the input bytes are reflected (reversed bit order) before processing. + /// Whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// A new instance. + [CLSCompliant(false)] + public static Crc32ParameterSet Create( + uint polynomial, + uint initialValue, + uint finalXorValue, + bool reflectInput, + bool reflectOutput) + { + Crc32ParameterSet set = reflectInput switch + { + false => new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue, reflectOutput), + _ => new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue, reflectOutput), + }; + + return set; + } + + internal void WriteCrcToSpan(uint crc, Span destination) + { + if (ReflectOutput) + { + 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 (ReflectOutput != ReflectInput) + { + crc = ReverseBits(crc); + } + + return crc ^ FinalXorValue; + } + + private static uint ReverseBits(uint value) + { + value = ((value & 0xAAAAAAAA) >> 1) | ((value & 0x55555555) << 1); + value = ((value & 0xCCCCCCCC) >> 2) | ((value & 0x33333333) << 2); + value = ((value & 0xF0F0F0F0) >> 4) | ((value & 0x0F0F0F0F) << 4); + value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); + return (value >> 16) | (value << 16); + } + } +} 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..c2599f88e8bb58 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,20 +138,57 @@ 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. + /// + /// 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 data to hash. + /// The parameters to use for the CRC computation. /// The CRC-64 hash of the provided data. - public static byte[] Hash(ReadOnlySpan source) + /// + /// 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. @@ -131,7 +199,42 @@ 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) + 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. + /// + /// On success, receives the number of bytes written to . + /// + /// + /// if is long enough to receive + /// the computed hash value (8 bytes); otherwise, . + /// + /// + /// 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,86 @@ 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 . + /// + [CLSCompliant(false)] + 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..965f19d6f95c58 --- /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 abstract 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, bool reflectOutput) + : base(polynomial, initialValue, finalXorValue, reflectInput: true, reflectOutput) + { + _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, bool reflectOutput) + : base(polynomial, initialValue, finalXorValue, reflectInput: false, reflectOutput) + { + _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..e6ada9b2532e94 --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.WellKnown.cs @@ -0,0 +1,24 @@ +// 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 abstract partial class Crc64ParameterSet + { + public static Crc64ParameterSet Crc64 => + field ??= new Ecma182ParameterSet(); + + public static Crc64ParameterSet Nvme => + field ??= Create(0xAD93D23594C93659, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true); + + private sealed class Ecma182ParameterSet : Crc64ParameterSet + { + public Ecma182ParameterSet() + : base(0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false, 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..1f14c33a47347d --- /dev/null +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs @@ -0,0 +1,104 @@ +// 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 +{ + public abstract 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 bytes are reflected (reversed bit order) before processing. + /// if the input bytes are reflected; otherwise, . + public bool ReflectInput { get; } + + /// Gets a value indicating whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// if the output CRC is reflected; otherwise, . + public bool ReflectOutput { get; } + + private Crc64ParameterSet(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectInput, bool reflectOutput) + { + Polynomial = polynomial; + InitialValue = initialValue; + FinalXorValue = finalXorValue; + ReflectInput = reflectInput; + ReflectOutput = reflectOutput; + } + + /// 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. + /// Whether the input bytes are reflected (reversed bit order) before processing. + /// Whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// A new instance. + [CLSCompliant(false)] + public static Crc64ParameterSet Create( + ulong polynomial, + ulong initialValue, + ulong finalXorValue, + bool reflectInput, + bool reflectOutput) + { + Crc64ParameterSet set = reflectInput switch + { + false => new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue, reflectOutput), + _ => new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue, reflectOutput), + }; + + return set; + } + + internal void WriteCrcToSpan(ulong crc, Span destination) + { + if (ReflectOutput) + { + 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 (ReflectOutput != ReflectInput) + { + crc = ReverseBits(crc); + } + + return crc ^ FinalXorValue; + } + + private static ulong ReverseBits(ulong value) + { + value = ((value & 0xAAAAAAAAAAAAAAAA) >> 1) | ((value & 0x5555555555555555) << 1); + value = ((value & 0xCCCCCCCCCCCCCCCC) >> 2) | ((value & 0x3333333333333333) << 2); + value = ((value & 0xF0F0F0F0F0F0F0F0) >> 4) | ((value & 0x0F0F0F0F0F0F0F0F) << 4); + value = ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); + value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); + return (value >> 32) | (value << 32); + } + } +} 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..f7d6b162659c58 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs @@ -0,0 +1,51 @@ +// 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, false, 0x00000000, 0x3010bf7f, 0x00000000, "CRC-32/AIXM")] + [InlineData(0xf4acfb13, 0xffffffff, true, true, 0xffffffff, 0x1697d06a, 0x904cddbf, "CRC-32/AUTOSAR")] + [InlineData(0xa833982b, 0xffffffff, true, true, 0xffffffff, 0x87315576, 0x45270551, "CRC-32/BASE91-D")] + [InlineData(0x04c11db7, 0xffffffff, false, false, 0xffffffff, 0xfc891918, 0xc704dd7b, "CRC-32/BZIP2")] + [InlineData(0x8001801b, 0x00000000, true, true, 0x00000000, 0x6ec2edc4, 0x00000000, "CRC-32/CD-ROM-EDC")] + [InlineData(0x04c11db7, 0x00000000, false, false, 0xffffffff, 0x765e7680, 0xc704dd7b, "CRC-32/CKSUM")] + [InlineData(0x1edc6f41, 0xffffffff, true, true, 0xffffffff, 0xe3069283, 0xb798b438, "CRC-32/ISCSI")] + [InlineData(0x04c11db7, 0xffffffff, true, true, 0xffffffff, 0xcbf43926, 0xdebb20e3, "CRC-32/ISO-HDLC")] + [InlineData(0x04c11db7, 0xffffffff, true, true, 0x00000000, 0x340bc6d9, 0x00000000, "CRC-32/JAMCRC")] + [InlineData(0x741b8cd7, 0xffffffff, true, true, 0x00000000, 0xd2c22f51, 0x00000000, "CRC-32/MEF")] + [InlineData(0x04c11db7, 0xffffffff, false, false, 0x00000000, 0x0376e6e7, 0x00000000, "CRC-32/MPEG-2")] + [InlineData(0x000000af, 0x00000000, false, false, 0x00000000, 0xbd0be338, 0x00000000, "CRC-32/XFER")] + public static void KnownAnswers( + uint poly, + uint init, + bool refIn, + bool refOut, + uint xorOut, + uint check, + uint residue, + string displayName) + { + _ = displayName; + Crc32ParameterSet crc32 = Crc32ParameterSet.Create(poly, init, xorOut, refIn, refOut); + Assert.Equal(poly, crc32.Polynomial); + Assert.Equal(init, crc32.InitialValue); + Assert.Equal(refIn, crc32.ReflectInput); + Assert.Equal(refOut, crc32.ReflectOutput); + 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(); + 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..2414f85f3532e4 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs @@ -0,0 +1,80 @@ +// 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, + reflectInput: true, + reflectOutput: 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.ReflectInput); + Assert.True(crc32.ReflectOutput); + } + } + + 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.ReflectInput); + Assert.True(crc32.ReflectOutput); + } + } +} 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..496db1f42e115d --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs @@ -0,0 +1,50 @@ +// 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.ReflectInput); + Assert.True(crc32.ReflectOutput); + } + } +} 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..630bb86b19c095 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs @@ -0,0 +1,87 @@ +// 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, + reflectInput: false, + reflectOutput: 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, + reflectInput: true, + reflectOutput: 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, + reflectInput: true, + reflectOutput: 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..ee10e26e583bd9 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs @@ -0,0 +1,203 @@ +// 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 (s_parameterSet.ReflectOutput == s_parameterSet.ReflectInput) + { + 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.ReflectOutput); + AssertEqualHashNumber(testCase.OutputHex, Crc32.HashToUInt32(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectOutput); + } + } + + 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..76a08ace81abdb --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs @@ -0,0 +1,46 @@ +// 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, false, 0x0000000000000000ul, 0x6C40DF5F0B497347ul, 0x0000000000000000ul, "CRC-64/ECMA-182")] + [InlineData(0x000000000000001Bul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0xB90956C775A41001ul, 0x5300000000000000ul, "CRC-64/GO-ISO")] + [InlineData(0x259C84CBA6426349ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0x0000000000000000ul, 0x75D4B74F024ECEEAul, 0x0000000000000000ul, "CRC-64/MS")] + [InlineData(0xAD93D23594C93659ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0xAE8B14860A799888ul, 0xF310303B2B6F6E42uL, "CRC-64/NVME")] + [InlineData(0xAD93D23594C935A9ul, 0x0000000000000000ul, true, true, 0x0000000000000000ul, 0xE9C6D914C4B8D9CAul, 0x0000000000000000ul, "CRC-64/REDIS")] + [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, false, false, 0xFFFFFFFFFFFFFFFFul, 0x62EC59E3F1A4F00Aul, 0xFCACBEBD5931A992ul, "CRC-64/WE")] + [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0x995DC9BBDF1939FAul, 0x49958C9ABD7D353Ful, "CRC-64/XZ")] + public static void KnownAnswers( + ulong poly, + ulong init, + bool refIn, + bool refOut, + ulong xorOut, + ulong check, + ulong residue, + string displayName) + { + _ = displayName; + Crc64ParameterSet crc64 = Crc64ParameterSet.Create(poly, init, xorOut, refIn, refOut); + Assert.Equal(poly, crc64.Polynomial); + Assert.Equal(init, crc64.InitialValue); + Assert.Equal(refIn, crc64.ReflectInput); + Assert.Equal(refOut, crc64.ReflectOutput); + 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(); + ulong finalResidue = final ^ xorOut; + Assert.Equal(residue, finalResidue); + } + } +} 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..f47bf74c9733d5 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs @@ -0,0 +1,212 @@ +// 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; + + // CRC(data || CRC(data)) only produces a stable value if the + // input and output are similarly reflected. + if (s_parameterSet.ReflectOutput == s_parameterSet.ReflectInput) + { + 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 VerifyHashToUInt32(TestCase testCase) + { + var alg = new Crc64(s_parameterSet); + alg.Append(testCase.Input); + AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt64(), littleEndian: s_parameterSet.ReflectOutput); + AssertEqualHashNumber(testCase.OutputHex, Crc64.HashToUInt64(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectOutput); + } + } + + 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..5b8937b68360e4 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.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 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.ReflectInput); + Assert.False(crc64.ReflectOutput); + } + } +} 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..75335413d3ec75 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs @@ -0,0 +1,39 @@ +// 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, + reflectInput: false, + reflectOutput: 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", + _ => null, + //_ => 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..79b679fe3843b1 --- /dev/null +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.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 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.ReflectInput); + Assert.True(nvme.ReflectOutput); + } + } +} 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 d4ed8b81f7ea42..ee468e058b2231 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 @@ -6,8 +6,18 @@ + + + + + + + + + + From f4b8a5ecc0c854cbe9ea2b6aad84243ed345c750 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 6 Feb 2026 16:54:44 -0800 Subject: [PATCH 2/6] Remove support for refIn!=refOut --- .../ref/System.IO.Hashing.cs | 10 ++--- .../IO/Hashing/Crc32ParameterSet.Table.cs | 8 ++-- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 7 ++- .../System/IO/Hashing/Crc32ParameterSet.cs | 36 +++++++--------- .../IO/Hashing/Crc64ParameterSet.Table.cs | 8 ++-- .../IO/Hashing/Crc64ParameterSet.WellKnown.cs | 4 +- .../System/IO/Hashing/Crc64ParameterSet.cs | 43 +++++++++---------- .../tests/Crc32ParameterSetTests.cs | 36 ++++++++-------- .../tests/Crc32Tests_ParameterSet_Crc32.cs | 9 ++-- .../tests/Crc32Tests_ParameterSet_Crc32C.cs | 3 +- .../tests/Crc32Tests_ParameterSet_Custom.cs | 9 ++-- .../tests/Crc32Tests_Parameterized.cs | 7 +-- .../tests/Crc64ParameterSetTests.cs | 26 +++++------ .../tests/Crc64Tests_Parameterized.cs | 9 ++-- .../tests/Crc64Tests_Parameterized_Crc64.cs | 3 +- .../tests/Crc64Tests_Parameterized_Custom.cs | 6 +-- .../tests/Crc64Tests_Parameterized_Nvme.cs | 3 +- 17 files changed, 105 insertions(+), 122 deletions(-) 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 1a7886f38b3672..948ce5f2882dba 100644 --- a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs +++ b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs @@ -42,10 +42,9 @@ internal Crc32ParameterSet() { } public uint InitialValue { get { throw null; } } [System.CLSCompliantAttribute(false)] public uint Polynomial { get { throw null; } } - public bool ReflectInput { get { throw null; } } - public bool ReflectOutput { 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 reflectInput, bool reflectOutput) { throw null; } + 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 { @@ -84,10 +83,9 @@ internal Crc64ParameterSet() { } public static System.IO.Hashing.Crc64ParameterSet Nvme { get { throw null; } } [System.CLSCompliantAttribute(false)] public ulong Polynomial { get { throw null; } } - public bool ReflectInput { get { throw null; } } - public bool ReflectOutput { 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 reflectInput, bool reflectOutput) { throw null; } + public static System.IO.Hashing.Crc64ParameterSet Create(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectValues) { throw null; } } public abstract partial class NonCryptographicHashAlgorithm { 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 index bb7d01cb8e88a6..62e18e92366595 100644 --- 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 @@ -63,8 +63,8 @@ private sealed class ReflectedTableBasedCrc32 : Crc32ParameterSet { private readonly uint[] _lookupTable; - internal ReflectedTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue, bool reflectOutput) - : base(polynomial, initialValue, finalXorValue, reflectInput: true, reflectOutput) + internal ReflectedTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: true) { _lookupTable = GenerateLookupTable(polynomial, reflectInput: true); } @@ -90,8 +90,8 @@ private sealed class ForwardTableBasedCrc32 : Crc32ParameterSet { private readonly uint[] _lookupTable; - internal ForwardTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue, bool reflectOutput) - : base(polynomial, initialValue, finalXorValue, reflectInput: false, reflectOutput) + internal ForwardTableBasedCrc32(uint polynomial, uint initialValue, uint finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: false) { _lookupTable = GenerateLookupTable(polynomial, reflectInput: false); } 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 index 0971f6c64f4a97..4f47e198604ead 100644 --- 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 @@ -42,14 +42,13 @@ private static Crc32ParameterSet MakeCrc32CParameterSet() polynomial: 0x1edc6f41, initialValue: 0xffffffff, finalXorValue: 0xffffffff, - reflectInput: true, - reflectOutput: true); + reflectValues: true); } private sealed class Ieee8023ParameterSet : Crc32ParameterSet { public Ieee8023ParameterSet() - : base(0x04c11db7, 0xffffffff, 0xffffffff, true, true) + : base(0x04c11db7, 0xffffffff, 0xffffffff, reflectValues: true) { } @@ -60,7 +59,7 @@ public Ieee8023ParameterSet() private sealed class Crc32CParameterSet : Crc32ParameterSet { public Crc32CParameterSet() - : base(0x1edc6f41, 0xffffffff, 0xffffffff, true, true) + : base(0x1edc6f41, 0xffffffff, 0xffffffff, reflectValues: true) { } 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 index 7f56c94a9ff778..541a3ddc461091 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs @@ -24,46 +24,42 @@ public partial class Crc32ParameterSet public uint FinalXorValue { get; } /// - /// Gets a value indicating whether the input value is treated as most significant bit (MSB) first, or last. + /// 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 ReflectInput { get; } + public bool ReflectValues { get; } - /// Gets a value indicating whether the output CRC is reflected (reversed bit order) before applying the final XOR. - /// if the output CRC is reflected; otherwise, . - public bool ReflectOutput { get; } - - private Crc32ParameterSet(uint polynomial, uint initialValue, uint finalXorValue, bool reflectInput, bool reflectOutput) + private Crc32ParameterSet(uint polynomial, uint initialValue, uint finalXorValue, bool reflectValues) { Polynomial = polynomial; InitialValue = initialValue; FinalXorValue = finalXorValue; - ReflectInput = reflectInput; - ReflectOutput = reflectOutput; + 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. - /// Whether the input bytes are reflected (reversed bit order) before processing. - /// Whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// + /// 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 reflectInput, - bool reflectOutput) + bool reflectValues) { - Crc32ParameterSet set = reflectInput switch + Crc32ParameterSet set = reflectValues switch { - false => new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue, reflectOutput), - _ => new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue, reflectOutput), + false => new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue), + _ => new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue), }; return set; @@ -71,7 +67,7 @@ public static Crc32ParameterSet Create( internal void WriteCrcToSpan(uint crc, Span destination) { - if (ReflectOutput) + if (ReflectValues) { BinaryPrimitives.WriteUInt32LittleEndian(destination, crc); } @@ -88,10 +84,8 @@ internal uint Finalize(uint value) { uint crc = value; - if (ReflectOutput != ReflectInput) - { - crc = ReverseBits(crc); - } + // If, in the future, refIn!=refOut is supported, then the + // answer should (probably) be bit-reversed here before the final XOR. return crc ^ FinalXorValue; } 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 index 965f19d6f95c58..ff0536f0371677 100644 --- 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 @@ -63,8 +63,8 @@ private sealed class ReflectedTableBasedCrc64 : Crc64ParameterSet { private readonly ulong[] _lookupTable; - internal ReflectedTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectOutput) - : base(polynomial, initialValue, finalXorValue, reflectInput: true, reflectOutput) + internal ReflectedTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: true) { _lookupTable = GenerateLookupTable(polynomial, reflectInput: true); } @@ -90,8 +90,8 @@ private sealed class ForwardTableBasedCrc64 : Crc64ParameterSet { private readonly ulong[] _lookupTable; - internal ForwardTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectOutput) - : base(polynomial, initialValue, finalXorValue, reflectInput: false, reflectOutput) + internal ForwardTableBasedCrc64(ulong polynomial, ulong initialValue, ulong finalXorValue) + : base(polynomial, initialValue, finalXorValue, reflectValues: false) { _lookupTable = GenerateLookupTable(polynomial, reflectInput: false); } 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 index e6ada9b2532e94..c1a1076e2346f8 100644 --- 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 @@ -9,12 +9,12 @@ public abstract partial class Crc64ParameterSet field ??= new Ecma182ParameterSet(); public static Crc64ParameterSet Nvme => - field ??= Create(0xAD93D23594C93659, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true, true); + field ??= Create(0xAD93D23594C93659, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, true); private sealed class Ecma182ParameterSet : Crc64ParameterSet { public Ecma182ParameterSet() - : base(0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false, false) + : base(0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false) { } 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 index 1f14c33a47347d..bb08e3dc60c48d 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs @@ -23,42 +23,43 @@ public abstract partial class Crc64ParameterSet [CLSCompliant(false)] public ulong FinalXorValue { get; } - /// Gets a value indicating whether the input bytes are reflected (reversed bit order) before processing. - /// if the input bytes are reflected; otherwise, . - public bool ReflectInput { 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; } - /// Gets a value indicating whether the output CRC is reflected (reversed bit order) before applying the final XOR. - /// if the output CRC is reflected; otherwise, . - public bool ReflectOutput { get; } - - private Crc64ParameterSet(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectInput, bool reflectOutput) + private Crc64ParameterSet(ulong polynomial, ulong initialValue, ulong finalXorValue, bool reflectValues) { Polynomial = polynomial; InitialValue = initialValue; FinalXorValue = finalXorValue; - ReflectInput = reflectInput; - ReflectOutput = reflectOutput; + 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. - /// Whether the input bytes are reflected (reversed bit order) before processing. - /// Whether the output CRC is reflected (reversed bit order) before applying the final XOR. + /// + /// 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 reflectInput, - bool reflectOutput) + bool reflectValues) { - Crc64ParameterSet set = reflectInput switch + Crc64ParameterSet set = reflectValues switch { - false => new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue, reflectOutput), - _ => new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue, reflectOutput), + false => new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue), + _ => new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue), }; return set; @@ -66,7 +67,7 @@ public static Crc64ParameterSet Create( internal void WriteCrcToSpan(ulong crc, Span destination) { - if (ReflectOutput) + if (ReflectValues) { BinaryPrimitives.WriteUInt64LittleEndian(destination, crc); } @@ -83,10 +84,8 @@ internal ulong Finalize(ulong value) { ulong crc = value; - if (ReflectOutput != ReflectInput) - { - crc = ReverseBits(crc); - } + // If, in the future, refIn!=refOut is supported, then the + // answer should (probably) be bit-reversed here before the final XOR. return crc ^ FinalXorValue; } diff --git a/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs index f7d6b162659c58..085214253ee407 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs @@ -8,34 +8,32 @@ namespace System.IO.Hashing.Tests public static class Crc32ParameterSetTests { [Theory] - [InlineData(0x814141ab, 0x00000000, false, false, 0x00000000, 0x3010bf7f, 0x00000000, "CRC-32/AIXM")] - [InlineData(0xf4acfb13, 0xffffffff, true, true, 0xffffffff, 0x1697d06a, 0x904cddbf, "CRC-32/AUTOSAR")] - [InlineData(0xa833982b, 0xffffffff, true, true, 0xffffffff, 0x87315576, 0x45270551, "CRC-32/BASE91-D")] - [InlineData(0x04c11db7, 0xffffffff, false, false, 0xffffffff, 0xfc891918, 0xc704dd7b, "CRC-32/BZIP2")] - [InlineData(0x8001801b, 0x00000000, true, true, 0x00000000, 0x6ec2edc4, 0x00000000, "CRC-32/CD-ROM-EDC")] - [InlineData(0x04c11db7, 0x00000000, false, false, 0xffffffff, 0x765e7680, 0xc704dd7b, "CRC-32/CKSUM")] - [InlineData(0x1edc6f41, 0xffffffff, true, true, 0xffffffff, 0xe3069283, 0xb798b438, "CRC-32/ISCSI")] - [InlineData(0x04c11db7, 0xffffffff, true, true, 0xffffffff, 0xcbf43926, 0xdebb20e3, "CRC-32/ISO-HDLC")] - [InlineData(0x04c11db7, 0xffffffff, true, true, 0x00000000, 0x340bc6d9, 0x00000000, "CRC-32/JAMCRC")] - [InlineData(0x741b8cd7, 0xffffffff, true, true, 0x00000000, 0xd2c22f51, 0x00000000, "CRC-32/MEF")] - [InlineData(0x04c11db7, 0xffffffff, false, false, 0x00000000, 0x0376e6e7, 0x00000000, "CRC-32/MPEG-2")] - [InlineData(0x000000af, 0x00000000, false, false, 0x00000000, 0xbd0be338, 0x00000000, "CRC-32/XFER")] + [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 refIn, - bool refOut, + bool refInOut, uint xorOut, uint check, uint residue, string displayName) { _ = displayName; - Crc32ParameterSet crc32 = Crc32ParameterSet.Create(poly, init, xorOut, refIn, refOut); + Crc32ParameterSet crc32 = Crc32ParameterSet.Create(poly, init, xorOut, refInOut); Assert.Equal(poly, crc32.Polynomial); Assert.Equal(init, crc32.InitialValue); - Assert.Equal(refIn, crc32.ReflectInput); - Assert.Equal(refOut, crc32.ReflectOutput); + Assert.Equal(refInOut, crc32.ReflectValues); Assert.Equal(xorOut, crc32.FinalXorValue); Crc32 hasher = new Crc32(crc32); @@ -44,6 +42,10 @@ public static void KnownAnswers( 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_ParameterSet_Crc32.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs index 2414f85f3532e4..2ffc410ff0f01a 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs @@ -32,8 +32,7 @@ public class CustomCrc32Driver : Crc32Driver polynomial: 0x04C11DB7, initialValue: 0xFFFFFFFF, finalXorValue: 0xFFFFFFFF, - reflectInput: true, - reflectOutput: true); + reflectValues: true); } public sealed class Crc32Tests_ParameterSet_Crc32 : Crc32Tests_Parameterized @@ -53,8 +52,7 @@ public void StaticProperty_HasExpectedValues() Assert.Equal(0x04C11DB7u, crc32.Polynomial); Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); - Assert.True(crc32.ReflectInput); - Assert.True(crc32.ReflectOutput); + Assert.True(crc32.ReflectValues); } } @@ -73,8 +71,7 @@ public void StaticProperty_HasExpectedValues() Assert.Equal(0x04C11DB7u, crc32.Polynomial); Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); - Assert.True(crc32.ReflectInput); - Assert.True(crc32.ReflectOutput); + 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 index 496db1f42e115d..7c7b0b8361a1c3 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs @@ -43,8 +43,7 @@ public void StaticProperty_HasExpectedValues() Assert.Equal(0x1EDC6F41u, crc32.Polynomial); Assert.Equal(0xFFFFFFFFu, crc32.InitialValue); Assert.Equal(0xFFFFFFFFu, crc32.FinalXorValue); - Assert.True(crc32.ReflectInput); - Assert.True(crc32.ReflectOutput); + 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 index 630bb86b19c095..0e921816544d08 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs @@ -9,8 +9,7 @@ public class Crc32CksumDriver : Crc32DriverBase polynomial: 0x04C11DB7, initialValue: 0x00000000, finalXorValue: 0xFFFFFFFF, - reflectInput: false, - reflectOutput: false); + reflectValues: false); internal override string EmptyOutput => "FFFFFFFF"; internal override string Residue => "38FB2284"; @@ -35,8 +34,7 @@ public class Crc32MefDriver : Crc32DriverBase polynomial: 0x741b8cd7, initialValue: 0xFFFFFFFF, finalXorValue: 0x00000000, - reflectInput: true, - reflectOutput: true); + reflectValues: true); internal override string EmptyOutput => "FFFFFFFF"; internal override string Residue => "00000000"; @@ -61,8 +59,7 @@ public class Crc32CDRomEdcDriver : Crc32DriverBase polynomial: 0x8001801B, initialValue: 0x00000000, finalXorValue: 0x00000000, - reflectInput: true, - reflectOutput: true); + reflectValues: true); internal override string EmptyOutput => "00000000"; internal override string Residue => "00000000"; diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs index ee10e26e583bd9..2305dcf9932459 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs @@ -49,7 +49,8 @@ public static IEnumerable TestCases yield return arr; - if (s_parameterSet.ReflectOutput == s_parameterSet.ReflectInput) + // 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; @@ -186,8 +187,8 @@ public void VerifyHashToUInt32(TestCase testCase) { var alg = new Crc32(s_parameterSet); alg.Append(testCase.Input); - AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt32(), littleEndian: s_parameterSet.ReflectOutput); - AssertEqualHashNumber(testCase.OutputHex, Crc32.HashToUInt32(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectOutput); + AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt32(), littleEndian: s_parameterSet.ReflectValues); + AssertEqualHashNumber(testCase.OutputHex, Crc32.HashToUInt32(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectValues); } } diff --git a/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs b/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs index 76a08ace81abdb..8fd170465d0c30 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64ParameterSetTests.cs @@ -8,29 +8,27 @@ namespace System.IO.Hashing.Tests public static class Crc64ParameterSetTests { [Theory] - [InlineData(0x42F0E1EBA9EA3693ul, 0x0000000000000000ul, false, false, 0x0000000000000000ul, 0x6C40DF5F0B497347ul, 0x0000000000000000ul, "CRC-64/ECMA-182")] - [InlineData(0x000000000000001Bul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0xB90956C775A41001ul, 0x5300000000000000ul, "CRC-64/GO-ISO")] - [InlineData(0x259C84CBA6426349ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0x0000000000000000ul, 0x75D4B74F024ECEEAul, 0x0000000000000000ul, "CRC-64/MS")] - [InlineData(0xAD93D23594C93659ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0xAE8B14860A799888ul, 0xF310303B2B6F6E42uL, "CRC-64/NVME")] - [InlineData(0xAD93D23594C935A9ul, 0x0000000000000000ul, true, true, 0x0000000000000000ul, 0xE9C6D914C4B8D9CAul, 0x0000000000000000ul, "CRC-64/REDIS")] - [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, false, false, 0xFFFFFFFFFFFFFFFFul, 0x62EC59E3F1A4F00Aul, 0xFCACBEBD5931A992ul, "CRC-64/WE")] - [InlineData(0x42F0E1EBA9EA3693ul, 0xFFFFFFFFFFFFFFFFul, true, true, 0xFFFFFFFFFFFFFFFFul, 0x995DC9BBDF1939FAul, 0x49958C9ABD7D353Ful, "CRC-64/XZ")] + [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 refIn, - bool refOut, + bool refInOut, ulong xorOut, ulong check, ulong residue, string displayName) { _ = displayName; - Crc64ParameterSet crc64 = Crc64ParameterSet.Create(poly, init, xorOut, refIn, refOut); + Crc64ParameterSet crc64 = Crc64ParameterSet.Create(poly, init, xorOut, refInOut); Assert.Equal(poly, crc64.Polynomial); Assert.Equal(init, crc64.InitialValue); - Assert.Equal(refIn, crc64.ReflectInput); - Assert.Equal(refOut, crc64.ReflectOutput); + Assert.Equal(refInOut, crc64.ReflectValues); Assert.Equal(xorOut, crc64.FinalXorValue); Crc64 hasher = new Crc64(crc64); @@ -39,6 +37,10 @@ public static void KnownAnswers( 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_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs index f47bf74c9733d5..39f9ae6700a792 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs @@ -49,9 +49,8 @@ public static IEnumerable TestCases yield return arr; - // CRC(data || CRC(data)) only produces a stable value if the - // input and output are similarly reflected. - if (s_parameterSet.ReflectOutput == s_parameterSet.ReflectInput) + // 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; @@ -195,8 +194,8 @@ public void VerifyHashToUInt32(TestCase testCase) { var alg = new Crc64(s_parameterSet); alg.Append(testCase.Input); - AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt64(), littleEndian: s_parameterSet.ReflectOutput); - AssertEqualHashNumber(testCase.OutputHex, Crc64.HashToUInt64(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectOutput); + AssertEqualHashNumber(testCase.OutputHex, alg.GetCurrentHashAsUInt64(), littleEndian: s_parameterSet.ReflectValues); + AssertEqualHashNumber(testCase.OutputHex, Crc64.HashToUInt64(s_parameterSet, testCase.Input), littleEndian: s_parameterSet.ReflectValues); } } diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs index 5b8937b68360e4..150a13fd94ac6b 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs @@ -46,8 +46,7 @@ public void StaticProperty_HasExpectedValues() Assert.Equal(0x42F0E1EBA9EA3693UL, crc64.Polynomial); Assert.Equal(0UL, crc64.InitialValue); Assert.Equal(0UL, crc64.FinalXorValue); - Assert.False(crc64.ReflectInput); - Assert.False(crc64.ReflectOutput); + 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 index 75335413d3ec75..ef13a6864216e7 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs @@ -11,8 +11,7 @@ public class Crc64WEDriver : Crc64DriverBase polynomial: 0x42f0e1eba9ea3693, initialValue: 0xFFFFFFFFFFFFFFFF, finalXorValue: 0xFFFFFFFFFFFFFFFF, - reflectInput: false, - reflectOutput: false); + reflectValues: false); internal override string EmptyOutput => "0000000000000000"; internal override string Residue => "03534142A6CE566D"; @@ -30,8 +29,7 @@ public class Crc64WEDriver : Crc64DriverBase "Lorem ipsum 272" => "10D41FA7ED684849", "Lorem ipsum 384" => "225F96A9DD5ED822", "Lorem ipsum 1001" => "033B46C6C3BC5254", - _ => null, - //_ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), + _ => throw new ArgumentOutOfRangeException(nameof(testCaseName), testCaseName, "Unmapped Value"), }; } diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs index 79b679fe3843b1..a2211565bd7228 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs @@ -46,8 +46,7 @@ public void StaticProperty_HasExpectedValues() Assert.Equal(0xAD93D23594C93659UL, nvme.Polynomial); Assert.Equal(0xFFFFFFFFFFFFFFFFUL, nvme.InitialValue); Assert.Equal(0xFFFFFFFFFFFFFFFFUL, nvme.FinalXorValue); - Assert.True(nvme.ReflectInput); - Assert.True(nvme.ReflectOutput); + Assert.True(nvme.ReflectValues); } } } From b2c2f00acef2aceb0fdb0179f7974e5b35a46b4f Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Mon, 9 Feb 2026 14:52:38 -0800 Subject: [PATCH 3/6] Respond to feedback --- .../src/System/IO/Hashing/Crc32.cs | 2 +- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 4 ++-- .../System.IO.Hashing/tests/Crc64Tests.cs | 15 +++++++++++++++ .../tests/Crc64Tests_Parameterized.cs | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) 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 70ec81ee4d28dc..7bc48a2a533649 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 @@ -295,7 +295,7 @@ public static uint HashToUInt32(ReadOnlySpan source) return ~Update(Crc32ParameterSet.Crc32.InitialValue, source); } - /// Computes the CRC-32 hash of the provided data, using the ITU-T V.42 / IEEE 802.3 parameters. + /// 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. 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 index 4f47e198604ead..18c15c6dfef1c4 100644 --- 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 @@ -32,7 +32,7 @@ public abstract partial class Crc32ParameterSet private static Crc32ParameterSet MakeCrc32CParameterSet() { #if NET - if (System.Runtime.Intrinsics.X86.Sse.IsSupported || System.Runtime.Intrinsics.Arm.Crc32.IsSupported) + if (System.Runtime.Intrinsics.X86.Sse42.IsSupported || System.Runtime.Intrinsics.Arm.Crc32.IsSupported) { return new Crc32CParameterSet(); } @@ -117,7 +117,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) foreach (byte value in remainingBytes) { - crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32C(crc, value); + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C(crc, value); } } 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 index 39f9ae6700a792..cc5477c6ba6cb9 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs @@ -190,7 +190,7 @@ public void StaticVerifyTryOneShot(TestCase testCase) [Theory] [MemberData(nameof(TestCases))] - public void VerifyHashToUInt32(TestCase testCase) + public void VerifyHashToUInt64(TestCase testCase) { var alg = new Crc64(s_parameterSet); alg.Append(testCase.Input); From 4cfd64d39681fa02275666908d1dad82ff347a7d Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Tue, 10 Feb 2026 17:06:47 -0800 Subject: [PATCH 4/6] Apply feedback --- .../System.IO.Hashing/ref/System.IO.Hashing.cs | 5 ++--- .../System/IO/Hashing/Crc32ParameterSet.Table.cs | 2 +- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 2 +- .../src/System/IO/Hashing/Crc64.cs | 1 - .../System/IO/Hashing/Crc64ParameterSet.Table.cs | 2 +- .../IO/Hashing/Crc64ParameterSet.WellKnown.cs | 14 +++++++++++++- .../src/System/IO/Hashing/Crc64ParameterSet.cs | 2 +- .../tests/Crc32ParameterSetTests.cs | 2 +- .../tests/Crc32Tests_ParameterSet_Crc32.cs | 2 +- .../tests/Crc32Tests_ParameterSet_Crc32C.cs | 2 +- .../tests/Crc32Tests_ParameterSet_Custom.cs | 2 +- .../tests/Crc32Tests_Parameterized.cs | 2 +- .../tests/Crc64Tests_Parameterized.cs | 2 +- .../tests/Crc64Tests_Parameterized_Crc64.cs | 2 +- .../tests/Crc64Tests_Parameterized_Custom.cs | 2 +- .../tests/Crc64Tests_Parameterized_Nvme.cs | 2 +- 16 files changed, 28 insertions(+), 18 deletions(-) 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 948ce5f2882dba..b147f56c6b788b 100644 --- a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs +++ b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs @@ -31,7 +31,7 @@ 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 abstract partial class Crc32ParameterSet + public partial class Crc32ParameterSet { internal Crc32ParameterSet() { } public static System.IO.Hashing.Crc32ParameterSet Crc32 { get { throw null; } } @@ -60,7 +60,6 @@ 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; } - [System.CLSCompliantAttribute(false)] 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; } @@ -72,7 +71,7 @@ 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 abstract partial class Crc64ParameterSet + public partial class Crc64ParameterSet { internal Crc64ParameterSet() { } public static System.IO.Hashing.Crc64ParameterSet Crc64 { get { throw null; } } 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 index 62e18e92366595..ad5970a1fddf23 100644 --- 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 @@ -5,7 +5,7 @@ namespace System.IO.Hashing { - public abstract partial class Crc32ParameterSet + public partial class Crc32ParameterSet { private static uint[] GenerateLookupTable(uint polynomial, bool reflectInput) { 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 index 18c15c6dfef1c4..32da226402d6a7 100644 --- 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 @@ -7,7 +7,7 @@ namespace System.IO.Hashing { - public abstract partial class Crc32ParameterSet + public partial class Crc32ParameterSet { /// /// Gets the parameter set for the variant of CRC-32 as used in 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 c2599f88e8bb58..e42a08639d84f2 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 @@ -273,7 +273,6 @@ public static int Hash(ReadOnlySpan source, Span destination) => /// /// is . /// - [CLSCompliant(false)] public static int Hash(Crc64ParameterSet parameterSet, ReadOnlySpan source, Span destination) { ArgumentNullException.ThrowIfNull(parameterSet); 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 index ff0536f0371677..820fc8093fbdc9 100644 --- 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 @@ -5,7 +5,7 @@ namespace System.IO.Hashing { - public abstract partial class Crc64ParameterSet + public partial class Crc64ParameterSet { private static ulong[] GenerateLookupTable(ulong polynomial, bool reflectInput) { 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 index c1a1076e2346f8..241e3c60a9811d 100644 --- 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 @@ -3,11 +3,23 @@ namespace System.IO.Hashing { - public abstract partial class Crc64ParameterSet + 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); 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 index bb08e3dc60c48d..98e3c0087f0601 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs @@ -5,7 +5,7 @@ namespace System.IO.Hashing { - public abstract partial class Crc64ParameterSet + public partial class Crc64ParameterSet { /// Gets the polynomial value used for the CRC calculation. /// The polynomial value used for the CRC calculation. diff --git a/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs index 085214253ee407..6b268d17ae5192 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32ParameterSetTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs index 2ffc410ff0f01a..d5c7a82783bbe8 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs index 7c7b0b8361a1c3..9b035a1ae920d2 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Crc32C.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs index 0e921816544d08..3d26be11a883ce 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_ParameterSet_Custom.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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 diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs index 2305dcf9932459..c268dfd71583a4 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests_Parameterized.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs index cc5477c6ba6cb9..392e522873cf88 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs index 150a13fd94ac6b..24495e0b63fc33 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Crc64.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs index ef13a6864216e7..a5cf2fb81bd8de 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Custom.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs index a2211565bd7228..91b04fb3d8fb7c 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests_Parameterized_Nvme.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Xunit; From bf27036bdf3efaa848c3c992caa66522dd612f60 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 12 Feb 2026 16:24:09 -0800 Subject: [PATCH 5/6] Apply feedback --- .../src/System.IO.Hashing.csproj | 2 +- .../src/System/IO/Hashing/Crc32.cs | 4 +-- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 16 +++++++++- .../System/IO/Hashing/Crc32ParameterSet.cs | 31 ++++++++++++------ .../System/IO/Hashing/Crc64ParameterSet.cs | 32 +++++++++++++------ 5 files changed, 62 insertions(+), 23 deletions(-) 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 a5af9be7f82eb9..46d78202a71803 100644 --- a/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj +++ b/src/libraries/System.IO.Hashing/src/System.IO.Hashing.csproj @@ -16,6 +16,7 @@ System.IO.Hashing.XxHash32 + @@ -23,7 +24,6 @@ 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 7bc48a2a533649..0954d506a01826 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 @@ -129,7 +129,7 @@ public static byte[] Hash(byte[] source) } /// - /// Computes the CRC32 hash value for the provided data using the specified parameter set. + /// 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. @@ -154,7 +154,7 @@ public static byte[] Hash(ReadOnlySpan source) => HashCore(Crc32ParameterSet.Crc32, source); /// - /// Computes the CRC32 hash value for the provided data using the specified parameter set. + /// Computes the CRC-32 hash value for the provided data using the specified parameter set. /// /// The data to hash. /// The parameters to use for the CRC computation. 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 index 32da226402d6a7..fe4d7a24cffe88 100644 --- 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 @@ -69,7 +69,21 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { if (System.Runtime.Intrinsics.X86.Sse42.IsSupported) { - ReadOnlySpan uintData = System.Runtime.InteropServices.MemoryMarshal.Cast(source); + 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) { 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 index 541a3ddc461091..ade85cb127ec36 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.cs @@ -5,6 +5,16 @@ 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. @@ -56,13 +66,9 @@ public static Crc32ParameterSet Create( uint finalXorValue, bool reflectValues) { - Crc32ParameterSet set = reflectValues switch - { - false => new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue), - _ => new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue), - }; - - return set; + return reflectValues ? + new ReflectedTableBasedCrc32(polynomial, initialValue, finalXorValue) : + new ForwardTableBasedCrc32(polynomial, initialValue, finalXorValue); } internal void WriteCrcToSpan(uint crc, Span destination) @@ -92,11 +98,18 @@ internal uint Finalize(uint value) 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); - value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); - return (value >> 16) | (value << 16); + + return BinaryPrimitives.ReverseEndianness(value); } } } 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 index 98e3c0087f0601..2115236e83a3c1 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64ParameterSet.cs @@ -5,6 +5,16 @@ 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. @@ -56,13 +66,9 @@ public static Crc64ParameterSet Create( ulong finalXorValue, bool reflectValues) { - Crc64ParameterSet set = reflectValues switch - { - false => new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue), - _ => new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue), - }; - - return set; + return reflectValues ? + new ReflectedTableBasedCrc64(polynomial, initialValue, finalXorValue) : + new ForwardTableBasedCrc64(polynomial, initialValue, finalXorValue); } internal void WriteCrcToSpan(ulong crc, Span destination) @@ -92,12 +98,18 @@ internal ulong Finalize(ulong value) 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); - value = ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); - value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); - return (value >> 32) | (value << 32); + + return BinaryPrimitives.ReverseEndianness(value); } } } From 2138b2f661e5c5300e65dc22d06881364f23f9d7 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 13 Feb 2026 12:19:18 -0800 Subject: [PATCH 6/6] Fix xmldoc ordering --- src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs | 2 +- src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 0954d506a01826..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 @@ -156,8 +156,8 @@ public static byte[] Hash(ReadOnlySpan source) => /// /// Computes the CRC-32 hash value for the provided data using the specified parameter set. /// - /// The data to hash. /// The parameters to use for the CRC computation. + /// The data to hash. /// The CRC-32 hash of the provided data. /// /// is . 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 e42a08639d84f2..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 @@ -165,8 +165,8 @@ public static byte[] Hash(Crc64ParameterSet parameterSet, byte[] source) /// /// Computes the CRC-64 hash value for the provided data using the specified parameter set. /// - /// The data to hash. /// The parameters to use for the CRC computation. + /// The data to hash. /// The CRC-64 hash of the provided data. /// /// is .