// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace Tools.DotNETCommon { /// /// Stores the hash value for a piece of content as a byte array, allowing it to be used as a dictionary key /// public class ContentHash : IEquatable { /// /// The bytes compromising this hash /// public byte[] Bytes { get; private set; } /// /// Constructor /// /// The hash data public ContentHash(byte[] Bytes) { this.Bytes = Bytes; } /// /// Compares two content hashes for equality /// /// The object to compare against /// True if the hashes are equal, false otherwise public override bool Equals(object Other) { return Equals(Other as ContentHash); } /// /// Compares two content hashes for equality /// /// The hash to compare against /// True if the hashes are equal, false otherwise public bool Equals(ContentHash Other) { if((object)Other == null) { return false; } if(Bytes.Length != Other.Bytes.Length) { return false; } for(int Idx = 0; Idx < Bytes.Length; Idx++) { if(Bytes[Idx] != Other.Bytes[Idx]) { return false; } } return true; } /// /// Compares two content hash objects for equality /// /// The first hash to compare /// The second has to compare /// True if the objects are equal, false otherwise public static bool operator==(ContentHash A, ContentHash B) { if((object)A == null) { return ((object)B == null); } else { return A.Equals(B); } } /// /// Compares two content hash objects for inequality /// /// The first hash to compare /// The second has to compare /// True if the objects are not equal, false otherwise public static bool operator!=(ContentHash A, ContentHash B) { return !(A == B); } /// /// Creates a content hash for a block of data, using a given algorithm. /// /// Data to compute the hash for /// Algorithm to use to create the hash /// New content hash instance containing the hash of the data public static ContentHash Compute(byte[] Data, HashAlgorithm Algorithm) { return new ContentHash(Algorithm.ComputeHash(Data)); } /// /// Creates a content hash for a string, using a given algorithm. /// /// Text to compute a hash for /// Algorithm to use to create the hash /// New content hash instance containing the hash of the text public static ContentHash Compute(string Text, HashAlgorithm Algorithm) { return new ContentHash(Algorithm.ComputeHash(Encoding.Unicode.GetBytes(Text))); } /// /// Creates a content hash for a file, using a given algorithm. /// /// File to compute a hash for /// Algorithm to use to create the hash /// New content hash instance containing the hash of the file public static ContentHash Compute(FileReference Location, HashAlgorithm Algorithm) { using(FileStream Stream = FileReference.Open(Location, FileMode.Open, FileAccess.Read, FileShare.Read)) { return new ContentHash(Algorithm.ComputeHash(Stream)); } } /// /// Creates a content hash for a block of data using MD5 /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static ContentHash MD5(byte[] Data) { using(MD5 Algorithm = System.Security.Cryptography.MD5.Create()) { return Compute(Data, Algorithm); } } /// /// Creates a content hash for a string using MD5. /// /// Text to compute a hash for /// New content hash instance containing the hash of the text public static ContentHash MD5(string Text) { using(MD5 Algorithm = System.Security.Cryptography.MD5.Create()) { return Compute(Text, Algorithm); } } /// /// Creates a content hash for a file, using a given algorithm. /// /// File to compute a hash for /// New content hash instance containing the hash of the file public static ContentHash MD5(FileReference Location) { using(MD5 Algorithm = System.Security.Cryptography.MD5.Create()) { return Compute(Location, Algorithm); } } /// /// Creates a content hash for a block of data using SHA1 /// /// Data to compute the hash for /// New content hash instance containing the hash of the data public static ContentHash SHA1(byte[] Data) { using(SHA1 Algorithm = System.Security.Cryptography.SHA1.Create()) { return Compute(Data, Algorithm); } } /// /// Creates a content hash for a string using SHA1. /// /// Text to compute a hash for /// New content hash instance containing the hash of the text public static ContentHash SHA1(string Text) { using(SHA1 Algorithm = System.Security.Cryptography.SHA1.Create()) { return Compute(Text, Algorithm); } } /// /// Creates a content hash for a file using SHA1. /// /// File to compute a hash for /// New content hash instance containing the hash of the file public static ContentHash SHA1(FileReference Location) { using(SHA1 Algorithm = System.Security.Cryptography.SHA1.Create()) { return Compute(Location, Algorithm); } } /// /// Computes a hash code for this digest /// /// Integer value to use as a hash code public override int GetHashCode() { int HashCode = Bytes[0]; for(int Idx = 1; Idx < Bytes.Length; Idx++) { HashCode = (HashCode * 31) + Bytes[Idx]; } return HashCode; } /// /// Formats this hash as a string /// /// The hashed value public override string ToString() { return StringUtils.FormatHexString(Bytes); } } /// /// Utility methods for serializing ContentHash objects /// public static class ContentHashExtensionMethods { /// /// Writes a ContentHash to a binary archive /// /// The writer to output data to /// The hash to write public static void WriteContentHash(this BinaryArchiveWriter Writer, ContentHash Hash) { if(Hash == null) { Writer.WriteByteArray(null); } else { Writer.WriteByteArray(Hash.Bytes); } } /// /// Reads a ContentHash from a binary archive /// /// Reader to serialize data from /// New hash instance public static ContentHash ReadContentHash(this BinaryArchiveReader Reader) { byte[] Data = Reader.ReadByteArray(); if(Data == null) { return null; } else { return new ContentHash(Data); } } } }