// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Threading; using System.Threading.Tasks; namespace EpicGames.Core { /// /// Struct representing a strongly typed Blake3 hash value (a 32-byte Blake3 hash). /// public readonly struct Blake3Hash : IEquatable, IComparable { /// /// Length of an Blake3Hash /// public const int NumBytes = 32; /// /// Length of the hash in bits /// public const int NumBits = NumBytes * 8; /// /// Memory storing the digest data /// public ReadOnlyMemory Memory { get; } /// /// Span for the underlying memory /// public ReadOnlySpan Span => Memory.Span; /// /// Hash consisting of zeroes /// public static Blake3Hash Zero { get; } = new Blake3Hash(new byte[NumBytes]); /// /// Constructor /// /// Memory to construct from public Blake3Hash(ReadOnlyMemory memory) { if (memory.Length != NumBytes) { throw new ArgumentException($"Blake3Hash must be {NumBytes} bytes long"); } Memory = memory; } /// /// Creates a content hash for a block of data. /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static Blake3Hash Compute(ReadOnlySpan data) { byte[] output = new byte[32]; Blake3.Hasher.Hash(data, output); return new Blake3Hash(output); } /// /// Creates a content hash for a block of data. /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static IoHash Compute(ReadOnlySequence sequence) { if (sequence.IsSingleSegment) { return Compute(sequence.FirstSpan); } using (Blake3.Hasher hasher = Blake3.Hasher.New()) { foreach (ReadOnlyMemory segment in sequence) { hasher.Update(segment.Span); } byte[] output = new byte[32]; hasher.Finalize(output); return new Blake3Hash(output); } } /// /// Creates a content hash for a stream. /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static IoHash Compute(Stream stream) { using (Blake3.Hasher hasher = Blake3.Hasher.New()) { Span buffer = new byte[16384]; int length; while ((length = stream.Read(buffer)) > 0) { hasher.Update(buffer.Slice(0, length)); } byte[] output = new byte[32]; hasher.Finalize(output); return new Blake3Hash(output); } } /// /// Creates a content hash for a stream asynchronously. /// /// Data to compute the hash for /// Cancellation token for the operation /// New content hash instance containing the hash of the data public static async Task ComputeAsync(Stream stream, CancellationToken cancellationToken = default) { using (Blake3.Hasher hasher = Blake3.Hasher.New()) { Memory buffer = new byte[16384]; int length; while ((length = await stream.ReadAsync(buffer, cancellationToken)) > 0) { hasher.Update(buffer.Slice(0, length).Span); } byte[] output = new byte[32]; hasher.Finalize(output); return new Blake3Hash(output); } } /// /// Parses a digest from the given hex string /// /// /// public static Blake3Hash Parse(string text) { return new Blake3Hash(StringUtils.ParseHexString(text)); } /// public int CompareTo(Blake3Hash other) { ReadOnlySpan a = Span; ReadOnlySpan b = other.Span; for (int idx = 0; idx < a.Length && idx < b.Length; idx++) { int compare = a[idx] - b[idx]; if (compare != 0) { return compare; } } return a.Length - b.Length; } /// public bool Equals(Blake3Hash other) => Span.SequenceEqual(other.Span); /// public override bool Equals(object? obj) => (obj is Blake3Hash hash) && hash.Span.SequenceEqual(Span); /// public override int GetHashCode() => BinaryPrimitives.ReadInt32LittleEndian(Span); /// public override string ToString() => StringUtils.FormatHexString(Memory.Span); /// /// Test two hash values for equality /// public static bool operator ==(Blake3Hash a, Blake3Hash b) => a.Span.SequenceEqual(b.Span); /// /// Test two hash values for equality /// public static bool operator !=(Blake3Hash a, Blake3Hash b) => !(a == b); /// /// Tests whether A > B /// public static bool operator >(Blake3Hash a, Blake3Hash b) => a.CompareTo(b) > 0; /// /// Tests whether A is less than B /// public static bool operator <(Blake3Hash a, Blake3Hash b) => a.CompareTo(b) < 0; /// /// Tests whether A is greater than or equal to B /// public static bool operator >=(Blake3Hash a, Blake3Hash b) => a.CompareTo(b) >= 0; /// /// Tests whether A is less than or equal to B /// public static bool operator <=(Blake3Hash a, Blake3Hash b) => a.CompareTo(b) <= 0; } /// /// Extension methods for dealing with Blake3Hash values /// public static class Blake3HashExtensions { /// /// Read an from a memory reader /// /// /// public static Blake3Hash ReadBlake3Hash(this MemoryReader reader) { return new Blake3Hash(reader.ReadFixedLengthBytes(Blake3Hash.NumBytes)); } /// /// Write an to a memory writer /// /// /// public static void WriteBlake3Hash(this MemoryWriter writer, Blake3Hash hash) { writer.WriteFixedLengthBytes(hash.Span); } } }