File: Workspace\Solution\Checksum.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Checksum of data can be used later to see whether two data are same or not
    /// without actually comparing data itself
    /// </summary>
    [DataContract]
    internal sealed partial class Checksum : IObjectWritable, IEquatable<Checksum>
    {
        /// <summary>
        /// The intended size of the <see cref="HashData"/> structure. 
        /// </summary>
        public const int HashSize = 20;
 
        public static readonly Checksum Null = new(default);
 
        [DataMember(Order = 0)]
        private readonly HashData _checksum;
 
        public Checksum(HashData hash)
            => _checksum = hash;
 
        /// <summary>
        /// Create Checksum from given byte array. if byte array is bigger than <see cref="HashSize"/>, it will be
        /// truncated to the size.
        /// </summary>
        public static Checksum From(byte[] checksum)
            => From(checksum.AsSpan());
 
        /// <summary>
        /// Create Checksum from given byte array. if byte array is bigger than <see cref="HashSize"/>, it will be
        /// truncated to the size.
        /// </summary>
        public static Checksum From(ImmutableArray<byte> checksum)
            => From(checksum.AsSpan());
 
        public static Checksum From(ReadOnlySpan<byte> checksum)
        {
            if (checksum.Length == 0)
                return Null;
 
            if (checksum.Length < HashSize)
                throw new ArgumentException($"checksum must be equal or bigger than the hash size: {HashSize}", nameof(checksum));
 
            Contract.ThrowIfFalse(MemoryMarshal.TryRead(checksum, out HashData hash));
            return new Checksum(hash);
        }
 
        public bool Equals(Checksum other)
        {
            return other != null && _checksum == other._checksum;
        }
 
        public override bool Equals(object obj)
            => Equals(obj as Checksum);
 
        public override int GetHashCode()
            => _checksum.GetHashCode();
 
        public string ToBase64String()
        {
#if NETCOREAPP
            Span<byte> bytes = stackalloc byte[HashSize];
            this.WriteTo(bytes);
            return Convert.ToBase64String(bytes);
#else
            unsafe
            {
                var data = new byte[HashSize];
                fixed (byte* dataPtr = data)
                {
                    *(HashData*)dataPtr = _checksum;
                }
 
                return Convert.ToBase64String(data, 0, HashSize);
            }
#endif
        }
 
        public static Checksum FromBase64String(string value)
            => value == null ? null : From(Convert.FromBase64String(value));
 
        public override string ToString()
            => ToBase64String();
 
        public static bool operator ==(Checksum left, Checksum right)
            => EqualityComparer<Checksum>.Default.Equals(left, right);
 
        public static bool operator !=(Checksum left, Checksum right)
            => !(left == right);
 
        public static bool operator ==(Checksum left, HashData right)
            => left._checksum == right;
 
        public static bool operator !=(Checksum left, HashData right)
            => !(left == right);
 
        bool IObjectWritable.ShouldReuseInSerialization => true;
 
        public void WriteTo(ObjectWriter writer)
            => _checksum.WriteTo(writer);
 
        public void WriteTo(Span<byte> span)
        {
            Contract.ThrowIfFalse(span.Length >= HashSize);
            Contract.ThrowIfFalse(MemoryMarshal.TryWrite(span, ref Unsafe.AsRef(in _checksum)));
        }
 
        public static Checksum ReadFrom(ObjectReader reader)
            => new(HashData.ReadFrom(reader));
 
        public static Func<Checksum, string> GetChecksumLogInfo { get; }
            = checksum => checksum.ToString();
 
        public static Func<IEnumerable<Checksum>, string> GetChecksumsLogInfo { get; }
            = checksums => string.Join("|", checksums.Select(c => c.ToString()));
 
        /// <summary>
        /// This structure stores the 20-byte hash as an inline value rather than requiring the use of
        /// <c>byte[]</c>.
        /// </summary>
        [DataContract]
        [StructLayout(LayoutKind.Explicit, Size = HashSize)]
        public readonly struct HashData : IEquatable<HashData>
        {
            [FieldOffset(0), DataMember(Order = 0)]
            private readonly long Data1;
 
            [FieldOffset(8), DataMember(Order = 1)]
            private readonly long Data2;
 
            [FieldOffset(16), DataMember(Order = 2)]
            private readonly int Data3;
 
            public HashData(long data1, long data2, int data3)
            {
                Data1 = data1;
                Data2 = data2;
                Data3 = data3;
            }
 
            public static bool operator ==(HashData x, HashData y)
                => x.Equals(y);
 
            public static bool operator !=(HashData x, HashData y)
                => !(x == y);
 
            public void WriteTo(ObjectWriter writer)
            {
                writer.WriteInt64(Data1);
                writer.WriteInt64(Data2);
                writer.WriteInt32(Data3);
            }
 
            public static unsafe HashData FromPointer(HashData* hash)
                => new(hash->Data1, hash->Data2, hash->Data3);
 
            public static HashData ReadFrom(ObjectReader reader)
                => new(reader.ReadInt64(), reader.ReadInt64(), reader.ReadInt32());
 
            public override int GetHashCode()
            {
                // The checksum is already a hash. Just read a 4-byte value to get a well-distributed hash code.
                return (int)Data1;
            }
 
            public override bool Equals(object obj)
                => obj is HashData other && Equals(other);
 
            public bool Equals(HashData other)
            {
                return Data1 == other.Data1
                    && Data2 == other.Data2
                    && Data3 == other.Data3;
            }
        }
    }
}