// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers.Binary; using System.Security.Cryptography; using System.Text; #pragma warning disable CA1000 // Do not declare static members on generic types #pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms namespace EpicGames.Core { /// /// Struct representing a weakly typed hash value. Counterpart to - a strongly typed digest. /// public readonly struct Digest : IEquatable { /// /// Memory storing the digest data /// public ReadOnlyMemory Memory { get; } /// /// Accessor for the span of memory storing the data /// public ReadOnlySpan Span => Memory.Span; /// /// Constructor /// /// Memory to construct from public Digest(ReadOnlyMemory memory) { 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) && Equals(digest); /// public bool Equals(Digest other) => other.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 /// /// protected DigestTraits(int length) { 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 readonly struct Digest : IEquatable> 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 { get; } /// /// Accessor for the span of memory storing the data /// public ReadOnlySpan Span => Memory.Span; /// /// Constructor /// /// Memory to construct from public Digest(ReadOnlyMemory memory) { Memory = memory; } /// public override bool Equals(object? obj) => (obj is Digest hash) && Equals(hash); /// public override int GetHashCode() => BinaryPrimitives.ReadInt32LittleEndian(Span); /// public override string ToString() => StringUtils.FormatHexString(Memory.Span); /// public bool Equals(Digest other) => other.Span.SequenceEqual(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.ReadVariableLengthBytesWithInt32Length()); } /// /// 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.WriteVariableLengthBytesWithInt32Length(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); } } }