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