| File: Utilities\Hashing\NonCryptographicHashAlgorithm.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Utilities.Shared\Microsoft.AspNetCore.Razor.Utilities.Shared.csproj (Microsoft.AspNetCore.Razor.Utilities.Shared) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Copied from https://github.com/dotnet/runtime/blob/f1d463f46268382a8287d71aea929dadaa5dfef5/src/libraries/System.IO.Hashing/src/System/IO/Hashing/NonCryptographicHashAlgorithm.cs#L1 // Remove once we can actually add a reference to System.IO.Hashing v8.0.0 // <auto-generated/> using System.Buffers; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; namespace System.IO.Hashing; /// <summary> /// Represents a non-cryptographic hash algorithm. /// </summary> internal abstract class NonCryptographicHashAlgorithm { /// <summary> /// Gets the number of bytes produced from this hash algorithm. /// </summary> /// <value>The number of bytes produced from this hash algorithm.</value> public int HashLengthInBytes { get; } /// <summary> /// Called from constructors in derived classes to initialize the /// <see cref="NonCryptographicHashAlgorithm"/> class. /// </summary> /// <param name="hashLengthInBytes"> /// The number of bytes produced from this hash algorithm. /// </param> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="hashLengthInBytes"/> is less than 1. /// </exception> protected NonCryptographicHashAlgorithm(int hashLengthInBytes) { if (hashLengthInBytes < 1) throw new ArgumentOutOfRangeException(nameof(hashLengthInBytes)); HashLengthInBytes = hashLengthInBytes; } /// <summary> /// When overridden in a derived class, /// 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 abstract void Append(ReadOnlySpan<byte> source); /// <summary> /// When overridden in a derived class, /// resets the hash computation to the initial state. /// </summary> public abstract void Reset(); /// <summary> /// When overridden in a derived class, /// writes the computed hash value to <paramref name="destination"/> /// without modifying accumulated state. /// </summary> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <remarks> /// <para> /// Implementations of this method must write exactly /// <see cref="HashLengthInBytes"/> bytes to <paramref name="destination"/>. /// Do not assume that the buffer was zero-initialized. /// </para> /// <para> /// The <see cref="NonCryptographicHashAlgorithm"/> class validates the /// size of the buffer before calling this method, and slices the span /// down to be exactly <see cref="HashLengthInBytes"/> in length. /// </para> /// </remarks> protected abstract void GetCurrentHashCore(Span<byte> destination); /// <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> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is <see langword="null"/>. /// </exception> public void Append(byte[] source) { if (source is null) { throw new ArgumentNullException(nameof(source)); } Append(new ReadOnlySpan<byte>(source)); } /// <summary> /// Appends the contents of <paramref name="stream"/> to the data already /// processed for the current hash computation. /// </summary> /// <param name="stream">The data to process.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/> is <see langword="null"/>. /// </exception> /// <seealso cref="AppendAsync(Stream, CancellationToken)"/> public void Append(Stream stream) { if (stream is null) { throw new ArgumentNullException(nameof(stream)); } byte[] buffer = ArrayPool<byte>.Shared.Rent(4096); while (true) { int read = stream.Read(buffer, 0, buffer.Length); if (read == 0) { break; } Append(new ReadOnlySpan<byte>(buffer, 0, read)); } ArrayPool<byte>.Shared.Return(buffer); } /// <summary> /// Asychronously reads the contents of <paramref name="stream"/> /// and appends them to the data already /// processed for the current hash computation. /// </summary> /// <param name="stream">The data to process.</param> /// <param name="cancellationToken"> /// The token to monitor for cancellation requests. /// The default value is <see cref="CancellationToken.None"/>. /// </param> /// <returns> /// A task that represents the asynchronous append operation. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/> is <see langword="null"/>. /// </exception> public Task AppendAsync(Stream stream, CancellationToken cancellationToken = default) { if (stream is null) { throw new ArgumentNullException(nameof(stream)); } return AppendAsyncCore(stream, cancellationToken); } private async Task AppendAsyncCore(Stream stream, CancellationToken cancellationToken) { byte[] buffer = ArrayPool<byte>.Shared.Rent(4096); while (true) { #if NET int read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); #else int read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); #endif if (read == 0) { break; } Append(new ReadOnlySpan<byte>(buffer, 0, read)); } ArrayPool<byte>.Shared.Return(buffer); } /// <summary> /// Gets the current computed hash value without modifying accumulated state. /// </summary> /// <returns> /// The hash value for the data already provided. /// </returns> public byte[] GetCurrentHash() { byte[] ret = new byte[HashLengthInBytes]; GetCurrentHashCore(ret); return ret; } /// <summary> /// Attempts to write the computed hash value to <paramref name="destination"/> /// without modifying accumulated state. /// </summary> /// <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> /// <returns> /// <see langword="true"/> if <paramref name="destination"/> is long enough to receive /// the computed hash value; otherwise, <see langword="false"/>. /// </returns> public bool TryGetCurrentHash(Span<byte> destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// <summary> /// Writes the computed hash value to <paramref name="destination"/> /// without modifying accumulated state. /// </summary> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <returns> /// The number of bytes written to <paramref name="destination"/>, /// which is always <see cref="HashLengthInBytes"/>. /// </returns> /// <exception cref="ArgumentException"> /// <paramref name="destination"/> is shorter than <see cref="HashLengthInBytes"/>. /// </exception> public int GetCurrentHash(Span<byte> destination) { if (destination.Length < HashLengthInBytes) { ThrowDestinationTooShort(); } GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// <summary> /// Gets the current computed hash value and clears the accumulated state. /// </summary> /// <returns> /// The hash value for the data already provided. /// </returns> public byte[] GetHashAndReset() { byte[] ret = new byte[HashLengthInBytes]; GetHashAndResetCore(ret); return ret; } /// <summary> /// Attempts to write the computed hash value to <paramref name="destination"/>. /// If successful, clears the accumulated state. /// </summary> /// <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> /// <returns> /// <see langword="true"/> and clears the accumulated state /// if <paramref name="destination"/> is long enough to receive /// the computed hash value; otherwise, <see langword="false"/>. /// </returns> public bool TryGetHashAndReset(Span<byte> destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// <summary> /// Writes the computed hash value to <paramref name="destination"/> /// then clears the accumulated state. /// </summary> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <returns> /// The number of bytes written to <paramref name="destination"/>, /// which is always <see cref="HashLengthInBytes"/>. /// </returns> /// <exception cref="ArgumentException"> /// <paramref name="destination"/> is shorter than <see cref="HashLengthInBytes"/>. /// </exception> public int GetHashAndReset(Span<byte> destination) { if (destination.Length < HashLengthInBytes) { ThrowDestinationTooShort(); } GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// <summary> /// Writes the computed hash value to <paramref name="destination"/> /// then clears the accumulated state. /// </summary> /// <param name="destination">The buffer that receives the computed hash value.</param> /// <remarks> /// <para> /// Implementations of this method must write exactly /// <see cref="HashLengthInBytes"/> bytes to <paramref name="destination"/>. /// Do not assume that the buffer was zero-initialized. /// </para> /// <para> /// The <see cref="NonCryptographicHashAlgorithm"/> class validates the /// size of the buffer before calling this method, and slices the span /// down to be exactly <see cref="HashLengthInBytes"/> in length. /// </para> /// <para> /// The default implementation of this method calls /// <see cref="GetCurrentHashCore"/> followed by <see cref="Reset"/>. /// Overrides of this method do not need to call either of those methods, /// but must ensure that the caller cannot observe a difference in behavior. /// </para> /// </remarks> protected virtual void GetHashAndResetCore(Span<byte> destination) { Debug.Assert(destination.Length == HashLengthInBytes); GetCurrentHashCore(destination); Reset(); } /// <summary> /// This method is not supported and should not be called. /// Call <see cref="GetCurrentHash()"/> or <see cref="GetHashAndReset()"/> /// instead. /// </summary> /// <returns>This method will always throw a <see cref="NotSupportedException"/>.</returns> /// <exception cref="NotSupportedException">In all cases.</exception> [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use GetCurrentHash() to retrieve the computed hash code.", true)] #pragma warning disable CS0809 // Obsolete member overrides non-obsolete member public override int GetHashCode() #pragma warning restore CS0809 // Obsolete member overrides non-obsolete member { throw new NotSupportedException("GetHashCode not supported"); } [DoesNotReturn] private protected static void ThrowDestinationTooShort() => throw new ArgumentException("Destination is too short", "destination"); }