// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Buffers; using System.Security.Cryptography; using System.Text; using System.Buffers.Binary; namespace EpicGames.Core { /// /// Struct representing a weakly typed hash value. Counterpart to - a strongly typed digest. /// public struct Digest { /// /// Memory storing the digest data /// public ReadOnlyMemory Memory; /// /// Accessor for the span of memory storing the data /// public ReadOnlySpan Span => Memory.Span; /// /// Constructor /// /// Memory to construct from public Digest(ReadOnlyMemory Memory) { this.Memory = Memory; } /// /// Creates a content hash for a block of data, using a given algorithm. /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static Digest Compute(byte[] Data) where T : DigestTraits, new() { using HashAlgorithm Algorithm = Digest.Traits.CreateAlgorithm(); return Algorithm.ComputeHash(Data); } /// /// Creates a content hash for a block of data, using a given algorithm. /// /// Text to compute the hash for /// New content hash instance containing the hash of the data public static Digest Compute(string Text) where T : DigestTraits, new() { return Compute(Encoding.UTF8.GetBytes(Text)); } /// /// Creates a content hash for a block of data, using a given algorithm. /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static Digest Compute(ReadOnlySpan Data) where T : DigestTraits, new() { byte[] Value = new byte[Digest.Traits.Length]; using (HashAlgorithm Algorithm = Digest.Traits.CreateAlgorithm()) { if (!Algorithm.TryComputeHash(Data, Value, out int Written) || Written != Value.Length) { throw new InvalidOperationException("Unable to compute hash for buffer"); } } return new Digest(Value); } /// /// Parses a digest from the given hex string /// /// /// public static Digest Parse(string Text) { return new Digest(StringUtils.ParseHexString(Text)); } /// /// Parses a digest from the given hex string /// /// /// public static Digest Parse(string Text) where T : DigestTraits, new() { return new Digest(StringUtils.ParseHexString(Text)); } /// public override bool Equals(object? Obj) => (Obj is Digest Digest) && Digest.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 ==(Digest A, Digest B) { return A.Memory.Span.SequenceEqual(B.Memory.Span); } /// /// Test two hash values for equality /// /// /// /// public static bool operator !=(Digest A, Digest B) { return !(A == B); } /// /// Implicit conversion operator from memory objects /// /// public static implicit operator Digest(ReadOnlyMemory Memory) { return new Digest(Memory); } /// /// Implicit conversion operator from byte arrays /// /// public static implicit operator Digest(byte[] Memory) { return new Digest(Memory); } } /// /// Traits for a hashing algorithm /// public abstract class DigestTraits { /// /// Length of the produced hash /// public int Length { get; } /// /// Constructor /// /// public DigestTraits(int Length) { this.Length = Length; } /// /// Creates a HashAlgorithm object /// /// public abstract HashAlgorithm CreateAlgorithm(); } /// /// Traits for the MD5 hash algorithm /// public class Md5 : DigestTraits { /// /// Length of the produced digest /// public new const int Length = 16; /// /// Constructor /// public Md5() : base(Length) { } /// public override HashAlgorithm CreateAlgorithm() => MD5.Create(); } /// /// Traits for the SHA1 hash algorithm /// public class Sha1 : DigestTraits { /// /// Length of the produced digest /// public new const int Length = 20; /// /// Constructor /// public Sha1() : base(Length) { } /// public override HashAlgorithm CreateAlgorithm() => SHA1.Create(); } /// /// Traits for the SHA1 hash algorithm /// public class Sha256 : DigestTraits { /// /// Length of the produced digest /// public new const int Length = 32; /// /// Constructor /// public Sha256() : base(Length) { } /// public override HashAlgorithm CreateAlgorithm() => SHA256.Create(); } /// /// Generic HashValue implementation /// public struct Digest where T : DigestTraits, new() { /// /// Traits instance /// public static T Traits { get; } = new T(); /// /// Length of a hash value /// public static int Length => Traits.Length; /// /// Zero digest value /// public static Digest Zero => new Digest(new byte[Traits.Length]); /// /// Memory storing the digest data /// public ReadOnlyMemory Memory; /// /// Accessor for the span of memory storing the data /// public ReadOnlySpan Span => Memory.Span; /// /// Constructor /// /// Memory to construct from public Digest(ReadOnlyMemory Memory) { this.Memory = Memory; } /// public override bool Equals(object? Obj) => (Obj is Digest 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 ==(Digest A, Digest B) => A.Span.SequenceEqual(B.Span); /// /// Test two hash values for equality /// /// /// /// public static bool operator !=(Digest A, Digest B) => !A.Span.SequenceEqual(B.Span); /// /// Implicit conversion operator from memory objects /// /// public static implicit operator Digest(ReadOnlyMemory Memory) { return new Digest(Memory); } /// /// Implicit conversion operator from byte arrays /// /// public static implicit operator Digest(byte[] Memory) { return new Digest(Memory); } } /// /// Extension methods for dealing with digests /// public static class DigestExtensions { /// /// Read a digest from a memory reader /// /// /// public static Digest ReadDigest(this MemoryReader Reader) { return new Digest(Reader.ReadVariableLengthBytes()); } /// /// Read a strongly-typed digest from a memory reader /// /// /// /// public static Digest ReadDigest(this MemoryReader Reader) where T : DigestTraits, new() { return new Digest(Reader.ReadFixedLengthBytes(Digest.Traits.Length)); } /// /// Write a digest to a memory writer /// /// /// public static void WriteDigest(this MemoryWriter Writer, Digest Digest) { Writer.WriteVariableLengthBytes(Digest.Span); } /// /// Write a strongly typed digest to a memory writer /// /// /// /// public static void WriteDigest(this MemoryWriter Writer, Digest Digest) where T : DigestTraits, new() { Writer.WriteFixedLengthBytes(Digest.Span); } } }