File: Resources\SpanReaderExtensions.cs
Web Access
Project: Microsoft.DotNet.Cli.CoreUtils.csproj (Microsoft.DotNet.Cli.CoreUtils)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;

namespace Microsoft.DotNet.Cli.Resources.Internal;

/// <summary>
///  Extensions for <see cref="SpanReader{T}"/>.
/// </summary>
internal static class SpanReaderExtensions
{
    /// <param name="reader">The <see cref="SpanReader{T}"/> to read from.</param>
    extension(ref SpanReader<char> reader)
    {
        /// <summary>
        ///  Tries to read an integer from the current position of the <see cref="SpanReader{T}"/>.
        /// </summary>
        /// <param name="value">When successful, contains the read integer.</param>
        /// <returns>
        ///  <see langword="true"/> if an integer was successfully read; otherwise, <see langword="false"/>.
        /// </returns>
        public bool TryReadPositiveInteger(out uint value)
        {
            // Read digits until we hit a non-digit character or the end of the span.
            value = default;
            bool foundDigit = false;

            while (reader.TryPeek(out char next) && char.IsDigit(next))
            {
                value = value * 10u + (uint)(next - '0');
                reader.Advance(1);
                foundDigit = true;
            }

            return foundDigit;
        }
    }

    /// <param name="reader">The <see cref="SpanReader{T}"/> to read from.</param>
    extension(ref SpanReader<byte> reader)
    {
        /// <summary>
        ///  Reads a little-endian <see cref="int"/> and advances past it.
        /// </summary>
        /// <param name="value">On success, the value read.</param>
        /// <returns>
        ///  <see langword="true"/> if four bytes were available and read; otherwise <see langword="false"/>.
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryReadInt32LittleEndian(out int value)
        {
            if (reader.TryRead(sizeof(int), out ReadOnlySpan<byte> bytes))
            {
                value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
                return true;
            }

            value = 0;
            return false;
        }

        /// <summary>
        ///  Reads a 7-bit encoded (LEB128) <see cref="int"/> and advances past it.
        /// </summary>
        /// <remarks>
        ///  <para>
        ///   Matches <see cref="System.IO.BinaryWriter.Write7BitEncodedInt(int)"/>. Returns
        ///   <see langword="false"/> for a truncated or overlong (overflowing) encoding rather than
        ///   throwing, leaving the reader's position unchanged, so it is safe on untrusted input.
        ///  </para>
        /// </remarks>
        /// <param name="value">On success, the value read.</param>
        /// <returns>
        ///  <see langword="true"/> if a well-formed value was read; otherwise <see langword="false"/>.
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryRead7BitEncodedInt32(out int value)
        {
            int start = reader.Position;
            uint result = 0;
            const int MaxBytesWithoutOverflow = 4;

            for (int shift = 0; shift < MaxBytesWithoutOverflow * 7; shift += 7)
            {
                if (!reader.TryRead(out byte b))
                {
                    value = 0;
                    reader.Position = start;
                    return false;
                }

                result |= (uint)(b & 0x7F) << shift;
                if (b <= 0x7F)
                {
                    value = (int)result;
                    return true;
                }
            }

            // The fifth byte can only contribute the top four bits; more would overflow an int.
            if (!reader.TryRead(out byte last) || last > 0b1111)
            {
                value = 0;
                reader.Position = start;
                return false;
            }

            result |= (uint)last << (MaxBytesWithoutOverflow * 7);
            value = (int)result;
            return true;
        }
    }
}