From 3d275542626ac6420269168ecbebe2bcb8e6ae54 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 3 Mar 2025 16:01:29 -0500 Subject: [PATCH] Add NonCryptographicHashAlgorithm Clone methods (#113087) --- .../ref/System.IO.Hashing.cs | 6 +++++ .../src/System/IO/Hashing/Crc32.cs | 10 +++++++ .../src/System/IO/Hashing/Crc64.cs | 10 +++++++ .../src/System/IO/Hashing/XxHash128.cs | 10 +++++++ .../src/System/IO/Hashing/XxHash3.cs | 10 +++++++ .../src/System/IO/Hashing/XxHash32.cs | 17 ++++++++++++ .../src/System/IO/Hashing/XxHash64.cs | 17 ++++++++++++ .../System.IO.Hashing/tests/Crc32Tests.cs | 1 + .../System.IO.Hashing/tests/Crc64Tests.cs | 1 + .../tests/NonCryptoHashTestDriver.cs | 26 +++++++++++++++++++ .../System.IO.Hashing/tests/XxHash128Tests.cs | 25 ++++++++++++++++++ .../tests/XxHash32Tests.007.cs | 1 + .../System.IO.Hashing/tests/XxHash32Tests.cs | 1 + .../tests/XxHash32Tests.f00d.cs | 1 + .../System.IO.Hashing/tests/XxHash3Tests.cs | 25 ++++++++++++++++++ .../tests/XxHash64Tests.007.cs | 1 + .../System.IO.Hashing/tests/XxHash64Tests.cs | 1 + .../tests/XxHash64Tests.f00d.cs | 1 + 18 files changed, 164 insertions(+) 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 88312d3dcf73d6..0f9379ecbb01b8 100644 --- a/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs +++ b/src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs @@ -10,6 +10,7 @@ public sealed partial class Crc32 : System.IO.Hashing.NonCryptographicHashAlgori { public Crc32() : base (default(int)) { } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.Crc32 Clone() { throw null; } [System.CLSCompliantAttribute(false)] public uint GetCurrentHashAsUInt32() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } @@ -25,6 +26,7 @@ public override void Reset() { } public sealed partial class Crc64 : System.IO.Hashing.NonCryptographicHashAlgorithm { public Crc64() : base (default(int)) { } + public System.IO.Hashing.Crc64 Clone() { throw null; } public override void Append(System.ReadOnlySpan source) { } [System.CLSCompliantAttribute(false)] public ulong GetCurrentHashAsUInt64() { throw null; } @@ -64,6 +66,7 @@ public sealed partial class XxHash128 : System.IO.Hashing.NonCryptographicHashAl public XxHash128() : base (default(int)) { } public XxHash128(long seed) : base (default(int)) { } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.XxHash128 Clone() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } public static byte[] Hash(byte[] source) { throw null; } public static byte[] Hash(byte[] source, long seed) { throw null; } @@ -77,6 +80,7 @@ public sealed partial class XxHash3 : System.IO.Hashing.NonCryptographicHashAlgo public XxHash3() : base (default(int)) { } public XxHash3(long seed) : base (default(int)) { } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.XxHash3 Clone() { throw null; } [System.CLSCompliantAttribute(false)] public ulong GetCurrentHashAsUInt64() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } @@ -94,6 +98,7 @@ public sealed partial class XxHash32 : System.IO.Hashing.NonCryptographicHashAlg public XxHash32() : base (default(int)) { } public XxHash32(int seed) : base (default(int)) { } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.XxHash32 Clone() { throw null; } [System.CLSCompliantAttribute(false)] public uint GetCurrentHashAsUInt32() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } @@ -111,6 +116,7 @@ public sealed partial class XxHash64 : System.IO.Hashing.NonCryptographicHashAlg public XxHash64() : base (default(int)) { } public XxHash64(long seed) : base (default(int)) { } public override void Append(System.ReadOnlySpan source) { } + public System.IO.Hashing.XxHash64 Clone() { throw null; } [System.CLSCompliantAttribute(false)] public ulong GetCurrentHashAsUInt64() { throw null; } protected override void GetCurrentHashCore(System.Span destination) { } 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 260bf0bdadb374..86cedbab894ece 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 @@ -39,6 +39,16 @@ public Crc32() { } + /// Initializes a new instance of the class using the state from another instance. + private Crc32(uint crc) : base(Size) + { + _crc = crc; + } + + /// 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); + /// /// Appends the contents of to the data already /// processed for the current hash computation. 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 89b81c657c518c..690037ca05fb33 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 @@ -38,6 +38,16 @@ public Crc64() { } + /// Initializes a new instance of the class using the state from another instance. + private Crc64(ulong crc) : base(Size) + { + _crc = crc; + } + + /// 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); + /// /// Appends the contents of to the data already /// processed for the current hash computation. diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs index e1181cb73f914d..8cd30b57e7f51e 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash128.cs @@ -38,6 +38,16 @@ public XxHash128(long seed) : base(HashLengthInBytes) Initialize(ref _state, (ulong)seed); } + /// Initializes a new instance of the class using the state from another instance. + private XxHash128(State state) : base(HashLengthInBytes) + { + _state = state; + } + + /// 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 XxHash128 Clone() => new(_state); + /// Computes the XXH128 hash of the provided data. /// The data to hash. /// The XXH128 128-bit hash code of the provided data. diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs index affdfe78d6a850..ee2073a52c06b6 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs @@ -37,6 +37,16 @@ public XxHash3(long seed) : base(HashLengthInBytes) Initialize(ref _state, (ulong)seed); } + /// Initializes a new instance of the class using the state from another instance. + private XxHash3(State state) : base(HashLengthInBytes) + { + _state = state; + } + + /// 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 XxHash3 Clone() => new(_state); + /// Computes the XXH3 hash of the provided data. /// The data to hash. /// The XXH3 64-bit hash code of the provided data. diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash32.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash32.cs index a746e934f39204..6f93bc56a7e17c 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash32.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash32.cs @@ -51,6 +51,23 @@ public XxHash32(int seed) Reset(); } + /// Initializes a new instance of the class using the state from another instance. + private XxHash32(uint seed, State state, byte[]? holdback, int length) : + base(HashSize) + { + _seed = seed; + _state = state; + if (((int)length & 0x0F) > 0) + { + _holdback = (byte[]?)holdback?.Clone(); + } + _length = length; + } + + /// 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 XxHash32 Clone() => new(_seed, _state, _holdback, _length); + /// /// Resets the hash computation to the initial state. /// diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.cs index 70ac2e600651b1..fa88682ed34e82 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash64.cs @@ -51,6 +51,23 @@ public XxHash64(long seed) Reset(); } + /// Initializes a new instance of the class using the state from another instance. + private XxHash64(ulong seed, State state, byte[]? holdback, long length) : + base(HashSize) + { + _seed = seed; + _state = state; + if (((int)length & 0x1F) > 0) + { + _holdback = (byte[]?)holdback?.Clone(); + } + _length = length; + } + + /// 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 XxHash64 Clone() => new(_seed, _state, _holdback, _length); + /// /// Resets the hash computation to the initial state. /// diff --git a/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs b/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs index f056f823f805c2..bb0d366282755d 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc32Tests.cs @@ -83,6 +83,7 @@ public static IEnumerable TestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new Crc32(); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((Crc32)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => Crc32.Hash(source); diff --git a/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs b/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs index 12621dc88072ed..fdc01953bd5a38 100644 --- a/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/Crc64Tests.cs @@ -92,6 +92,7 @@ public static IEnumerable TestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new Crc64(); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((Crc64)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => Crc64.Hash(source); diff --git a/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs b/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs index 3204d93d4fb577..92b0567427fe11 100644 --- a/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs +++ b/src/libraries/System.IO.Hashing/tests/NonCryptoHashTestDriver.cs @@ -21,6 +21,7 @@ protected NonCryptoHashTestDriver(byte[] emptyHash) } protected abstract NonCryptographicHashAlgorithm CreateInstance(); + protected abstract NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance); protected abstract byte[] StaticOneShot(byte[] source); protected abstract byte[] StaticOneShot(ReadOnlySpan source); @@ -283,6 +284,31 @@ public void StaticVerifyTryOneShotSpanTooShortNoWrites() } } + [Fact] + public void Clone_ProducesSameSequence() + { + NonCryptographicHashAlgorithm hash = CreateInstance(); + + for (int outer = 0; outer < 4; outer++) + { + NonCryptographicHashAlgorithm clone = Clone(hash); + Assert.Equal(hash.HashLengthInBytes, clone.HashLengthInBytes); + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + + Random r = new Random(42); + byte[] bytes = new byte[r.Next(1, 10)]; + + for (int inner = 0; inner < 4; inner++) + { + r.NextBytes(bytes); + hash.Append(bytes); + clone.Append(bytes); + + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + } + } + } + private void VerifyEmptyResult(ReadOnlySpan result) { if (!result.SequenceEqual(_emptyHash)) diff --git a/src/libraries/System.IO.Hashing/tests/XxHash128Tests.cs b/src/libraries/System.IO.Hashing/tests/XxHash128Tests.cs index 3ba2044d069505..536d40ec418579 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash128Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash128Tests.cs @@ -131,6 +131,31 @@ public void Hash_Streaming_Expected() } } + [Fact] + public void Clone_ProducesSameSequence() + { + XxHash128 hash = new(42); + + for (int outer = 0; outer < 4; outer++) + { + XxHash128 clone = hash.Clone(); + Assert.Equal(hash.HashLengthInBytes, clone.HashLengthInBytes); + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + + Random r = new Random(42); + byte[] bytes = new byte[r.Next(1, 10)]; + + for (int inner = 0; inner < 4; inner++) + { + r.NextBytes(bytes); + hash.Append(bytes); + clone.Append(bytes); + + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + } + } + } + private static Hash128 ReadHashBigEndian(ReadOnlySpan span) { var high = BinaryPrimitives.ReadUInt64BigEndian(span); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.007.cs b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.007.cs index db6f73da73e944..2dd915488d8b2d 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.007.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.007.cs @@ -118,6 +118,7 @@ public static IEnumerable LargeTestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash32(Seed); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash32)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash32.Hash(source, Seed); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.cs b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.cs index e6bd60a1ac7dab..fe035aa69ce212 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.cs @@ -132,6 +132,7 @@ public static IEnumerable LargeTestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash32(); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash32)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash32.Hash(source); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.f00d.cs b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.f00d.cs index 455651926e62ec..fc27e9bf7ba68c 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash32Tests.f00d.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash32Tests.f00d.cs @@ -118,6 +118,7 @@ public static IEnumerable LargeTestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash32(Seed); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash32)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash32.Hash(source, Seed); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash3Tests.cs b/src/libraries/System.IO.Hashing/tests/XxHash3Tests.cs index fe6592bd85a861..6752e2bcd2c2c1 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash3Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash3Tests.cs @@ -122,6 +122,31 @@ public void Hash_Streaming_Expected() } } + [Fact] + public void Clone_ProducesSameSequence() + { + XxHash3 hash = new(42); + + for (int outer = 0; outer < 4; outer++) + { + XxHash3 clone = hash.Clone(); + Assert.Equal(hash.HashLengthInBytes, clone.HashLengthInBytes); + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + + Random r = new Random(42); + byte[] bytes = new byte[r.Next(1, 10)]; + + for (int inner = 0; inner < 4; inner++) + { + r.NextBytes(bytes); + hash.Append(bytes); + clone.Append(bytes); + + Assert.Equal(hash.GetCurrentHash(), clone.GetCurrentHash()); + } + } + } + private static IEnumerable<(ulong Hash, long Seed, string Ascii)> TestCases() { yield return (Hash: 0x2d06800538d394c2UL, Seed: 0x0000000000000000L, Ascii: ""); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.007.cs b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.007.cs index d91b1fde80f62b..ebccd2c5072711 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.007.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.007.cs @@ -103,6 +103,7 @@ public static IEnumerable TestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash64(Seed); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash64)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash64.Hash(source, Seed); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.cs b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.cs index 42d55a79287a6e..13dd01dbbf1971 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.cs @@ -145,6 +145,7 @@ public static IEnumerable LargeTestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash64(); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash64)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash64.Hash(source); diff --git a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.f00d.cs b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.f00d.cs index 2b0595aea12d94..91eefa14a14479 100644 --- a/src/libraries/System.IO.Hashing/tests/XxHash64Tests.f00d.cs +++ b/src/libraries/System.IO.Hashing/tests/XxHash64Tests.f00d.cs @@ -128,6 +128,7 @@ public static IEnumerable LargeTestCases }; protected override NonCryptographicHashAlgorithm CreateInstance() => new XxHash64(Seed); + protected override NonCryptographicHashAlgorithm Clone(NonCryptographicHashAlgorithm instance) => ((XxHash64)instance).Clone(); protected override byte[] StaticOneShot(byte[] source) => XxHash64.Hash(source, Seed);