File: src\Shared\ServerInfrastructure\BufferExtensions.cs
Web Access
Project: src\src\Servers\Kestrel\samples\http2cat\http2cat.csproj (http2cat)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
 
#nullable enable
 
namespace System.Buffers;
 
internal static class BufferExtensions
{
    private const int _maxULongByteLength = 20;
 
    [ThreadStatic]
    private static byte[]? _numericBytesScratch;
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ReadOnlySpan<byte> ToSpan(in this ReadOnlySequence<byte> buffer)
    {
        if (buffer.IsSingleSegment)
        {
            return buffer.FirstSpan;
        }
        return buffer.ToArray();
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void CopyTo(in this ReadOnlySequence<byte> buffer, PipeWriter pipeWriter)
    {
        if (buffer.IsSingleSegment)
        {
            pipeWriter.Write(buffer.FirstSpan);
        }
        else
        {
            CopyToMultiSegment(buffer, pipeWriter);
        }
    }
 
    private static void CopyToMultiSegment(in ReadOnlySequence<byte> buffer, PipeWriter pipeWriter)
    {
        foreach (var item in buffer)
        {
            pipeWriter.Write(item.Span);
        }
    }
 
    public static ArraySegment<byte> GetArray(this Memory<byte> buffer)
    {
        return ((ReadOnlyMemory<byte>)buffer).GetArray();
    }
 
    public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
    {
        if (!MemoryMarshal.TryGetArray(memory, out var result))
        {
            throw new InvalidOperationException("Buffer backed by array was expected");
        }
        return result;
    }
 
    /// <summary>
    /// Returns position of first occurrence of item in the <see cref="ReadOnlySequence{T}"/>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static SequencePosition? PositionOfAny<T>(in this ReadOnlySequence<T> source, T value0, T value1) where T : IEquatable<T>
    {
        if (source.IsSingleSegment)
        {
            int index = source.First.Span.IndexOfAny(value0, value1);
            if (index != -1)
            {
                return source.GetPosition(index);
            }
 
            return null;
        }
        else
        {
            return PositionOfAnyMultiSegment(source, value0, value1);
        }
    }
 
    private static SequencePosition? PositionOfAnyMultiSegment<T>(in ReadOnlySequence<T> source, T value0, T value1) where T : IEquatable<T>
    {
        SequencePosition position = source.Start;
        SequencePosition result = position;
        while (source.TryGet(ref position, out ReadOnlyMemory<T> memory))
        {
            int index = memory.Span.IndexOfAny(value0, value1);
            if (index != -1)
            {
                return source.GetPosition(index, result);
            }
            else if (position.GetObject() == null)
            {
                break;
            }
 
            result = position;
        }
 
        return null;
    }
 
    internal static void WriteAscii(ref this BufferWriter<PipeWriter> buffer, string data)
    {
        if (string.IsNullOrEmpty(data))
        {
            return;
        }
 
        var dest = buffer.Span;
        var sourceLength = data.Length;
        // Fast path, try encoding to the available memory directly
        if (sourceLength <= dest.Length)
        {
            Encoding.ASCII.GetBytes(data, dest);
            buffer.Advance(sourceLength);
        }
        else
        {
            WriteEncodedMultiWrite(ref buffer, data, sourceLength, Encoding.ASCII);
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    internal static void WriteNumeric(ref this BufferWriter<PipeWriter> bufferWriter, ulong number)
    {
        const byte AsciiDigitStart = (byte)'0';
 
        var buffer = bufferWriter.Span;
 
        // Fast path, try copying to the available memory directly
        if (number < 10 && buffer.Length >= 1)
        {
            buffer[0] = (byte)(((uint)number) + AsciiDigitStart);
            bufferWriter.Advance(1);
        }
        else if (number < 100 && buffer.Length >= 2)
        {
            var val = (uint)number;
            var tens = (uint)(byte)((val * 205u) >> 11); // div10, valid to 1028
 
            buffer[0] = (byte)(tens + AsciiDigitStart);
            buffer[1] = (byte)(val - (tens * 10) + AsciiDigitStart);
            bufferWriter.Advance(2);
        }
        else if (number < 1000 && buffer.Length >= 3)
        {
            var val = (uint)number;
            var digit0 = (uint)(byte)((val * 41u) >> 12); // div100, valid to 1098
            var digits01 = (uint)(byte)((val * 205u) >> 11); // div10, valid to 1028
 
            buffer[0] = (byte)(digit0 + AsciiDigitStart);
            buffer[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
            buffer[2] = (byte)(val - (digits01 * 10) + AsciiDigitStart);
            bufferWriter.Advance(3);
        }
        else
        {
            WriteNumericMultiWrite(ref bufferWriter, number);
        }
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void WriteNumericMultiWrite(ref this BufferWriter<PipeWriter> buffer, ulong number)
    {
        const byte AsciiDigitStart = (byte)'0';
 
        var value = number;
        var position = _maxULongByteLength;
        var byteBuffer = NumericBytesScratch;
        do
        {
            // Consider using Math.DivRem() if available
            var quotient = value / 10;
            byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
            value = quotient;
        }
        while (value != 0);
 
        var length = _maxULongByteLength - position;
        buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
    }
 
    internal static void WriteEncoded(ref this BufferWriter<PipeWriter> buffer, string data, Encoding encoding)
    {
        if (string.IsNullOrEmpty(data))
        {
            return;
        }
 
        var dest = buffer.Span;
        var sourceLength = encoding.GetByteCount(data);
        // Fast path, try encoding to the available memory directly
        if (sourceLength <= dest.Length)
        {
            encoding.GetBytes(data, dest);
            buffer.Advance(sourceLength);
        }
        else
        {
            WriteEncodedMultiWrite(ref buffer, data, sourceLength, encoding);
        }
    }
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void WriteEncodedMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data, int encodedLength, Encoding encoding)
    {
        var source = data.AsSpan();
        var totalBytesUsed = 0;
        var encoder = encoding.GetEncoder();
        var minBufferSize = encoding.GetMaxByteCount(1);
        buffer.Ensure(minBufferSize);
        var bytes = buffer.Span;
        var completed = false;
 
        // This may be a bug, but encoder.Convert returns completed = true for UTF7 too early.
        // Therefore, we check encodedLength - totalBytesUsed too.
        while (!completed || encodedLength - totalBytesUsed != 0)
        {
            // Zero length spans are possible, though unlikely.
            // encoding.Convert and .Advance will both handle them so we won't special case for them.
            encoder.Convert(source, bytes, flush: true, out var charsUsed, out var bytesUsed, out completed);
            buffer.Advance(bytesUsed);
 
            totalBytesUsed += bytesUsed;
            if (totalBytesUsed >= encodedLength)
            {
                Debug.Assert(totalBytesUsed == encodedLength);
                // Encoded everything
                break;
            }
 
            source = source.Slice(charsUsed);
 
            // Get new span, more to encode.
            buffer.Ensure(minBufferSize);
            bytes = buffer.Span;
        }
    }
 
    private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
 
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static byte[] CreateNumericBytesScratch()
    {
        var bytes = new byte[_maxULongByteLength];
        _numericBytesScratch = bytes;
        return bytes;
    }
}