using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace TEAMModelOS.SDK { public static class MurmurHash3 { public static uint Hash32(string input) { var buffer = Encoding.UTF8.GetBytes(input); uint seed = 0; using var stream = new MemoryStream(buffer); var result = MurmurHash3.Hash32(stream, seed); return result; } public static string Hash128(string input) { var buffer = Encoding.UTF8.GetBytes(input); uint seed = 0; using var stream = new MemoryStream(buffer); var result = MurmurHash3.Hash128(stream, seed); return Convert.ToHexString(result); } /// /// Calculate 32-bit MurmurHash3 hash value using x86 version of the algorithm. /// /// Input stream. /// Seed value. /// Hash value. public static uint Hash32(Stream stream, uint seed) { const int uintSize = sizeof(uint); const uint final1 = 0x85ebca6b; const uint final2 = 0xc2b2ae35; const uint n = 0xe6546b64; const uint m = 5; uint hash = seed; var buffer = new byte[uintSize].AsSpan(); uint length = 0; int bytesRead; while ((bytesRead = stream.Read(buffer)) == uintSize) { uint k = Bits.ToUInt32(buffer); round32(ref k, ref hash); hash = Bits.RotateLeft(hash, 13); hash *= m; hash += n; length += (uint)bytesRead; } // process remaning bytes if (bytesRead > 0) { uint remaining = Bits.PartialBytesToUInt32(buffer[..bytesRead]); round32(ref remaining, ref hash); length += (uint)bytesRead; } hash ^= length; // finalization mix hash ^= hash >> 16; hash *= final1; hash ^= hash >> 13; hash *= final2; hash ^= hash >> 16; return hash; } /// /// Calculate 32-bit MurmurHash3 hash value using x86 version of the algorithm. /// /// Input stream. /// Seed value. /// A representing the asynchronous hash operation. public static async Task Hash32Async(Stream stream, uint seed) { const int uintSize = sizeof(uint); const uint final1 = 0x85ebca6b; const uint final2 = 0xc2b2ae35; const uint n = 0xe6546b64; const uint m = 5; uint hash = seed; var buffer = new byte[uintSize].AsMemory(); uint length = 0; int bytesRead; while ((bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false)) == uintSize) { uint k = Bits.ToUInt32(buffer.Span); round32(ref k, ref hash); hash = Bits.RotateLeft(hash, 13); hash *= m; hash += n; length += (uint)bytesRead; } // process remaning bytes if (bytesRead > 0) { uint remaining = Bits.PartialBytesToUInt32(buffer[..bytesRead].Span); round32(ref remaining, ref hash); length += (uint)bytesRead; } hash ^= length; // finalization mix hash ^= hash >> 16; hash *= final1; hash ^= hash >> 13; hash *= final2; hash ^= hash >> 16; return hash; } /// /// Calculate 32-bit MurmurHash3 hash value. /// /// Input buffer. /// Seed value. /// Hash value. public static uint Hash32(ReadOnlySpan buffer, uint seed) { const int uintSize = sizeof(uint); const uint final1 = 0x85ebca6b; const uint final2 = 0xc2b2ae35; const uint n = 0xe6546b64; const uint m = 5; uint hash = seed; int length = buffer.Length; var (numUInts, leftBytes) = Math.DivRem(length, uintSize); int i = 0; for (; i < numUInts * sizeof(uint); i += sizeof(uint)) { uint k = Bits.ToUInt32(buffer[i..(i + sizeof(uint))]); round32(ref k, ref hash); hash = Bits.RotateLeft(hash, 13); hash *= m; hash += n; } if (leftBytes > 0) { uint remaining = Bits.PartialBytesToUInt32(buffer[i..(i + leftBytes)]); round32(ref remaining, ref hash); } hash ^= (uint)length; // finalization mix hash ^= hash >> 16; hash *= final1; hash ^= hash >> 13; hash *= final2; hash ^= hash >> 16; return hash; } /// /// Calculate 128-bit MurmurHash3 hash value using 64-bit version of the algorithm. /// /// Input stream. /// Seed value. /// 128-bit hash value in a Span. public static byte[] Hash128(Stream stream, uint seed) { const int ulongSize = sizeof(ulong); const int blockSize = ulongSize * 2; const ulong c1 = 0x87c37b91114253d5UL; const ulong c2 = 0x4cf5ad432745937fUL; ulong h1 = seed; ulong h2 = seed; var buffer = new byte[blockSize].AsSpan(); int outputLength = 0; int readBytes; while ((readBytes = stream.Read(buffer)) == blockSize) { ulong ik1 = Bits.ToUInt64(buffer); ulong ik2 = Bits.ToUInt64(buffer[ulongSize..]); round128(ref ik1, ref h1, c1, c2, h2, 31, 27, 0x52dce729U); round128(ref ik2, ref h2, c2, c1, h1, 33, 31, 0x38495ab5U); outputLength += blockSize; } ulong k1; ulong k2; if (readBytes > ulongSize) { k2 = Bits.PartialBytesToUInt64(buffer[ulongSize..]); tailRound128(ref k2, ref h2, c2, c1, 33); outputLength += readBytes - ulongSize; readBytes = ulongSize; } if (readBytes > 0) { k1 = Bits.PartialBytesToUInt64(buffer[..readBytes]); tailRound128(ref k1, ref h1, c1, c2, 31); outputLength += readBytes; } h1 ^= (ulong)outputLength; h2 ^= (ulong)outputLength; h1 += h2; h2 += h1; fmix64(ref h1); fmix64(ref h2); h1 += h2; h2 += h1; return makeBytes(h1, h2); } /// /// Calculate 128-bit MurmurHash3 hash value using x64 version of the algorithm. /// /// Input buffer. /// Seed value. /// 128-bit hash value as a Span of bytes. public static byte[] Hash128(ReadOnlySpan buffer, uint seed) { const int blockSize = 16; const ulong c1 = 0x87c37b91114253d5UL; const ulong c2 = 0x4cf5ad432745937fUL; ulong h1 = seed; ulong h2 = seed; int length = buffer.Length; var (numBlocks, leftBytes) = Math.DivRem(length, blockSize); int offset = 0; int end = numBlocks * sizeof(ulong) * 2; while (offset != end) { ulong ik1 = Bits.ToUInt64(buffer[offset..(offset + sizeof(ulong))]); offset += sizeof(ulong); ulong ik2 = Bits.ToUInt64(buffer[offset..(offset + sizeof(ulong))]); offset += sizeof(ulong); round128(ref ik1, ref h1, c1, c2, h2, 31, 27, 0x52dce729U); round128(ref ik2, ref h2, c2, c1, h1, 33, 31, 0x38495ab5U); } int tail = offset; ulong k1; ulong k2; if (leftBytes > sizeof(ulong)) { offset += sizeof(ulong); k2 = Bits.PartialBytesToUInt64(buffer[offset..]); tailRound128(ref k2, ref h2, c2, c1, 33); leftBytes = sizeof(ulong); } if (leftBytes > 0) { k1 = Bits.PartialBytesToUInt64(buffer[tail..(tail + leftBytes)]); tailRound128(ref k1, ref h1, c1, c2, 31); } h1 ^= (ulong)length; h2 ^= (ulong)length; h1 += h2; h2 += h1; fmix64(ref h1); fmix64(ref h2); h1 += h2; h2 += h1; return makeBytes(h1, h2); } private static byte[] makeBytes(ulong h1, ulong h2) { var result = new byte[16]; BitConverter.TryWriteBytes(result, h1); BitConverter.TryWriteBytes(result.AsSpan()[8..], h2); return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void round32(ref uint value, ref uint hash) { const uint c1 = 0xcc9e2d51; const uint c2 = 0x1b873593; value *= c1; value = Bits.RotateLeft(value, 15); value *= c2; hash ^= value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void round128( ref ulong k, ref ulong h, ulong c1, ulong c2, ulong hn, int krot, int hrot, uint x) { k *= c1; k = Bits.RotateLeft(k, krot); k *= c2; h ^= k; h = Bits.RotateLeft(h, hrot); h += hn; h = (h * 5) + x; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void fmix64(ref ulong h) { h ^= h >> 33; h *= 0xff51afd7ed558ccdUL; h ^= h >> 33; h *= 0xc4ceb9fe1a85ec53UL; h ^= h >> 33; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void tailRound128(ref ulong k, ref ulong h, ulong c1, ulong c2, int rot) { k *= c1; k = Bits.RotateLeft(k, rot); k *= c2; h ^= k; } } internal static class Bits { [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong RotateLeft(ulong value, int bits) { return (value << bits) | (value >> (64 - bits)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint RotateLeft(uint value, int bits) { return (value << bits) | (value >> (32 - bits)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint RotateRight(uint value, int bits) { return (value >> bits) | (value << (32 - bits)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong RotateRight(ulong value, int bits) { return (value >> bits) | (value << (64 - bits)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong ToUInt64(ReadOnlySpan bytes) { return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(bytes)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ToUInt32(ReadOnlySpan bytes) { return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(bytes)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong PartialBytesToUInt64(ReadOnlySpan remainingBytes) { // a switch/case approach is slightly faster than the loop but .net // refuses to inline it due to larger code size. ulong result = 0; // trying to modify leftBytes would invalidate inlining // need to use local variable instead for (int i = 0; i < remainingBytes.Length; i++) { result |= ((ulong)remainingBytes[i]) << (i << 3); } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint PartialBytesToUInt32(ReadOnlySpan remainingBytes) { int len = remainingBytes.Length; if (len > 3) { return Bits.ToUInt32(remainingBytes); } // a switch/case approach is slightly faster than the loop but .net // refuses to inline it due to larger code size. uint result = remainingBytes[0]; if (len > 1) { result |= (uint)(remainingBytes[1] << 8); } if (len > 2) { result |= (uint)(remainingBytes[2] << 16); } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint SwapBytes32(uint num) { return (Bits.RotateLeft(num, 8) & 0x00FF00FFu) | (Bits.RotateRight(num, 8) & 0xFF00FF00u); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ulong SwapBytes64(ulong num) { num = (Bits.RotateLeft(num, 48) & 0xFFFF0000FFFF0000ul) | (Bits.RotateLeft(num, 16) & 0x0000FFFF0000FFFFul); return (Bits.RotateLeft(num, 8) & 0xFF00FF00FF00FF00ul) | (Bits.RotateRight(num, 8) & 0x00FF00FF00FF00FFul); } } }