Skip to content

Commit

Permalink
Add HashTo/GetCurrentHashAs{UInt32/64} methods (#78075)
Browse files Browse the repository at this point in the history
* Add HashTo/GetCurrentHashAs{UInt32/64} methods

* Address PR feedback
  • Loading branch information
stephentoub committed Nov 16, 2022
1 parent ee9045c commit 9bddd01
Show file tree
Hide file tree
Showing 17 changed files with 258 additions and 51 deletions.
20 changes: 20 additions & 0 deletions src/libraries/System.IO.Hashing/ref/System.IO.Hashing.cs
Expand Up @@ -10,23 +10,31 @@ public sealed partial class Crc32 : System.IO.Hashing.NonCryptographicHashAlgori
{
public Crc32() : base (default(int)) { }
public override void Append(System.ReadOnlySpan<byte> source) { }
[System.CLSCompliantAttribute(false)]
public uint GetCurrentHashAsUInt32() { throw null; }
protected override void GetCurrentHashCore(System.Span<byte> destination) { }
protected override void GetHashAndResetCore(System.Span<byte> destination) { }
public static byte[] Hash(byte[] source) { throw null; }
public static byte[] Hash(System.ReadOnlySpan<byte> source) { throw null; }
public static int Hash(System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint HashToUInt32(System.ReadOnlySpan<byte> source) { throw null; }
public override void Reset() { }
public static bool TryHash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class Crc64 : System.IO.Hashing.NonCryptographicHashAlgorithm
{
public Crc64() : base (default(int)) { }
public override void Append(System.ReadOnlySpan<byte> source) { }
[System.CLSCompliantAttribute(false)]
public ulong GetCurrentHashAsUInt64() { throw null; }
protected override void GetCurrentHashCore(System.Span<byte> destination) { }
protected override void GetHashAndResetCore(System.Span<byte> destination) { }
public static byte[] Hash(byte[] source) { throw null; }
public static byte[] Hash(System.ReadOnlySpan<byte> source) { throw null; }
public static int Hash(System.ReadOnlySpan<byte> source, System.Span<byte> destination) { throw null; }
[System.CLSCompliantAttribute(false)]
public static ulong HashToUInt64(System.ReadOnlySpan<byte> source) { throw null; }
public override void Reset() { }
public static bool TryHash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
Expand Down Expand Up @@ -56,11 +64,15 @@ public sealed partial class XxHash3 : System.IO.Hashing.NonCryptographicHashAlgo
public XxHash3() : base (default(int)) { }
public XxHash3(long seed) : base (default(int)) { }
public override void Append(System.ReadOnlySpan<byte> source) { }
[System.CLSCompliantAttribute(false)]
public ulong GetCurrentHashAsUInt64() { throw null; }
protected override void GetCurrentHashCore(System.Span<byte> destination) { }
public static byte[] Hash(byte[] source) { throw null; }
public static byte[] Hash(byte[] source, long seed) { throw null; }
public static byte[] Hash(System.ReadOnlySpan<byte> source, long seed = (long)0) { throw null; }
public static int Hash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, long seed = (long)0) { throw null; }
[System.CLSCompliantAttribute(false)]
public static ulong HashToUInt64(System.ReadOnlySpan<byte> source, long seed = (long)0) { throw null; }
public override void Reset() { }
public static bool TryHash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, long seed = (long)0) { throw null; }
}
Expand All @@ -69,11 +81,15 @@ public sealed partial class XxHash32 : System.IO.Hashing.NonCryptographicHashAlg
public XxHash32() : base (default(int)) { }
public XxHash32(int seed) : base (default(int)) { }
public override void Append(System.ReadOnlySpan<byte> source) { }
[System.CLSCompliantAttribute(false)]
public uint GetCurrentHashAsUInt32() { throw null; }
protected override void GetCurrentHashCore(System.Span<byte> destination) { }
public static byte[] Hash(byte[] source) { throw null; }
public static byte[] Hash(byte[] source, int seed) { throw null; }
public static byte[] Hash(System.ReadOnlySpan<byte> source, int seed = 0) { throw null; }
public static int Hash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, int seed = 0) { throw null; }
[System.CLSCompliantAttribute(false)]
public static uint HashToUInt32(System.ReadOnlySpan<byte> source, int seed = 0) { throw null; }
public override void Reset() { }
public static bool TryHash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int seed = 0) { throw null; }
}
Expand All @@ -82,11 +98,15 @@ public sealed partial class XxHash64 : System.IO.Hashing.NonCryptographicHashAlg
public XxHash64() : base (default(int)) { }
public XxHash64(long seed) : base (default(int)) { }
public override void Append(System.ReadOnlySpan<byte> source) { }
[System.CLSCompliantAttribute(false)]
public ulong GetCurrentHashAsUInt64() { throw null; }
protected override void GetCurrentHashCore(System.Span<byte> destination) { }
public static byte[] Hash(byte[] source) { throw null; }
public static byte[] Hash(byte[] source, long seed) { throw null; }
public static byte[] Hash(System.ReadOnlySpan<byte> source, long seed = (long)0) { throw null; }
public static int Hash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, long seed = (long)0) { throw null; }
[System.CLSCompliantAttribute(false)]
public static ulong HashToUInt64(System.ReadOnlySpan<byte> source, long seed = (long)0) { throw null; }
public override void Reset() { }
public static bool TryHash(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, long seed = (long)0) { throw null; }
}
Expand Down
34 changes: 22 additions & 12 deletions src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32.cs
Expand Up @@ -11,8 +11,9 @@ namespace System.IO.Hashing
/// </summary>
/// <remarks>
/// <para>
/// This implementation emits the answer in the Little Endian byte order so that
/// the CRC residue relationship (CRC(message concat CRC(message))) is a fixed value) holds.
/// For methods that return byte arrays or that write into spans of bytes, this implementation
/// emits the answer in the Little Endian byte order so that the CRC residue relationship
/// (CRC(message concat CRC(message))) is a fixed value) holds.
/// For CRC-32 this stable output is the byte sequence <c>{ 0x1C, 0xDF, 0x44, 0x21 }</c>,
/// the Little Endian representation of <c>0x2144DF1C</c>.
/// </para>
Expand Down Expand Up @@ -77,6 +78,11 @@ protected override void GetHashAndResetCore(Span<byte> destination)
_crc = InitialState;
}

