// 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);
}
}
}