// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Buffers.Binary; using System.ComponentModel; using System.Globalization; using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Serialization; namespace EpicGames.Core { /// /// Struct representing a strongly typed Sha1Hash value. /// [JsonConverter(typeof(Sha1HashJsonConverter))] [TypeConverter(typeof(Sha1HashTypeConverter))] public struct Sha1Hash : IEquatable, IComparable { /// /// Length of an Sha1Hash /// public const int NumBytes = 20; /// /// Length of the hash in bits /// public const int NumBits = NumBytes * 8; readonly ulong _a; readonly ulong _b; readonly uint _c; /// /// Hash consisting of zeroes /// public static Sha1Hash Zero { get; } = new Sha1Hash(0, 0, 0); /// /// Constructor /// /// Memory to construct from public Sha1Hash(ReadOnlySpan span) : this(BinaryPrimitives.ReadUInt64BigEndian(span), BinaryPrimitives.ReadUInt64BigEndian(span.Slice(8)), BinaryPrimitives.ReadUInt32BigEndian(span.Slice(16))) { } /// /// Constructor /// /// Memory to construct from public Sha1Hash(ulong a, ulong b, uint c) { _a = a; _b = b; _c = c; } /// /// 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 Sha1Hash Compute(ReadOnlySpan data) { byte[] output = new byte[20]; using (SHA1 sha1 = SHA1.Create()) { int bytesWritten; if (!sha1.TryComputeHash(data, output, out bytesWritten) || bytesWritten != NumBytes) { throw new Exception($"Unable to hash data"); } } return new Sha1Hash(output); } /// /// Parses a digest from the given hex string /// /// /// public static Sha1Hash Parse(string text) { return new Sha1Hash(StringUtils.ParseHexString(text)); } /// /// Parses a digest from the given hex string /// /// /// public static Sha1Hash Parse(ReadOnlySpan text) { return new Sha1Hash(StringUtils.ParseHexString(text)); } /// public int CompareTo(Sha1Hash other) { if (_a != other._a) { return (_a < other._a) ? -1 : +1; } else if (_b != other._b) { return (_b < other._b) ? -1 : +1; } else { return (_c < other._c) ? -1 : +1; } } /// public bool Equals(Sha1Hash other) => _a == other._a && _b == other._b && _c == other._c; /// public override bool Equals(object? obj) => (obj is Sha1Hash hash) && Equals(hash); /// public override int GetHashCode() => (int)_a; /// public Utf8String ToUtf8String() => StringUtils.FormatUtf8HexString(ToByteArray()); /// public override string ToString() => StringUtils.FormatHexString(ToByteArray()); public byte[] ToByteArray() { byte[] data = new byte[NumBytes]; CopyTo(data); return data; } /// /// Copies this hash into a span /// /// public void CopyTo(Span span) { BinaryPrimitives.WriteUInt64BigEndian(span, _a); BinaryPrimitives.WriteUInt64BigEndian(span[8..], _b); BinaryPrimitives.WriteUInt32BigEndian(span[16..], _c); } /// /// Test two hash values for equality /// public static bool operator ==(Sha1Hash a, Sha1Hash b) => a.Equals(b); /// /// Test two hash values for equality /// public static bool operator !=(Sha1Hash a, Sha1Hash b) => !(a == b); /// /// Tests whether A > B /// public static bool operator >(Sha1Hash a, Sha1Hash b) => a.CompareTo(b) > 0; /// /// Tests whether A is less than B /// public static bool operator <(Sha1Hash a, Sha1Hash b) => a.CompareTo(b) < 0; /// /// Tests whether A is greater than or equal to B /// public static bool operator >=(Sha1Hash a, Sha1Hash b) => a.CompareTo(b) >= 0; /// /// Tests whether A is less than or equal to B /// public static bool operator <=(Sha1Hash a, Sha1Hash b) => a.CompareTo(b) <= 0; } /// /// Extension methods for dealing with Sha1Hash values /// public static class Sha1HashExtensions { /// /// Read an from a memory reader /// /// /// public static Sha1Hash ReadSha1Hash(this MemoryReader reader) { return new Sha1Hash(reader.ReadFixedLengthBytes(Sha1Hash.NumBytes).Span); } /// /// Write an to a memory writer /// /// /// public static void WriteSha1Hash(this MemoryWriter writer, Sha1Hash hash) { hash.CopyTo(writer.AllocateSpan(Sha1Hash.NumBytes)); } } /// /// Type converter for Sha1Hash to and from JSON /// sealed class Sha1HashJsonConverter : JsonConverter { /// public override Sha1Hash Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Sha1Hash.Parse(reader.ValueSpan); /// public override void Write(Utf8JsonWriter writer, Sha1Hash value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToUtf8String().Span); } /// /// Type converter from strings to Sha1Hash objects /// sealed class Sha1HashTypeConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string); } /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { return Sha1Hash.Parse((string)value); } } }