| File: System\IO\Hashing\XxHash64.cs | Web Access |
| Project: src\runtime\src\libraries\System.IO.Hashing\src\System.IO.Hashing.csproj (System.IO.Hashing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; // Implemented from the specification at // https://github.com/Cyan4973/xxHash/blob/f9155bd4c57e2270a4ffbb176485e5d713de1c9b/doc/xxhash_spec.md namespace System.IO.Hashing { /// <summary> /// Provides an implementation of the XxHash64 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 XxHash64 : NonCryptographicHashAlgorithm { private const int HashSize = sizeof(ulong); private const int StripeSize = 4 * sizeof(ulong); private readonly ulong _seed; private State _state; private byte[]? _holdback; private long _length; /// <summary> /// Initializes a new instance of the <see cref="XxHash64"/> class. /// </summary> /// <remarks> /// The XxHash64 algorithm supports an optional seed value. /// Instances created with this constructor use the default seed, zero. /// </remarks> public XxHash64() : this(0) { } /// <summary> /// Initializes a new instance of the <see cref="XxHash64"/> class with /// a specified seed. /// </summary> /// <param name="seed"> /// The hash seed value for computations from this instance. /// </param> public XxHash64(long seed) : base(HashSize) { _seed = (ulong)seed; Reset(); } /// <summary>Initializes a new instance of the <see cref="XxHash64"/> class using the state from another instance.</summary> private XxHash64(ulong seed, State state, byte[]? holdback, long length) : base(HashSize) { _seed = seed; _state = state; if (((int)length & 0x1F) > 0) { _holdback = (byte[]?)holdback?.Clone(); } _length = length; } /// <summary>Returns a clone of the current instance, with a copy of the current instance's internal state.</summary> /// <returns>A new instance that will produce the same sequence of values as the current instance.</returns> public XxHash64 Clone() => new(_seed, _state, _holdback, _length); /// <summary> /// Resets the hash computation to the initial state. /// </summary> public override void Reset() { _state = new State(_seed); _length = 0; } /// <summary> /// Appends the contents of <paramref name="source"/> to the data already /// processed for the current hash computation. /// </summary> /// <param name="source">The data to process.</param> public override void Append(ReadOnlySpan<byte> source) { // Every time we've read 32 bytes, process the stripe. // Data that isn't perfectly mod-32 gets stored in a holdback // buffer. int held = (int)_length & 0x1F; if (held != 0) { int remain = StripeSize - held; if (source.Length >= remain) { source.Slice(0, remain).CopyTo(_holdback.AsSpan(held)); _state.ProcessStripe(_holdback); source = source.Slice(remain); _length += remain; } else { source.CopyTo(_holdback.AsSpan(held)); _length += source.Length; return; } } while (source.Length >= StripeSize) { _state.ProcessStripe(source); source = source.Slice(StripeSize); _length += StripeSize; } if (source.Length > 0) { _holdback ??= new byte[StripeSize]; source.CopyTo(_holdback); _length += source.Length; } } /// <summary> /// Writes the computed hash value to <paramref name="destination"/> /// without modifying accumulated state. /// </summary> 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() { int remainingLength = (int)_length & 0x1F; ReadOnlySpan<byte> remaining = ReadOnlySpan<byte>.Empty; if (remainingLength > 0) { remaining = new ReadOnlySpan<byte>(_holdback, 0, remainingLength); } return _state.Complete(_length, remaining); } /// <summary> /// Computes the XxHash64 hash of the provided data. /// </summary> /// <param name="source">The data to hash.</param> /// <returns>The XxHash64 hash of the provided data.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is <see langword="null"/>. /// </exception> public static byte[] Hash(byte[] source) { ArgumentNullException.ThrowIfNull(source); return Hash(new ReadOnlySpan<byte>(source)); } /// <summary> /// Computes the XxHash64 hash of the provided data using the provided seed. /// </summary> /// <param name="source">The data to hash.</param> /// <param name="seed">The seed value for this hash computation.</param> /// <returns>The XxHash64 hash of the provided data.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is <see langword="null"/>. /// </exception> public static byte[] Hash(byte[] source, long seed) { ArgumentNullException.ThrowIfNull(source); return Hash(new ReadOnlySpan<byte>(source), seed); } /// <summary> /// Computes the XxHash64 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 XxHash64 hash of the provided data.</returns> public static byte[] Hash(ReadOnlySpan<byte> source, long seed = 0) { byte[] ret = new byte[HashSize]; ulong hash = HashToUInt64(source, seed); BinaryPrimitives.WriteUInt64BigEndian(ret, hash); return ret; } /// <summary> /// Attempts to compute the XxHash64 hash of the provided data into the provided destination. /// </summary> /// <param name="source">The data to hash.</param> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <param name="bytesWritten"> /// On success, receives the number of bytes written to <paramref name="destination"/>. /// </param> /// <param name="seed">The seed value for this hash computation. The default is zero.</param> /// <returns> /// <see langword="true"/> if <paramref name="destination"/> is long enough to receive /// the computed hash value (8 bytes); otherwise, <see langword="false"/>. /// </returns> public static bool TryHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten, long seed = 0) { if (destination.Length < HashSize) { bytesWritten = 0; return false; } ulong hash = HashToUInt64(source, seed); BinaryPrimitives.WriteUInt64BigEndian(destination, hash); bytesWritten = HashSize; return true; } /// <summary> /// Computes the XxHash64 hash of the provided data into the provided destination. /// </summary> /// <param name="source">The data to hash.</param> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <param name="seed">The seed value for this hash computation. The default is zero.</param> /// <returns> /// The number of bytes written to <paramref name="destination"/>. /// </returns> public static int Hash(ReadOnlySpan<byte> source, Span<byte> destination, long seed = 0) { if (destination.Length < HashSize) { ThrowDestinationTooShort(); } ulong hash = HashToUInt64(source, seed); BinaryPrimitives.WriteUInt64BigEndian(destination, hash); return HashSize; } /// <summary>Computes the XxHash64 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 XxHash64 hash.</returns> [CLSCompliant(false)] public static ulong HashToUInt64(ReadOnlySpan<byte> source, long seed = 0) { int totalLength = source.Length; State state = new State((ulong)seed); while (source.Length >= StripeSize) { state.ProcessStripe(source); source = source.Slice(StripeSize); } return state.Complete((uint)totalLength, source); } } }