// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Ported from dotnet/runtime (release/9.0): // src/libraries/System.IO.Hashing/src/System/IO/Hashing/NonCryptographicHashAlgorithm.cs // Adapted for the Unity editor (Mono, netstandard2.1 API surface): // - Removed async methods (AppendAsync, WriteAsync, BeginWrite, EndWrite). // - Removed #if NET specific overloads (Write(ReadOnlySpan), etc.). // - Replaced SR resource strings with inline constants. // - Removed [DoesNotReturn] and other .NET-specific attributes. // - Removed EditorBrowsable/Obsolete GetHashCode override (not needed for internal use). using System; using System.Diagnostics; using System.IO; namespace Unity.PlasticSCM.Editor.Hashing { /// /// Represents a non-cryptographic hash algorithm. /// internal abstract class NonCryptographicHashAlgorithm { /// /// Gets the number of bytes produced from this hash algorithm. /// /// The number of bytes produced from this hash algorithm. public int HashLengthInBytes { get; } /// /// Called from constructors in derived classes to initialize the /// class. /// /// /// The number of bytes produced from this hash algorithm. /// /// /// is less than 1. /// protected NonCryptographicHashAlgorithm(int hashLengthInBytes) { if (hashLengthInBytes < 1) throw new ArgumentOutOfRangeException(nameof(hashLengthInBytes)); HashLengthInBytes = hashLengthInBytes; } /// /// When overridden in a derived class, /// appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. public abstract void Append(ReadOnlySpan source); /// /// When overridden in a derived class, /// resets the hash computation to the initial state. /// public abstract void Reset(); /// /// When overridden in a derived class, /// writes the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. protected abstract void GetCurrentHashCore(Span destination); /// /// Appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. /// /// is . /// public void Append(byte[] source) { if (source is null) throw new ArgumentNullException(nameof(source)); Append(new ReadOnlySpan(source)); } /// /// Appends the contents of to the data already /// processed for the current hash computation. /// /// The data to process. /// /// is . /// public void Append(Stream stream) { if (stream is null) throw new ArgumentNullException(nameof(stream)); stream.CopyTo(new CopyToDestinationStream(this)); } /// /// Gets the current computed hash value without modifying accumulated state. /// /// /// The hash value for the data already provided. /// public byte[] GetCurrentHash() { byte[] ret = new byte[HashLengthInBytes]; GetCurrentHashCore(ret); return ret; } /// /// Attempts to write the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. /// /// On success, receives the number of bytes written to . /// /// /// if is long enough to receive /// the computed hash value; otherwise, . /// public bool TryGetCurrentHash(Span destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// /// Writes the computed hash value to /// without modifying accumulated state. /// /// The buffer that receives the computed hash value. /// /// The number of bytes written to , /// which is always . /// /// /// is shorter than . /// public int GetCurrentHash(Span destination) { if (destination.Length < HashLengthInBytes) ThrowDestinationTooShort(); GetCurrentHashCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// /// Gets the current computed hash value and clears the accumulated state. /// /// /// The hash value for the data already provided. /// public byte[] GetHashAndReset() { byte[] ret = new byte[HashLengthInBytes]; GetHashAndResetCore(ret); return ret; } /// /// Attempts to write the computed hash value to . /// If successful, clears the accumulated state. /// /// The buffer that receives the computed hash value. /// /// On success, receives the number of bytes written to . /// /// /// and clears the accumulated state /// if is long enough to receive /// the computed hash value; otherwise, . /// public bool TryGetHashAndReset(Span destination, out int bytesWritten) { if (destination.Length < HashLengthInBytes) { bytesWritten = 0; return false; } GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); bytesWritten = HashLengthInBytes; return true; } /// /// Writes the computed hash value to /// then clears the accumulated state. /// /// The buffer that receives the computed hash value. /// /// The number of bytes written to , /// which is always . /// /// /// is shorter than . /// public int GetHashAndReset(Span destination) { if (destination.Length < HashLengthInBytes) ThrowDestinationTooShort(); GetHashAndResetCore(destination.Slice(0, HashLengthInBytes)); return HashLengthInBytes; } /// /// Writes the computed hash value to /// then clears the accumulated state. /// /// The buffer that receives the computed hash value. protected virtual void GetHashAndResetCore(Span destination) { Debug.Assert(destination.Length == HashLengthInBytes); GetCurrentHashCore(destination); Reset(); } private protected static void ThrowDestinationTooShort() => throw new ArgumentException("Destination is too short.", "destination"); /// Stream-derived type used to support copying from a source stream to this instance via CopyTo. private sealed class CopyToDestinationStream : Stream { private readonly NonCryptographicHashAlgorithm mHash; public CopyToDestinationStream(NonCryptographicHashAlgorithm hash) { mHash = hash; } public override bool CanWrite => true; public override void Write(byte[] buffer, int offset, int count) { mHash.Append(new ReadOnlySpan(buffer, offset, count)); } public override void WriteByte(byte value) { Span span = stackalloc byte[1]; span[0] = value; mHash.Append(span); } public override void Flush() { } public override bool CanRead => false; public override bool CanSeek => false; public override long Length => throw new NotSupportedException(); public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException(); } } }