DTLib/XXHash.cs
2021-10-26 19:05:31 +03:00

178 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Security.Cryptography;
namespace DTLib
{
//
// честно взятый с гитхаба алгоритм хеширования
// выдаёт хеш в виде массива четырёх байтов
//
sealed class XXHash32 : HashAlgorithm
{
private const uint PRIME32_1 = 2654435761U;
private const uint PRIME32_2 = 2246822519U;
private const uint PRIME32_3 = 3266489917U;
private const uint PRIME32_4 = 668265263U;
private const uint PRIME32_5 = 374761393U;
private static readonly Func<byte[], int, uint> FuncGetLittleEndianUInt32;
private static readonly Func<uint, uint> FuncGetFinalHashUInt32;
private uint _Seed32;
private uint _ACC32_1;
private uint _ACC32_2;
private uint _ACC32_3;
private uint _ACC32_4;
private uint _Hash32;
private int _RemainingLength;
private long _TotalLength = 0;
private int _CurrentIndex;
private byte[] _CurrentArray;
static XXHash32()
{
if (BitConverter.IsLittleEndian)
{
FuncGetLittleEndianUInt32 = new Func<byte[], int, uint>((x, i) =>
{
unsafe
{
fixed (byte* array = x)
{
return *(uint*)(array + i);
}
}
});
FuncGetFinalHashUInt32 = new Func<uint, uint>(i => (i & 0x000000FFU) << 24 | (i & 0x0000FF00U) << 8 | (i & 0x00FF0000U) >> 8 | (i & 0xFF000000U) >> 24);
}
else
{
FuncGetLittleEndianUInt32 = new Func<byte[], int, uint>((x, i) =>
{
unsafe
{
fixed (byte* array = x)
{
return (uint)(array[i++] | (array[i++] << 8) | (array[i++] << 16) | (array[i] << 24));
}
}
});
FuncGetFinalHashUInt32 = new Func<uint, uint>(i => i);
}
}
// Creates an instance of <see cref="XXHash32"/> class by default seed(0).
// <returns></returns>
public static new XXHash32 Create() => new();
// Initializes a new instance of the <see cref="XXHash32"/> class by default seed(0).
public XXHash32() => Initialize(0);
// Initializes a new instance of the <see cref="XXHash32"/> class, and sets the <see cref="Seed"/> to the specified value.
// <param name="seed">Represent the seed to be used for xxHash32 computing.</param>
public XXHash32(uint seed) => Initialize(seed);
// Gets the <see cref="uint"/> value of the computed hash code.
// <exception cref="InvalidOperationException">Hash computation has not yet completed.</exception>
public uint HashUInt32 => State == 0 ? _Hash32 : throw new InvalidOperationException("Hash computation has not yet completed.");
// Gets or sets the value of seed used by xxHash32 algorithm.
// <exception cref="InvalidOperationException">Hash computation has not yet completed.</exception>
public uint Seed
{
get => _Seed32;
set
{
if (value != _Seed32)
{
if (State != 0)
throw new InvalidOperationException("Hash computation has not yet completed.");
_Seed32 = value;
Initialize();
}
}
}
// Initializes this instance for new hash computing.
public override void Initialize()
{
_ACC32_1 = _Seed32 + PRIME32_1 + PRIME32_2;
_ACC32_2 = _Seed32 + PRIME32_2;
_ACC32_3 = _Seed32 + 0;
_ACC32_4 = _Seed32 - PRIME32_1;
}
// Routes data written to the object into the hash algorithm for computing the hash.
// <param name="array">The input to compute the hash code for.</param>
// <param name="ibStart">The offset into the byte array from which to begin using data.</param>
// <param name="cbSize">The number of bytes in the byte array to use as data.</param>
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
if (State != 1)
State = 1;
int size = cbSize - ibStart;
_RemainingLength = size & 15;
if (cbSize >= 16)
{
int limit = size - _RemainingLength;
do
{
_ACC32_1 = Round32(_ACC32_1, FuncGetLittleEndianUInt32(array, ibStart));
ibStart += 4;
_ACC32_2 = Round32(_ACC32_2, FuncGetLittleEndianUInt32(array, ibStart));
ibStart += 4;
_ACC32_3 = Round32(_ACC32_3, FuncGetLittleEndianUInt32(array, ibStart));
ibStart += 4;
_ACC32_4 = Round32(_ACC32_4, FuncGetLittleEndianUInt32(array, ibStart));
ibStart += 4;
} while (ibStart < limit);
}
_TotalLength += cbSize;
if (_RemainingLength != 0)
{
_CurrentArray = array;
_CurrentIndex = ibStart;
}
}
// Finalizes the hash computation after the last data is processed by the cryptographic stream object.
// <returns>The computed hash code.</returns>
protected override byte[] HashFinal()
{
_Hash32 = _TotalLength >= 16
? RotateLeft32(_ACC32_1, 1) + RotateLeft32(_ACC32_2, 7) + RotateLeft32(_ACC32_3, 12) + RotateLeft32(_ACC32_4, 18)
: _Seed32 + PRIME32_5;
_Hash32 += (uint)_TotalLength;
while (_RemainingLength >= 4)
{
_Hash32 = RotateLeft32(_Hash32 + FuncGetLittleEndianUInt32(_CurrentArray, _CurrentIndex) * PRIME32_3, 17) * PRIME32_4;
_CurrentIndex += 4;
_RemainingLength -= 4;
}
unsafe
{
fixed (byte* arrayPtr = _CurrentArray)
{
while (_RemainingLength-- >= 1)
{
_Hash32 = RotateLeft32(_Hash32 + arrayPtr[_CurrentIndex++] * PRIME32_5, 11) * PRIME32_1;
}
}
}
_Hash32 = (_Hash32 ^ (_Hash32 >> 15)) * PRIME32_2;
_Hash32 = (_Hash32 ^ (_Hash32 >> 13)) * PRIME32_3;
_Hash32 ^= _Hash32 >> 16;
_TotalLength = State = 0;
return BitConverter.GetBytes(FuncGetFinalHashUInt32(_Hash32));
}
private static uint Round32(uint input, uint value) => RotateLeft32(input + (value * PRIME32_2), 13) * PRIME32_1;
private static uint RotateLeft32(uint value, int count) => (value << count) | (value >> (32 - count));
private void Initialize(uint seed)
{
HashSizeValue = 32;
_Seed32 = seed;
Initialize();
}
}
}