// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers.Binary; using System.ComponentModel; using System.Globalization; using System.IO; using System.Security.Cryptography; #pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms namespace EpicGames.Core { /// /// Struct representing a strongly typed Md5Hash value /// [TypeConverter(typeof(Md5HashTypeConverter))] public readonly struct Md5Hash : IEquatable, IComparable { /// /// Length of an Md5Hash /// public const int NumBytes = 16; /// /// 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 Md5Hash Zero { get; } = new Md5Hash(new byte[NumBytes]); /// /// Constructor /// /// Memory to construct from public Md5Hash(ReadOnlyMemory memory) { if (memory.Length != NumBytes) { throw new ArgumentException($"Md5Hash must be {NumBytes} bytes long"); } 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 Md5Hash Compute(ReadOnlySpan data) { byte[] output = new byte[NumBytes]; if (!MD5.TryHashData(data, output, out int bytesWritten) || bytesWritten != NumBytes) { throw new Exception("Unable to hash data"); } return new Md5Hash(output); } /// /// Creates a content hash for the input Stream object /// /// The Stream object to compoute the has for /// New content hash instance containing the hash of the data public static Md5Hash Compute(Stream stream) { using (MD5 hasher = MD5.Create()) { return new Md5Hash(hasher.ComputeHash(stream)); } } /// /// Parses a digest from the given hex string /// /// /// public static Md5Hash Parse(string text) { return new Md5Hash(StringUtils.ParseHexString(text)); } /// /// Parses a digest from the given hex string /// /// /// public static Md5Hash Parse(Utf8String text) { return new Md5Hash(StringUtils.ParseHexString(text.Span)); } /// public int CompareTo(Md5Hash 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(Md5Hash other) => Span.SequenceEqual(other.Span); /// public override bool Equals(object? obj) => (obj is Md5Hash 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 ==(Md5Hash a, Md5Hash b) => a.Span.SequenceEqual(b.Span); /// /// Test two hash values for equality /// public static bool operator !=(Md5Hash a, Md5Hash b) => !(a == b); /// /// Tests whether A > B /// public static bool operator >(Md5Hash a, Md5Hash b) => a.CompareTo(b) > 0; /// /// Tests whether A is less than B /// public static bool operator <(Md5Hash a, Md5Hash b) => a.CompareTo(b) < 0; /// /// Tests whether A is greater than or equal to B /// public static bool operator >=(Md5Hash a, Md5Hash b) => a.CompareTo(b) >= 0; /// /// Tests whether A is less than or equal to B /// public static bool operator <=(Md5Hash a, Md5Hash b) => a.CompareTo(b) <= 0; } /// /// Extension methods for dealing with Md5Hash values /// public static class Md5HashExtensions { /// /// Read an from a memory reader /// /// /// public static Md5Hash ReadMd5Hash(this MemoryReader reader) { return new Md5Hash(reader.ReadFixedLengthBytes(Md5Hash.NumBytes)); } /// /// Write an to a memory writer /// /// /// public static void WriteMd5Hash(this MemoryWriter writer, Md5Hash hash) { writer.WriteFixedLengthBytes(hash.Span); } } /// /// Type converter from strings to Md5Hash objects /// sealed class Md5HashTypeConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } /// public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return Md5Hash.Parse((string)value); } } }