diff --git a/libraries/Microsoft.Bot.Builder.Azure/CosmosDBKeyEscape.cs b/libraries/Microsoft.Bot.Builder.Azure/CosmosDBKeyEscape.cs
index e6e154d669..c191315941 100644
--- a/libraries/Microsoft.Bot.Builder.Azure/CosmosDBKeyEscape.cs
+++ b/libraries/Microsoft.Bot.Builder.Azure/CosmosDBKeyEscape.cs
@@ -117,11 +117,43 @@ private static string TruncateKeyIfNeeded(string key, bool truncateKeysForCompat
if (key.Length > MaxKeyLength)
{
- var hash = key.GetHashCode().ToString("x", CultureInfo.InvariantCulture);
+ var hash = key.GetDeterministicHashCode().ToString("x", CultureInfo.InvariantCulture);
key = key.Substring(0, MaxKeyLength - hash.Length) + hash;
}
return key;
}
+
+ ///
+ /// Creates a deterministic hash code by iterating through the string two characters at a time,
+ /// updating two separate hash values, and then combining them at the end.
+ /// This approach helps in reducing hash collisions and provides a consistent hash code for the same string across
+ /// different runs and environments.
+ ///
+ /// The string to calculate the hash on.
+ /// The hash code.
+ private static int GetDeterministicHashCode(this string str)
+ {
+ unchecked
+ {
+ var hash1 = (5381 << 16) + 5381; //shifts 5381 left by 16 bits and adds 5381 to it
+ var hash2 = hash1;
+ for (var i = 0; i < str.Length; i += 2)
+ {
+ // ((hash1 << 5) + hash1) is equivalent to hash1 * 33, which is a common multiplier in hash functions.
+ // The character str[i] is then XORed with this value.
+ hash1 = ((hash1 << 5) + hash1) ^ str[i];
+ if (i == str.Length - 1)
+ {
+ break;
+ }
+
+ hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
+ }
+
+ //1566083941 is a large prime number used to mix the two hash values together, ensuring a more uniform distribution of hash codes.
+ return hash1 + (hash2 * 1566083941);
+ }
+ }
}
}
diff --git a/tests/Microsoft.Bot.Builder.Azure.Tests/CosmosDBKeyEscapeTests.cs b/tests/Microsoft.Bot.Builder.Azure.Tests/CosmosDBKeyEscapeTests.cs
index 1f5ec6f3f2..f924bde1f8 100644
--- a/tests/Microsoft.Bot.Builder.Azure.Tests/CosmosDBKeyEscapeTests.cs
+++ b/tests/Microsoft.Bot.Builder.Azure.Tests/CosmosDBKeyEscapeTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Reflection;
using Xunit;
namespace Microsoft.Bot.Builder.Azure.Tests
@@ -40,7 +41,8 @@ public void Long_Key_Should_Be_Truncated()
Assert.True(sanitizedKey.Length <= CosmosDbKeyEscape.MaxKeyLength, "Key too long");
// The resulting key should be:
- var hash = tooLongKey.GetHashCode().ToString("x");
+ var getHashMethod = typeof(CosmosDbKeyEscape).GetMethod("GetDeterministicHashCode", BindingFlags.NonPublic | BindingFlags.Static);
+ var hash = ((int)getHashMethod.Invoke(null, new object[] { tooLongKey })).ToString("x");
var correctKey = sanitizedKey.Substring(0, CosmosDbKeyEscape.MaxKeyLength - hash.Length) + hash;
Assert.Equal(correctKey, sanitizedKey);