File: Utilities\Checksum.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Utilities.Shared\Microsoft.AspNetCore.Razor.Utilities.Shared.csproj (Microsoft.AspNetCore.Razor.Utilities.Shared)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace Microsoft.AspNetCore.Razor.Utilities;
 
/// <summary>
///  Checksum of data can be used later to see whether two data are same or not
///  without actually comparing data itself.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = HashSize)]
internal readonly partial record struct Checksum(
    [field: FieldOffset(0)] long Data1,
    [field: FieldOffset(8)] long Data2) : IComparable<Checksum>
{
    /// <summary>
    ///  The intended size of the <see cref="Checksum"/> in bytes.
    /// </summary>
    private const int HashSize = 16;
 
#if !NET
    // Small (HashSize length), per-thread array to use when converting to base64 on non-.NET.
    [ThreadStatic]
    private static byte[]? s_bytes;
#endif
 
    /// <summary>
    ///  Represents a default/null/invalid Checksum, equivalent to <c>default(Checksum)</c>. This value
    ///  contains all zeroes, which is considered infinitesimally unlikely to ever happen from hashing data
    ///  (including when hashing null/empty/zero data inputs).
    /// </summary>
    public static readonly Checksum Null = default;
 
    public static Checksum From(byte[] bytes)
        => From(bytes.AsSpan());
 
    public static Checksum From(ImmutableArray<byte> bytes)
        => From(bytes.AsSpan());
 
    public static Checksum From(ReadOnlySpan<byte> bytes)
    {
        ArgHelper.ThrowIfLessThan(bytes.Length, HashSize);
 
        if (!MemoryMarshal.TryRead(bytes, out Checksum result))
        {
            return ThrowHelper.ThrowInvalidOperationException<Checksum>("Could not read hash data");
        }
 
        return result;
    }
 
    public string ToBase64String()
    {
#if NET
        Span<byte> bytes = stackalloc byte[HashSize];
        WriteTo(bytes);
        return Convert.ToBase64String(bytes);
#else
        var bytes = s_bytes ??= new byte[HashSize];
        WriteTo(bytes.AsSpan());
        return Convert.ToBase64String(bytes);
#endif
    }
 
    public static Checksum FromBase64String(string value)
        => From(Convert.FromBase64String(value));
 
    public void WriteTo(Span<byte> destination)
    {
        ArgHelper.ThrowIfDestinationTooShort(destination, HashSize);
        Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), this);
    }
 
    public bool Equals(Checksum other)
        => Data1 == other.Data1 &&
           Data2 == other.Data2;
 
    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 int CompareTo(Checksum other)
    {
        var result = Data1.CompareTo(other.Data1);
        return result != 0 ? result : Data2.CompareTo(other.Data2);
    }
 
    public override string ToString()
        => ToBase64String();
}