XxHash32
Provides an implementation of the XxHash32 algorithm.
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace System.IO.Hashing
{
public sealed class XxHash32 : NonCryptographicHashAlgorithm
{
private struct State
{
private uint _acc1;
private uint _acc2;
private uint _acc3;
private uint _acc4;
private readonly uint _smallAcc;
private bool _hadFullStripe;
internal State(uint seed)
{
_acc1 = seed + 606290984;
_acc2 = (uint)((int)seed + -2048144777);
_acc3 = seed;
_acc4 = (uint)((int)seed - -1640531535);
_smallAcc = seed + 374761393;
_hadFullStripe = false;
}
internal void ProcessStripe(ReadOnlySpan<byte> source)
{
source = source.Slice(0, 16);
_acc1 = ApplyRound(_acc1, source);
_acc2 = ApplyRound(_acc2, source.Slice(4));
_acc3 = ApplyRound(_acc3, source.Slice(8));
_acc4 = ApplyRound(_acc4, source.Slice(12));
_hadFullStripe = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Runtime.CompilerServices.IsReadOnly]
private uint Converge()
{
return System.Numerics.BitOperations.RotateLeft(_acc1, 1) + System.Numerics.BitOperations.RotateLeft(_acc2, 7) + System.Numerics.BitOperations.RotateLeft(_acc3, 12) + System.Numerics.BitOperations.RotateLeft(_acc4, 18);
}
private static uint ApplyRound(uint acc, ReadOnlySpan<byte> lane)
{
acc = (uint)((int)acc + (int)BinaryPrimitives.ReadUInt32LittleEndian(lane) * -2048144777);
acc = System.Numerics.BitOperations.RotateLeft(acc, 13);
acc = (uint)((int)acc * -1640531535);
return acc;
}
[System.Runtime.CompilerServices.IsReadOnly]
internal uint Complete(int length, ReadOnlySpan<byte> remaining)
{
uint num = _hadFullStripe ? Converge() : _smallAcc;
num = (uint)((int)num + length);
while (remaining.Length >= 4) {
uint num2 = BinaryPrimitives.ReadUInt32LittleEndian(remaining);
num = (uint)((int)num + (int)num2 * -1028477379);
num = System.Numerics.BitOperations.RotateLeft(num, 17);
num *= 668265263;
remaining = remaining.Slice(4);
}
foreach (uint num3 in remaining) {
num += num3 * 374761393;
num = System.Numerics.BitOperations.RotateLeft(num, 11);
num = (uint)((int)num * -1640531535);
}
num ^= num >> 15;
num = (uint)((int)num * -2048144777);
num ^= num >> 13;
num = (uint)((int)num * -1028477379);
return num ^ (num >> 16);
}
}
private const int HashSize = 4;
private const int StripeSize = 16;
private readonly uint _seed;
private State _state;
private byte[] _holdback;
private int _length;
public XxHash32()
: this(0)
{
}
public XxHash32(int seed)
: base(4)
{
_seed = (uint)seed;
Reset();
}
private XxHash32(uint seed, State state, byte[] holdback, int length)
: base(4)
{
_seed = seed;
_state = state;
if ((length & 15) > 0)
_holdback = (byte[])holdback?.Clone();
_length = length;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public XxHash32 Clone()
{
return new XxHash32(_seed, _state, _holdback, _length);
}
public override void Reset()
{
_state = new State(_seed);
_length = 0;
}
public override void Append(ReadOnlySpan<byte> source)
{
int num = _length & 15;
if (num != 0) {
int num2 = 16 - num;
if (source.Length < num2) {
source.CopyTo(_holdback.AsSpan(num));
_length += source.Length;
return;
}
source.Slice(0, num2).CopyTo(_holdback.AsSpan(num));
_state.ProcessStripe(_holdback);
source = source.Slice(num2);
_length += num2;
}
while (source.Length >= 16) {
_state.ProcessStripe(source);
source = source.Slice(16);
_length += 16;
}
if (source.Length > 0) {
if (_holdback == null)
_holdback = new byte[16];
source.CopyTo(_holdback);
_length += source.Length;
}
}
protected override void GetCurrentHashCore(Span<byte> destination)
{
uint currentHashAsUInt = GetCurrentHashAsUInt32();
BinaryPrimitives.WriteUInt32BigEndian(destination, currentHashAsUInt);
}
[CLSCompliant(false)]
public uint GetCurrentHashAsUInt32()
{
int num = _length & 15;
ReadOnlySpan<byte> remaining = ReadOnlySpan<byte>.Empty;
if (num > 0)
remaining = new ReadOnlySpan<byte>(_holdback, 0, num);
return _state.Complete(_length, remaining);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public static byte[] Hash(byte[] source)
{
ExceptionPolyfills.ThrowIfNull(source, "source");
return Hash(new ReadOnlySpan<byte>(source), 0);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public static byte[] Hash(byte[] source, int seed)
{
ExceptionPolyfills.ThrowIfNull(source, "source");
return Hash(new ReadOnlySpan<byte>(source), seed);
}
[return: System.Runtime.CompilerServices.Nullable(1)]
public static byte[] Hash(ReadOnlySpan<byte> source, int seed = 0)
{
byte[] obj = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(value: HashToUInt32(source, seed), destination: obj);
return obj;
}
public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, int seed = 0)
{
if (destination.Length < 4) {
bytesWritten = 0;
return false;
}
uint value = HashToUInt32(source, seed);
BinaryPrimitives.WriteUInt32BigEndian(destination, value);
bytesWritten = 4;
return true;
}
public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination, int seed = 0)
{
if (destination.Length < 4)
NonCryptographicHashAlgorithm.ThrowDestinationTooShort();
uint value = HashToUInt32(source, seed);
BinaryPrimitives.WriteUInt32BigEndian(destination, value);
return 4;
}
[CLSCompliant(false)]
public static uint HashToUInt32(ReadOnlySpan<byte> source, int seed = 0)
{
int length = source.Length;
State state = new State((uint)seed);
while (source.Length >= 16) {
state.ProcessStripe(source);
source = source.Slice(16);
}
return state.Complete(length, source);
}
}
}