File: Utilities\Checksum.Builder.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.Buffers.Binary;
using System.Diagnostics;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace Microsoft.AspNetCore.Razor.Utilities;
 
internal readonly partial record struct Checksum
{
    internal readonly ref partial struct Builder
    {
        private const int XxHash128SizeBytes = 128 / 8;
 
        private static readonly XxHash128Pool s_hashPool = XxHash128Pool.Default;
 
        private enum TypeKind : byte
        {
            Null,
            Bool,
            Int32,
            Int64,
            String,
            Checksum,
            Byte,
            Char,
        }
 
        // Small (8 byte), per-thread byte array to use as a buffer for appending primitive values to the hash.
        [ThreadStatic]
        private static byte[]? s_buffer;
 
        private readonly XxHash128 _hash;
 
        public Builder()
        {
            _hash = s_hashPool.Get();
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static Span<byte> GetBuffer(int length = 8)
        {
            Debug.Assert(length <= 8, $"length should never be greater than 8.");
 
            var buffer = s_buffer ??= new byte[8];
            return buffer.AsSpan(0, length);
        }
 
        public Checksum FreeAndGetChecksum()
        {
            Span<byte> hash = stackalloc byte[XxHash128SizeBytes];
            _hash.GetHashAndReset(hash);
            var result = From(hash);
 
            s_hashPool.Return(_hash);
            return result;
        }
 
        private void AppendBuffer(int count)
        {
            Debug.Assert(s_buffer is not null);
 
            _hash.Append(s_buffer.AsSpan(0, count));
        }
 
        private void AppendTypeKind(TypeKind kind)
        {
            AppendByteValue((byte)kind);
        }
 
        private void AppendBoolValue(bool value)
        {
            AppendByteValue((byte)(value ? 1 : 0));
        }
 
        private void AppendByteValue(byte value)
        {
            var buffer = GetBuffer(length: sizeof(byte));
            buffer[0] = value;
            _hash.Append(buffer);
        }
 
        private void AppendCharValue(char value)
        {
            var buffer = GetBuffer(length: sizeof(char));
            BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
            _hash.Append(buffer);
        }
 
        private void AppendInt32Value(int value)
        {
            var buffer = GetBuffer(length: sizeof(int));
            BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
            _hash.Append(buffer);
        }
 
        private void AppendInt64Value(long value)
        {
            var buffer = GetBuffer(length: sizeof(long));
            BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
            _hash.Append(buffer);
        }
 
        private void AppendStringValue(string value)
        {
            _hash.Append(MemoryMarshal.AsBytes(value.AsSpan()));
            _hash.Append(MemoryMarshal.AsBytes("\0".AsSpan()));
        }
        private void AppendChecksumValue(Checksum value)
        {
            AppendInt64Value(value.Data1);
            AppendInt64Value(value.Data2);
        }
 
        public void AppendNull()
        {
            AppendTypeKind(TypeKind.Null);
        }
 
        public void Append(bool value)
        {
            AppendTypeKind(TypeKind.Bool);
            AppendBoolValue(value);
        }
 
        public void Append(byte value)
        {
            AppendTypeKind(TypeKind.Byte);
            AppendByteValue(value);
        }
 
        public void Append(char value)
        {
            AppendTypeKind(TypeKind.Char);
            AppendCharValue(value);
        }
 
        public void Append(int value)
        {
            AppendTypeKind(TypeKind.Int32);
            AppendInt32Value(value);
        }
 
        public void Append(long value)
        {
            AppendTypeKind(TypeKind.Int64);
            AppendInt64Value(value);
        }
 
        public void Append(string? value)
        {
            if (value is null)
            {
                AppendNull();
                return;
            }
 
            AppendTypeKind(TypeKind.String);
            AppendStringValue(value);
        }
 
        public void Append(Checksum value)
        {
            AppendTypeKind(TypeKind.Checksum);
            AppendChecksumValue(value);
        }
 
        public void Append(object? value)
        {
            switch (value)
            {
                case null:
                    AppendNull();
                    break;
 
                case string s:
                    Append(s);
                    break;
 
                case Checksum c:
                    Append(c);
                    break;
 
                case bool b:
                    Append(b);
                    break;
 
                case int i:
                    Append(i);
                    break;
 
                case long l:
                    Append(l);
                    break;
 
                case byte b:
                    Append(b);
                    break;
 
                case char c:
                    Append(c);
                    break;
 
                default:
                    throw new ArgumentException(
                        SR.FormatUnsupported_type_0(value.GetType().FullName), nameof(value));
            }
        }
    }
}