diff --git a/src/csharp/Grpc.Core.Tests/Internal/WellKnownStringsTest.cs b/src/csharp/Grpc.Core.Tests/Internal/WellKnownStringsTest.cs
new file mode 100644
index 00000000000..f5c8e01a141
--- /dev/null
+++ b/src/csharp/Grpc.Core.Tests/Internal/WellKnownStringsTest.cs
@@ -0,0 +1,46 @@
+using System.Text;
+using Grpc.Core.Internal;
+using NUnit.Framework;
+
+namespace Grpc.Core.Tests.Internal
+{
+ public class WellKnownStringsTest
+ {
+ [Test]
+ [TestCase("", true)]
+ [TestCase("u", false)]
+ [TestCase("us", false)]
+ [TestCase("use", false)]
+ [TestCase("user", false)]
+ [TestCase("user-", false)]
+ [TestCase("user-a", false)]
+ [TestCase("user-ag", false)]
+ [TestCase("user-age", false)]
+ [TestCase("user-agent", true)]
+ [TestCase("user-agent ", false)]
+ [TestCase("useragent ", false)]
+ [TestCase("User-Agent", false)]
+ [TestCase("sdlkfjlskjfdlkjs;lfdksflsdfkh skjdfh sdkfhskdhf skjfhk sdhjkjh", false)]
+
+ // test for endianness snafus (reversed in segments)
+ [TestCase("ega-resutn", false)]
+ public unsafe void TestWellKnownStrings(string input, bool expected)
+ {
+ // create a copy of the data; no cheating!
+ byte[] bytes = Encoding.ASCII.GetBytes(input);
+ fixed(byte* ptr = bytes)
+ {
+ string result = WellKnownStrings.TryIdentify(ptr, bytes.Length);
+ if (expected) Assert.AreEqual(input, result);
+ else Assert.IsNull(result);
+
+ if (expected)
+ {
+ // try again, and check we get the same instance
+ string again = WellKnownStrings.TryIdentify(ptr, bytes.Length);
+ Assert.AreSame(result, again);
+ }
+ }
+ }
+ }
+}
diff --git a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
index 1663a9d44ac..cc306891119 100644
--- a/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
@@ -76,39 +76,6 @@ namespace Grpc.Core.Internal
return metadata;
}
- private static class WellKnownStrings
- {
- // turn a string of ASCII-length 8 into a ulong using the CPUs current
- // endianness; this allows us to do the same thing in TryIdentify,
- // testing string prefixes (of length >= 8) in a single load/compare
- private static unsafe ulong Thunk8(string value)
- {
- int byteCount = Encoding.ASCII.GetByteCount(value);
- if (byteCount != 8) throw new ArgumentException(nameof(value));
- ulong result = 0;
- fixed (char* cPtr = value)
- {
- Encoding.ASCII.GetBytes(cPtr, value.Length, (byte*)&result, byteCount);
- }
- return result;
- }
- private static readonly ulong UserAgent = Thunk8("user-age");
- public static unsafe string TryIdentify(byte* source, int length)
- {
- switch (length)
- {
- case 10:
- // test the first 8 bytes via evilness
- ulong first8 = *(ulong*)source;
- if (first8 == UserAgent & source[8] == (byte)'n' & source[9] == (byte)'t')
- return "user-agent";
-
- break;
- }
- return null;
- }
- }
-
internal IntPtr Handle
{
get
diff --git a/src/csharp/Grpc.Core/Internal/WellKnownStrings.cs b/src/csharp/Grpc.Core/Internal/WellKnownStrings.cs
new file mode 100644
index 00000000000..1e78ffdab43
--- /dev/null
+++ b/src/csharp/Grpc.Core/Internal/WellKnownStrings.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Grpc.Core.Internal
+{
+ ///
+ /// Utility type for identifying "well-known" strings (i.e. headers/keys etc that
+ /// we expect to see frequently, and don't want to allocate lots of copies of)
+ ///
+ internal static class WellKnownStrings
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe ulong Coerce64(byte* value)
+ {
+ return *(ulong*)value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe uint Coerce32(byte* value)
+ {
+ return *(uint*)value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static unsafe ushort Coerce16(byte* value)
+ {
+ return *(ushort*)value;
+ }
+
+ ///
+ /// Test whether the provided byte sequence is recognized as a well-known string; if
+ /// so, return a shared instance of that string; otherwise, return null
+ ///
+ public static unsafe string TryIdentify(byte* source, int length)
+ {
+ // note: the logic here is hard-coded to constants for optimal processing;
+ // refer to an ASCII/hex converter (and remember to reverse **segments** for little-endian)
+ if (BitConverter.IsLittleEndian) // this is a JIT intrinsic; branch removal happens on modern runtimes
+ {
+ switch (length)
+ {
+ case 0: return "";
+ case 10:
+ switch(Coerce64(source))
+ {
+ case 0x6567612d72657375: return Coerce16(source + 8) == 0x746e ? "user-agent" : null;
+ }
+ break;
+ }
+ }
+ else
+ {
+ switch (length)
+ {
+ case 0: return "";
+ case 10:
+ switch (Coerce64(source))
+ {
+ case 0x757365722d616765: return Coerce16(source + 8) == 0x6e74 ? "user-agent" : null;
+ }
+ break;
+ }
+ }
+ return null;
+ }
+ }
+}