/// <summary>Gets the current computed hash value without modifying accumulated state.</summary>
/// <returns>The hash value for the data already provided.</returns>
[CLSCompliant(false)]
public uint GetCurrentHashAsUInt32() => ~_crc;

/// <summary>
/// Computes the CRC-32 hash of the provided data.
/// </summary>
Expand All @@ -103,7 +109,8 @@ public static byte[] Hash(byte[] source)
public static byte[] Hash(ReadOnlySpan<byte> source)
{
byte[] ret = new byte[Size];
StaticHash(source, ret);
uint hash = HashToUInt32(source);
BinaryPrimitives.WriteUInt32LittleEndian(ret, hash);
return ret;
}

Expand All @@ -127,7 +134,9 @@ public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, ou
return false;
}

bytesWritten = StaticHash(source, destination);
uint hash = HashToUInt32(source);
BinaryPrimitives.WriteUInt32LittleEndian(destination, hash);
bytesWritten = Size;
return true;
}

Expand All @@ -146,17 +155,18 @@ public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination)
ThrowDestinationTooShort();
}

return StaticHash(source, destination);
}

private static int StaticHash(ReadOnlySpan<byte> source, Span<byte> destination)
{
uint crc = InitialState;
crc = Update(crc, source);
BinaryPrimitives.WriteUInt32LittleEndian(destination, ~crc);
uint hash = HashToUInt32(source);
BinaryPrimitives.WriteUInt32LittleEndian(destination, hash);
return Size;
}

/// <summary>Computes the CRC-32 hash of the provided data.</summary>
/// <param name="source">The data to hash.</param>
/// <returns>The computed CRC-32 hash.</returns>
[CLSCompliant(false)]
public static uint HashToUInt32(ReadOnlySpan<byte> source) =>
~Update(InitialState, source);

private static uint Update(uint crc, ReadOnlySpan<byte> source)
{
for (int i = 0; i < source.Length; i++)
Expand Down
32 changes: 21 additions & 11 deletions src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc64.cs
Expand Up @@ -10,7 +10,8 @@ namespace System.IO.Hashing
/// </summary>
/// <remarks>
/// <para>
/// This implementation emits the answer in the Big Endian byte order so that
/// For methods that return byte arrays or that write into spans of bytes,
/// this implementation emits the answer in the Big Endian byte order so that
/// the CRC residue relationship (CRC(message concat CRC(message))) is a fixed value) holds.
/// For CRC-64 this stable output is the byte sequence
/// <c>{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }</c>.
Expand Down Expand Up @@ -75,6 +76,11 @@ protected override void GetHashAndResetCore(Span<byte> destination)
_crc = InitialState;
}

