File: src\Shared\runtime\Http2\Hpack\IntegerDecoder.cs
Web Access
Project: src\src\Shared\test\Shared.Tests\Microsoft.AspNetCore.Shared.Tests.csproj (Microsoft.AspNetCore.Shared.Tests)
// 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.Numerics;
 
namespace System.Net.Http.HPack
{
    internal struct IntegerDecoder
    {
        private int _i;
        private int _m;
 
        /// <summary>
        /// Decodes the first byte of the integer.
        /// </summary>
        /// <param name="b">
        /// The first byte of the variable-length encoded integer.
        /// </param>
        /// <param name="prefixLength">
        /// The number of lower bits in this prefix byte that the
        /// integer has been encoded into. Must be between 1 and 8.
        /// Upper bits must be zero.
        /// </param>
        /// <param name="result">
        /// If decoded successfully, contains the decoded integer.
        /// </param>
        /// <returns>
        /// If the integer has been fully decoded, true.
        /// Otherwise, false -- <see cref="TryDecode(byte, out int)"/> must be called on subsequent bytes.
        /// </returns>
        /// <remarks>
        /// The term "prefix" can be confusing. From the HPACK spec:
        /// An integer is represented in two parts: a prefix that fills the current octet and an
        /// optional list of octets that are used if the integer value does not fit within the prefix.
        /// </remarks>
        public bool BeginTryDecode(byte b, int prefixLength, out int result)
        {
            Debug.Assert(prefixLength >= 1 && prefixLength <= 8);
            Debug.Assert((b & ~((1 << prefixLength) - 1)) == 0, "bits other than prefix data must be set to 0.");
 
            if (b < ((1 << prefixLength) - 1))
            {
                result = b;
                return true;
            }
 
            _i = b;
            _m = 0;
            result = 0;
            return false;
        }
 
        /// <summary>
        /// Decodes subsequent bytes of an integer.
        /// </summary>
        /// <param name="b">The next byte.</param>
        /// <param name="result">
        /// If decoded successfully, contains the decoded integer.
        /// </param>
        /// <returns>If the integer has been fully decoded, true. Otherwise, false -- <see cref="TryDecode(byte, out int)"/> must be called on subsequent bytes.</returns>
        public bool TryDecode(byte b, out int result)
        {
            // Check if shifting b by _m would result in > 31 bits.
            // No masking is required: if the 8th bit is set, it indicates there is a
            // bit set in a future byte, so it is fine to check that here as if it were
            // bit 0 on the next byte.
            // This is a simplified form of:
            //   int additionalBitsRequired = 32 - BitOperations.LeadingZeroCount((uint)b);
            //   if (_m + additionalBitsRequired > 31)
            if (BitOperations.LeadingZeroCount((uint)b) <= _m)
            {
                throw new HPackDecodingException(SR.net_http_hpack_bad_integer);
            }
 
            _i += ((b & 0x7f) << _m);
 
            // If the addition overflowed, the result will be negative.
            if (_i < 0)
            {
                throw new HPackDecodingException(SR.net_http_hpack_bad_integer);
            }
 
            _m += 7;
 
            if ((b & 128) == 0)
            {
                if (b == 0 && _m / 7 > 1)
                {
                    // Do not accept overlong encodings.
                    throw new HPackDecodingException(SR.net_http_hpack_bad_integer);
                }
 
                result = _i;
                return true;
            }
 
            result = 0;
            return false;
        }
    }
}