diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj
index 5d34ff1083..59ddd05a82 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Microsoft.TestPlatform.AdapterUtilities.csproj
@@ -49,6 +49,10 @@
+
+
+
+
NullableHelpers.cs
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.AdapterUtilities/PublicAPI/PublicAPI.Unshipped.txt
index 7dc5c58110..129935abda 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/PublicAPI/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.AppendBytes(byte[]! bytes) -> void
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.AppendString(string! str) -> void
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.GetHash() -> byte[]!
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.GetId() -> System.Guid
+Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.TestIdProvider2() -> void
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
index fbefb86473..59648cc8ef 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
@@ -8,8 +8,9 @@
namespace Microsoft.TestPlatform.AdapterUtilities;
///
-/// Used to generate id for tests.
+/// Used to generate id for tests using SHA1.
///
+[Obsolete("TestIdProvider is deprecated and will soon be removed because it uses unsafe cryptographical hash SHA1 (for non-crypto purposes). Migrate to TestIdProvider2 that uses a non-cryptographical hash which is more appropriate for the task.")]
public class TestIdProvider
{
private Guid _id = Guid.Empty;
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider2.cs b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider2.cs
new file mode 100644
index 0000000000..227a44f0e1
--- /dev/null
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider2.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO.Hashing;
+using System.Text;
+
+namespace Microsoft.TestPlatform.AdapterUtilities;
+///
+/// Used to generate id for tests.
+///
+public class TestIdProvider2
+{
+ private Guid _id = Guid.Empty;
+ private byte[]? _hash;
+
+ private readonly XxHash128 _xxhash;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TestIdProvider2()
+ {
+ _xxhash = new XxHash128();
+ }
+
+ ///
+ /// Appends a string to id generation seed.
+ ///
+ /// String to append to the id seed.
+ /// Thrown if or is called already.
+ /// Thrown when is .
+ public void AppendString(string str)
+ {
+ if (_hash != null)
+ {
+ throw new InvalidOperationException(Resources.Resources.ErrorCannotAppendAfterHashCalculation);
+ }
+ _ = str ?? throw new ArgumentNullException(nameof(str));
+
+ var bytes = Encoding.Unicode.GetBytes(str);
+
+ _xxhash.Append(bytes);
+ }
+
+ ///
+ /// Appends an array of bytes to id generation seed.
+ ///
+ /// Array to append to the id seed.
+ /// Thrown if or is called already.
+ /// Thrown when is .
+ public void AppendBytes(byte[] bytes)
+ {
+ if (_hash != null)
+ {
+ throw new InvalidOperationException(Resources.Resources.ErrorCannotAppendAfterHashCalculation);
+ }
+ _ = bytes ?? throw new ArgumentNullException(nameof(bytes));
+
+ if (bytes.Length == 0)
+ {
+ return;
+ }
+
+ _xxhash.Append(bytes);
+ }
+
+ ///
+ /// Calculates the Id seed.
+ ///
+ /// An array containing the seed.
+ ///
+ /// and cannot be called
+ /// on instance after this method is called.
+ ///
+ public byte[] GetHash()
+ {
+ if (_hash != null)
+ {
+ return _hash;
+ }
+
+ // Finalize the hash. We don't have any more data so we provide empty.
+ _hash = _xxhash.GetCurrentHash();
+
+ return _hash!;
+ }
+
+ ///
+ /// Calculates the Id from the seed.
+ ///
+ /// Id
+ ///
+ /// and cannot be called
+ /// on instance after this method is called.
+ ///
+ public Guid GetId()
+ {
+ if (_id != Guid.Empty)
+ {
+ return _id;
+ }
+
+#if NET6_0_OR_GREATER
+ var hashSlice = GetHash().AsSpan().Slice(0, 16);
+ _id = new Guid(hashSlice);
+#else
+ // create from span?
+ var toGuid = new byte[16];
+ Array.Copy(GetHash(), toGuid, 16);
+ _id = new Guid(toGuid);
+#endif
+
+ return _id;
+ }
+}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj
index 1675d4cb00..b588f3c9d2 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj
+++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj
@@ -39,6 +39,10 @@
+
+
+
+
TextTemplatingFileGenerator
diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt
index 343eac0f8d..3f7a0092a2 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt
@@ -9,3 +9,4 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Name.get -> string!
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Properties.get -> System.Collections.Generic.IDictionary!
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.TelemetryEvent(string! name, System.Collections.Generic.IDictionary! properties) -> void
+static Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities.EqtHash.GuidFromString2(string! data) -> System.Guid
diff --git a/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs
index b9c260fb9f..a8a9cfe9e9 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/TestCase.cs
@@ -201,7 +201,7 @@ private Guid GetTestId()
// If ManagedType and ManagedMethod properties are filled than TestId should be based on those.
testcaseFullName += GetFullyQualifiedName();
- return EqtHash.GuidFromString(testcaseFullName);
+ return EqtHash.GuidFromString2(testcaseFullName);
}
private void SetVariableAndResetId(ref T variable, T value)
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs
index ef595cffb5..9b7a018851 100644
--- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs
+++ b/src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
+using System.IO.Hashing;
+using System.Security.Cryptography;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities;
@@ -17,6 +19,8 @@ public static class EqtHash
/// Calculates a hash of the string and copies the first 128 bits of the hash
/// to a new Guid.
///
+ [Obsolete("GuidFromString is deprecated and will soon be removed because it uses unsafe cryptographical hash SHA1 (for non-crypto purposes). Migrate to GuidFromString2 that uses a non-cryptographical hash which is more appropriate for the task.")]
+
public static Guid GuidFromString(string data)
{
TPDebug.Assert(data != null);
@@ -27,7 +31,8 @@ public static Guid GuidFromString(string data)
// Any algorithm or logic change must require a sign off from feature owners of above
// Also, TPV2 and TPV1 must use same Algorithm until the time TPV1 is completely deleted to be on-par
// If LUT or .Net core scenario uses TPV2 to discover, but if it uses TPV1 in Devenv, then there will be testcase matching issues
- byte[] hash = Sha1Helper.ComputeSha1(System.Text.Encoding.Unicode.GetBytes(data));
+ using HashAlgorithm provider = SHA1.Create();
+ byte[] hash = provider.ComputeHash(System.Text.Encoding.Unicode.GetBytes(data));
// Guid is always 16 bytes
TPDebug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes");
@@ -37,4 +42,27 @@ public static Guid GuidFromString(string data)
return new Guid(toGuid);
}
+
+ ///
+ /// Calculates a hash of the string and copies the first 128 bits of the hash
+ /// to a new Guid.
+ ///
+ public static Guid GuidFromString2(string data)
+ {
+ TPDebug.Assert(data != null);
+
+ byte[] hash = XxHash128.Hash(System.Text.Encoding.Unicode.GetBytes(data));
+
+ // Guid is always 16 bytes
+ TPDebug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes");
+
+#if NET6_0_OR_GREATER
+ return new Guid(hash.AsSpan().Slice(0, 16));
+#else
+ // create from span?
+ var toGuid = new byte[16];
+ Array.Copy(hash, toGuid, 16);
+ return new Guid(toGuid);
+#endif
+ }
}
diff --git a/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs b/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs
deleted file mode 100644
index 7b2a249fa3..0000000000
--- a/src/Microsoft.TestPlatform.ObjectModel/Utilities/Sha1Helper.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using System;
-using System.Security.Cryptography;
-
-namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
-
-///
-/// Used to calculate SHA1 hash.
-///
-/// https://tools.ietf.org/html/rfc3174
-///
-internal static class Sha1Helper
-{
- public static byte[] ComputeSha1(byte[] message)
- {
- using HashAlgorithm provider = SHA1.Create();
- byte[] hash = provider.ComputeHash(message);
-
- return hash;
- }
-
- ///
- /// SHA-1 Implementation as in https://tools.ietf.org/html/rfc3174
- ///
- ///
- /// This implementation only works with messages with a length
- /// that is a multiple of the size of 8-bits.
- ///
- internal class Sha1Implementation
- {
- /*
- * Many of the variable, function and parameter names in this code
- * were used because those were the names used in the publication.
- *
- * For more information please refer to https://tools.ietf.org/html/rfc3174.
- */
-
- private const int BlockBits = 512;
- private const int DigestBits = 160;
- private const int BlockBytes = BlockBits / 8;
- private const int DigestBytes = DigestBits / 8;
-
- ///
- /// A sequence of logical functions to be used in SHA-1.
- /// Each f(t), 0 <= t <= 79, operates on three 32-bit words B, C, D and produces a 32-bit word as output.
- ///
- /// Function index. 0 <= t <= 79
- /// Word B
- /// Word C
- /// Word D
- ///
- /// f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19)
- /// f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39)
- /// f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
- /// f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79)
- ///
- private static uint F(int t, uint b, uint c, uint d)
- {
- return t switch
- {
- >= 0 and <= 19 => b & c | ~b & d,
- >= 20 and <= 39 or >= 60 and <= 79 => b ^ c ^ d,
- _ => t is >= 40 and <= 59
- ? b & c | b & d | c & d
- : throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t))
- };
- }
-
- ///
- /// Returns a constant word K(t) which is used in the SHA-1.
- ///
- /// Word index.
- ///
- /// K(t) = 0x5A827999 ( 0 <= t <= 19)
- /// K(t) = 0x6ED9EBA1 (20 <= t <= 39)
- /// K(t) = 0x8F1BBCDC (40 <= t <= 59)
- /// K(t) = 0xCA62C1D6 (60 <= t <= 79)
- ///
- private static uint K(int t)
- {
- return t switch
- {
- >= 0 and <= 19 => 0x5A827999u,
- >= 20 and <= 39 => 0x6ED9EBA1u,
- >= 40 and <= 59 => 0x8F1BBCDCu,
- _ => t is >= 60 and <= 79
- ? 0xCA62C1D6u
- : throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t))
- };
- }
-
- ///
- /// The circular left shift operation.
- ///
- /// An uint word.
- /// 0 <= n < 32
- /// S^n(X) = (X << n) OR (X >> 32-n)
- private static uint S(uint x, byte n)
- {
- return n > 32 ? throw new ArgumentOutOfRangeException(nameof(n)) : (x << n) | (x >> (32 - n));
- }
-
- ///
- /// Ensures that given bytes are in big endian notation.
- ///
- /// An array of bytes
- private static void EnsureBigEndian(ref byte[] array)
- {
- ValidateArg.NotNull(array, nameof(array));
-
- if (BitConverter.IsLittleEndian)
- {
- Array.Reverse(array);
- }
- }
-
- private readonly uint[] _h = new uint[5];
-
- private void Reset()
- {
- // as defined in https://tools.ietf.org/html/rfc3174#section-6.1
- _h[0] = 0x67452301u;
- _h[1] = 0xEFCDAB89u;
- _h[2] = 0x98BADCFEu;
- _h[3] = 0x10325476u;
- _h[4] = 0xC3D2E1F0u;
- }
-
- public byte[] ComputeHash(byte[] message)
- {
- ValidateArg.NotNull(message, nameof(message));
-
- Reset();
- PadMessage(ref message);
-
- var messageCount = message.Length / BlockBytes;
- for (var i = 0; i < messageCount; ++i)
- {
- ProcessBlock(message, i * BlockBytes, BlockBytes);
- }
-
- var digest = new byte[DigestBytes];
- for (int t = 0; t < _h.Length; t++)
- {
- var hi = BitConverter.GetBytes(_h[t]);
- EnsureBigEndian(ref hi);
-
- Buffer.BlockCopy(hi, 0, digest, t * hi.Length, hi.Length);
- }
-
- return digest;
- }
-
- private static void PadMessage(ref byte[] message)
- {
- var length = message.Length;
- var paddingBytes = BlockBytes - (length % BlockBytes);
-
- // 64bit uint message size will be appended to end of the padding, making sure we have space for it.
- if (paddingBytes <= 8)
- paddingBytes += BlockBytes;
-
- var padding = new byte[paddingBytes];
- padding[0] = 0b10000000;
-
- var messageBits = (ulong)message.Length << 3;
- var messageSize = BitConverter.GetBytes(messageBits);
- EnsureBigEndian(ref messageSize);
-
- Buffer.BlockCopy(messageSize, 0, padding, padding.Length - messageSize.Length, messageSize.Length);
-
- Array.Resize(ref message, message.Length + padding.Length);
- Buffer.BlockCopy(padding, 0, message, length, padding.Length);
- }
-
- private void ProcessBlock(byte[] message, int start, int length)
- {
- if (start + length > message.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(length));
- }
- if (length != BlockBytes)
- {
- throw new ArgumentException($"Invalid block size. Actual: {length}, Expected: {BlockBytes}", nameof(length));
- }
-
- var w = new uint[80];
-
- // Get W(0) .. W(15)
- for (int t = 0; t <= 15; t++)
- {
- var wordBytes = new byte[sizeof(uint)];
- Buffer.BlockCopy(message, start + (t * sizeof(uint)), wordBytes, 0, sizeof(uint));
- EnsureBigEndian(ref wordBytes);
-
- w[t] = BitConverter.ToUInt32(wordBytes, 0);
- }
-
- // Calculate W(16) .. W(79)
- for (int t = 16; t <= 79; t++)
- {
- w[t] = S(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1);
- }
-
- uint a = _h[0],
- b = _h[1],
- c = _h[2],
- d = _h[3],
- e = _h[4];
-
- for (int t = 0; t < 80; t++)
- {
- var temp = S(a, 5) + F(t, b, c, d) + e + w[t] + K(t);
- e = d;
- d = c;
- c = S(b, 30);
- b = a;
- a = temp;
- }
-
- _h[0] += a;
- _h[1] += b;
- _h[2] += c;
- _h[3] += d;
- _h[4] += e;
- }
- }
-}
diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
index 175ea9bcb2..b22b822bb2 100644
--- a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
+++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
@@ -9,6 +9,7 @@
namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.TestIdProvider;
[TestClass]
+[Obsolete("Testing obsolete api that we did not remove yet.")]
public class CompatibilityTests
{
[TestMethod]