/// <summary>Gets the current computed hash value without modifying accumulated state.</summary>
/// <returns>The hash value for the data already provided.</returns>
[CLSCompliant(false)]
public ulong GetCurrentHashAsUInt64() => _crc;

/// <summary>
/// Computes the CRC-64 hash of the provided data.
/// </summary>
Expand All @@ -101,7 +107,8 @@ public static byte[] Hash(byte[] source)
public static byte[] Hash(ReadOnlySpan<byte> source)
{
byte[] ret = new byte[Size];
StaticHash(source, ret);
ulong hash = HashToUInt64(source);
BinaryPrimitives.WriteUInt64BigEndian(ret, hash);
return ret;
}

Expand All @@ -125,7 +132,9 @@ public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, ou
return false;
}

bytesWritten = StaticHash(source, destination);
ulong hash = HashToUInt64(source);
BinaryPrimitives.WriteUInt64BigEndian(destination, hash);
bytesWritten = Size;
return true;
}

Expand All @@ -144,17 +153,18 @@ public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination)
ThrowDestinationTooShort();
}

return StaticHash(source, destination);
}

private static int StaticHash(ReadOnlySpan<byte> source, Span<byte> destination)
{
ulong crc = InitialState;
crc = Update(crc, source);
BinaryPrimitives.WriteUInt64BigEndian(destination, crc);
ulong hash = HashToUInt64(source);
BinaryPrimitives.WriteUInt64BigEndian(destination, hash);
return Size;
}

/// <summary>Computes the CRC-64 hash of the provided data.</summary>
/// <param name="source">The data to hash.</param>
/// <returns>The computed CRC-64 hash.</returns>
[CLSCompliant(false)]
public static ulong HashToUInt64(ReadOnlySpan<byte> source) =>
Update(InitialState, source);

private static ulong Update(ulong crc, ReadOnlySpan<byte> source)
{
for (int i = 0; i < source.Length; i++)
Expand Down
34 changes: 24 additions & 10 deletions src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash3.cs
Expand Up @@ -129,7 +129,8 @@ public static byte[] Hash(byte[] source, long seed)
public static byte[] Hash(ReadOnlySpan<byte> source, long seed = 0)
{
byte[] result = new byte[HashLengthInBytes];
BinaryPrimitives.WriteInt64BigEndian(result, HashToInt64(source, seed));
ulong hash = HashToUInt64(source, seed);
BinaryPrimitives.WriteUInt64BigEndian(result, hash);
return result;
}

Expand Down Expand Up @@ -159,7 +160,7 @@ public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, ou
{
if (destination.Length >= sizeof(long))
{
long hash = HashToInt64(source, seed);
ulong hash = HashToUInt64(source, seed);

if (BitConverter.IsLittleEndian)
{
Expand All @@ -175,28 +176,32 @@ public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, ou
return false;
}

// TODO https://github.com/dotnet/runtime/issues/76279: Make this public.
private static long HashToInt64(ReadOnlySpan<byte> source, long seed = 0)
/// <summary>Computes the XXH3 hash of the provided data.</summary>
/// <param name="source">The data to hash.</param>
/// <param name="seed">The seed value for this hash computation.</param>
/// <returns>The computed XXH3 hash.</returns>
[CLSCompliant(false)]
public static ulong HashToUInt64(ReadOnlySpan<byte> source, long seed = 0)
{
uint length = (uint)source.Length;
fixed (byte* sourcePtr = &MemoryMarshal.GetReference(source))
{
if (length <= 16)
{
return (long)HashLength0To16(sourcePtr, length, (ulong)seed);
return HashLength0To16(sourcePtr, length, (ulong)seed);
}

if (length <= 128)
{
return (long)HashLength17To128(sourcePtr, length, (ulong)seed);
return HashLength17To128(sourcePtr, length, (ulong)seed);
}

if (length <= MidSizeMaxBytes)
{
return (long)HashLength129To240(sourcePtr, length, (ulong)seed);
return HashLength129To240(sourcePtr, length, (ulong)seed);
}

return (long)HashLengthOver240(sourcePtr, length, (ulong)seed);
return HashLengthOver240(sourcePtr, length, (ulong)seed);
}
}

Expand Down Expand Up @@ -310,6 +315,15 @@ public override void Append(ReadOnlySpan<byte> source)
/// <summary>Writes the computed 64-bit hash value to <paramref name="destination"/> without modifying accumulated state.</summary>
/// <param name="destination">The buffer that receives the computed hash value.</param>
protected override void GetCurrentHashCore(Span<byte> destination)
{
ulong hash = GetCurrentHashAsUInt64();
BinaryPrimitives.WriteUInt64BigEndian(destination, hash);
}

/// <summary>Gets the current computed hash value without modifying accumulated state.</summary>
/// <returns>The hash value for the data already provided.</returns>
[CLSCompliant(false)]
public ulong GetCurrentHashAsUInt64()
{
ulong current;

Expand Down Expand Up @@ -352,11 +366,11 @@ protected override void GetCurrentHashCore(Span<byte> destination)
{
fixed (byte* buffer = _state.Buffer)
{
current = (ulong)HashToInt64(new ReadOnlySpan<byte>(buffer, (int)_state.TotalLength), (long)_state.Seed);
current = HashToUInt64(new ReadOnlySpan<byte>(buffer, (int)_state.TotalLength), (long)_state.Seed);
}
}

BinaryPrimitives.WriteUInt64BigEndian(destination, current);
return current;

void DigestLong(ulong* accumulators, byte* secret)
{
Expand Down
38 changes: 29 additions & 9 deletions src/libraries/System.IO.Hashing/src/System/IO/Hashing/XxHash32.cs
Expand Up @@ -11,6 +11,10 @@ namespace System.IO.Hashing
/// <summary>
/// Provides an implementation of the XxHash32 algorithm.
/// </summary>
/// <remarks>
/// For methods that persist the computed numerical hash value as bytes,
/// the value is written in the Big Endian byte order.
/// </remarks>
public sealed partial class XxHash32 : NonCryptographicHashAlgorithm
{
private const int HashSize = sizeof(uint);
Expand Down Expand Up @@ -109,6 +113,15 @@ public override void Append(ReadOnlySpan<byte> source)
/// without modifying accumulated state.
/// </summary>
protected override void GetCurrentHashCore(Span<byte> destination)
{
uint hash = GetCurrentHashAsUInt32();
BinaryPrimitives.WriteUInt32BigEndian(destination, hash);
}

/// <summary>Gets the current computed hash value without modifying accumulated state.</summary>
/// <returns>The hash value for the data already provided.</returns>
[CLSCompliant(false)]
public uint GetCurrentHashAsUInt32()
{
int remainingLength = _length & 0x0F;
ReadOnlySpan<byte> remaining = ReadOnlySpan<byte>.Empty;
Expand All @@ -118,8 +131,7 @@ protected override void GetCurrentHashCore(Span<byte> destination)
remaining = new ReadOnlySpan<byte>(_holdback, 0, remainingLength);
}

uint acc = _state.Complete(_length, remaining);
BinaryPrimitives.WriteUInt32BigEndian(destination, acc);
return _state.Complete(_length, remaining);
}

/// <summary>
Expand Down Expand Up @@ -168,7 +180,8 @@ public static byte[] Hash(byte[] source, int seed)
public static byte[] Hash(ReadOnlySpan<byte> source, int seed = 0)
{
byte[] ret = new byte[HashSize];
StaticHash(source, ret, seed);
uint hash = HashToUInt32(source, seed);
BinaryPrimitives.WriteUInt32BigEndian(ret, hash);
return ret;
}

Expand All @@ -193,7 +206,9 @@ public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, ou
return false;
}

bytesWritten = StaticHash(source, destination, seed);
uint hash = HashToUInt32(source, seed);
BinaryPrimitives.WriteUInt32BigEndian(destination, hash);
bytesWritten = HashSize;
return true;
}

Expand All @@ -213,10 +228,17 @@ public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination, int se
ThrowDestinationTooShort();
}

return StaticHash(source, destination, seed);
uint hash = HashToUInt32(source, seed);
BinaryPrimitives.WriteUInt32BigEndian(destination, hash);
return HashSize;
}

private static int StaticHash(ReadOnlySpan<byte> source, Span<byte> destination, int seed)
/// <summary>Computes the XxHash32 hash of the provided data.</summary>
/// <param name="source">The data to hash.</param>
/// <param name="seed">The seed value for this hash computation. The default is zero.</param>
/// <returns>The computed XxHash32 hash.</returns>
[CLSCompliant(false)]
public static uint HashToUInt32(ReadOnlySpan<byte> source, int seed = 0)
{
int totalLength = source.Length;
State state = new State((uint)seed);
Expand All @@ -227,9 +249,7 @@ private static int StaticHash(ReadOnlySpan<byte> source, Span<byte> destination,
source = source.Slice(StripeSize);
}

uint val = state.Complete(totalLength, source);
BinaryPrimitives.WriteUInt32BigEndian(destination, val);
return HashSize;
return state.Complete(totalLength, source);
}
}
}

0 comments on commit 9bddd01

Please sign in to comment.